sci 2025-07-20

hi! i am trying to write a wrapper function around sci/macro and having trouble. this is my code:

(defn copy-macro [sym] `(do ^:sci/macro (fn [_&form# _&env# & rest
#] (~sym rest#))))
(sci/init {:bindings {'html (copy-macro h/html)})
that gives an error i don't understand:
Syntax error macroexpanding clojure.core/fn at (blossom/parse.clj:22:32).
(blossom/_&form blossom/_&env & clojure.core/rest) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
blossom/_&form - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

is this in clojure or clojurescript btw? in JVM clojure you can copy macros with sci/copy-var

clojure, but i want to compile it to a native executable with GraalVM and i'm not sure if copy-var works there

yes, it works also for that

as long as you use copy-var on the top level, not in a function

everything in SCI is designed with graal native-image in mind

i will probably do that then 🙂 thank you!

oh wait apparently load-file was doing caching hm

... oh it works now

is there a way to know which namespaces are loaded by a (require) function call? my use case is that i loaded hiccup2.core into the SCI interpreter, but it keeps failing because it wants things like hiccup.util that aren't loaded

can you explain how you added hiccup2.core to SCI?

like so:

(defn copy-ns [ns]
    (let [binding (sci/create-ns ns)
          publics (ns-publics ns)]
      (update-vals publics #(sci/copy-var* % binding))))
  (sci/init {:namespaces
              {'hiccup2.core (copy-ns 'hiccup2.core)}})

you may just want to copy the configuration from here, since hiccup also is added to babashka via SCI: https://github.com/babashka/babashka/blob/master/feature-hiccup/babashka/impl/hiccup.clj

well, i would like this to work for other libraries besides hiccup

and it's hard to test those exhaustively because this runtime error only shows up if the guest code actually calls the function that needs the namespace

> but it keeps failing because it wants things like hiccup.util that aren't loaded If a macro expands to a call to hiccup.util you need to make sure that namespace also exists in your SCI config

right. my question is how to get an exhaustive list of namespaces i need, since i don't control all the code that is being run in the SCI interpreter. a friend pointed me to loaded-libs and i had the thought to call that before and after (require) in the host and see how they differ

how can you know an exhaustive list of things you need if you don't know what is getting run

well, i know which namespaces i want to load. if someone tries to use hiccup and it gives an error because i didn't add it to the interpreter, that's ok, they will see a reasonable error message. but if i add hiccup2.core and not hiccup.util a) the errors are much worse and b) that's always wrong, i know statically that i wanted to load hiccup and all things it depends on

it depends right. if the public API you want to expose is just hiccup(2).core, then you don't need to add hiccup.util I guess? the fact that it's loaded in the host is kind of irrelevant to that

no, this is true even if hiccup.util is not public api

let me show you the error i just got

Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:46).
Could not resolve symbol: hiccup.util/raw-string

this is in a call to hiccup/html in the interpreter

hiccup/html resolved ok, but the functions it expands to aren't present

is hiccup/html hiccup2/html or hiccup1/html

hiccup2.core/html

yes, you added a macro which expands to a call in hiccup.util, so you need to add that namespace. but this isn't always the case in general. with macros, yes

it's just a matter of testing, I can't give you a general recipe for this. most exposed things in babashka are tested exhaustively via the unit tests of the libraries

i will try the loaded-libs thing for now and attack everything with tests to be safe

note that there is also a complexity about dynamic vars. sometimes you need to wrap an existing function and bind the host vars to the values of the SCI dynamic vars, before calling the host function: https://github.com/babashka/babashka/blob/bd20a5f973fcf1ed33131eb1079927be9de0d1d8/feature-hiccup/babashka/impl/hiccup.clj#L70-L76

and this is required for all functions that use dynamic vars?

if you expose the dynamic var to the user, yes

i am confused. let me restate the question. that render-html function uses a util/*html-mode* binding. what happens if i do a naive thing where i use {:bindings {'render-html h/render-html}} and then call it inside the interpreter? does it error that *html-mode* isn't bound in the interpreter? does it allow assigning to the guest var, but still use the host var regardless?

the issue is that the host function isn't aware of the SCI vars

right. i'm asking whether "expose the dynamic var to the user" means directly, though a public api, or indirectly through some other function that happens to use that dynamic var

i don't see *html-mode* in hiccup's public api docs, but babashka is still binding it, so i suspect this is true even if it's used indirectly

the html-mode dyn var is bound in the html macro

ok, i think i see

and in the snippet you linked earlier, you are binding the current value of *html-mode*, that's what @ does?

a SCI var is a defef-able thing, so you get the current value of it

in the host environment

it would be easier if SCI allowed working with host vars directly, but one of its features is sandboxing which unfortunately makes this a bit more work

👍 1

i'm still trying to understand the semantics. so html-mode in the guest is initialized to the current value in the host. presumably it can be modified in the guest. and then when the guest exits, all the changes are discarded, the host value stays the same

the host value always stays the same, it's never affected by the guest, except in this wrapper function, where I bind the host vars to the guest's current value before calling the host function

ohhh right because render-html is a host function and sees the host's env

this is very helpful, thank you

👍 1