@borkdude In response to https://clojurians.slack.com/archives/C015LCR9MHD/p1757793567783709?thread_ts=1757700192.260519&cid=C015LCR9MHD 🧵
Yeah if we're interpreting Clojure code, how are we supposed to handle case*, I don't see it in the Clojure docs as one of the supported "special forms". Do we just roll our own implementation that matches the case doc string and call it good?
I'm going off this page, FYI https://clojure.org/reference/special_forms
according to
user=> (keys clojure.lang.Compiler/specials)
(& monitor-exit case* try reify* finally loop* do letfn* if clojure.core/import* new deftype* let* fn* recur set! . var quote catch throw monitor-enter def)
case* is a special symbolSeems to be a private token for the compiler. Not so much an API stable construct. https://clojurians.slack.com/archives/C03S1KBA2/p1757865364019049?thread_ts=1757865364.019049&cid=C03S1KBA2
it depends on how far you want to go in Clojure compatibility. Some tools assume there is something like case* which is why I adopted it in SCI, else some programs just won't run in bb
programs like tools.analyzer-like stuff
Interesting. But if I'm interpreting Clojure source, and I'm written in pure Clojure, and I want to support as many hosts as possible, maybe it's best to not know case* exists?
Just draw the line at clojure.core/case and implement it custom instead of macroexpanding?
it depends what your goals are
I used to just treat case as a special form, but got hit by hit when running programs in bb. That's why I changed it. Maybe for other stuff it's fine not to do it.
I'm using interpretation as a form of tracing, but I don't support tracing inside clojure.core
Then it sounds fine not to go beyond case. FWIW, in bb/SCI the case macro just expands in the same form but replaces case with case* ;)
Oh wait, even easier. I can just wrap case* in a variadic fn. Something that I can call apply on. Then I get the compiler's implementation for free without rolling my own.
This is a simplified example and does not work for all fn calls. But shows how recursion can potentially be controlled.
(def ctx
(let [max-cnt 500]
(sci/init {:namespaces {'clojure.core
{'fn ^:sci/macro
(fn [_form _env args & body]
`(let [cnt# (volatile! ~max-cnt)]
(fn* ~args
(if (< (vswap! cnt# dec) 0)
(throw (ex-info (str "Max iteration reached " ~max-cnt) {:max ~max-cnt}))
~@body))))}}})))
(sci/eval-string* ctx "((fn [i] (if (zero? i) :done (recur (dec i)))) 499)") ;=> :done
(sci/eval-string* ctx "((fn [i] (if (zero? i) :done (recur (dec i)))) 500)") ;=> throws
However, fn* can also be called directly and not sure what we can do about that
(sci/eval-string* ctx "((fn* [i] (if (zero? i) :done-a-lot-more-than-500 (recur (dec i)))) 1000)")you can prevent people calling fn* directly with :deny '[fn*]
but not sure if fn will still work in that case
I guess not
No it doesn't
(sci/eval-string "((fn [a] a) 1)" {:deny ['fn*]}) ;=> clojure.lang.ExceptionInfo: fn* is not allowed! [at :1:2]
well, that's something that can be fixed
pretty easily
ok cool, i have been mostly approaching it from a user perspective so I thought it would not be that easy
I've done a similar thing for loop and core macros that expand to loop like doseq
Yeah I remember. I think together it would tackle the infinite loop "problem"
user=> (sci/eval-string "(doseq [i [1 2 3]] i)" {:deny '[loop]})
nilhmm:
user=> (sci/eval-string "(doseq [i [1 2 3]] i)" {:deny '[loop loop*]})
Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:47).
loop* is not allowed!
I guess that needs another fix for loop*Cool, I have no idea, but i am happy to hear you think it is doable
So babashka and others can have the fast version and people caring about this safety can have a version with a counter
yes, it's fixable. I have some special cases here where I check if it's not this specific symbol: https://github.com/babashka/sci/blob/6758ba028da559c536a06becbbedade7b0ba6448/src/sci/impl/utils.cljc#L170
I think loop* wasn't part of SCI before
got introduced later
So I am guessing you will change something at the analyzer level?
the deny logic checks if it's not that specific symbol with identical?
it got introduced here i think https://github.com/babashka/sci/blob/master/CHANGELOG.md#0637-2022-12-20
yep
issue(s) welcome of course
I can definitely create one with this context. Will do so in the next two days
I had some examples lying around https://github.com/babashka/sci/issues/1002 Didn't test with loop* can look into that later
loop* and loop is a similar case, we can lump that together in this issue if you mention it there
I added an example for loop* and let* . For some reason case* is not a problem
interesting, looks like denying case* doesn't work:
user=> (sci/eval-string "(macroexpand '(case 1 2 3))" {:deny '[case*]})
(case* 1 2 3)please mention it in the issue as well
oh wait, it does work, I was macroexpanding
user=> (sci/eval-string "(case* 1 2 3))" {:deny '[case*]})
Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:47).
case* is not allowed!in SCI case doesn't expand to case* , it's simply an alias
afk now