joyride 2025-10-30

I’m having troubles fixing an issue with println not printing newlines in the released Joyride extension. The newline prints fine in the development build. I’ve added some debug logging on this branch: https://github.com/BetterThanTomorrow/joyride/blob/253-trailing-newlines-take-2/src/joyride/sci.cljs From the debug logging I note: • There is some default printing somewhere, that also prints to the console ◦ This is probably fine, or at least not causing the problem I am trying to understand • When it works: ◦ The trailing newline is a separate print (which is lacking from the release build) ◦ The default console printer is not printing this newline

Joyride is using a pretty ancient SCI, so maybe this problem goes away if I upgrade. The problem with that is that I have tried a few times and failed getting a working Joyride extension with latest SCI. But maybe I now have the compelling reason to solve that problem… (I don’t remember right now what broke.)

I do remember fixing something like this in SCI so worth a try

Thanks. Then that’ll be my next step and I’ll probably come back with refreshed memory of the hurdle with that. 😃

I have bad news and bad news. 😃 • Upgrading SCI doesn’t fix the println problem • Upgrading SCI breaks the E2E tests where we use Joyride for running tests. Specifically it is tests using the deftest-async macro (nicked from nbb, iirc) that break with Could not find instance method: catch$

can you try master branch?

👀 1

which is on the master branch

the println error might be in the nrepl server

does the code go through nrepl or not?

Ah, “it” was the test macro thing. I can confirm it fixed it!

> this commit fixes it

println issue… Not nrepl. I get it also when just evaluating code on the Joyride SCI repl.

also in a script, not in REPL?

That too. Anything that uses our eval-string function.

can you reproduce it on this SCI playground? it has the same println config as joyride. https://babashka.org/scittle/codemirror.html if so, please copy/paste the repro

oh I see now the screenshot in the initial message with the wrapfn

I. can't remember adding this

The wrapfn? It’s pretty new.

well isn't that the issue then?

The wrapfn never gets called with the trailing newline.

In release builds. In the dev host we get the trailing newline call.

In SCI println is this: https://github.com/babashka/sci/blob/f40ee1a6cf6c53b8271b41c101c3391773d67e76/src/sci/impl/io.cljc#L239 It just called CLJS println. And CLJS println prints the newline separately through *print-fn*

Perhaps shadow or so sets print-newline differently in production mode, don't know

You could try to print it (not in SCI, but in CLJS) on startup or so for debugging (it being *print-newline* )

🙏 1

I was digging around a bit with this yesterday, without making much sense from it.

The initial value of SCI's print newline is inherited from the host: https://github.com/babashka/sci/blob/f40ee1a6cf6c53b8271b41c101c3391773d67e76/src/sci/impl/io.cljc#L63 You could also just set it manually by evaluating:

(alter-var-root #'*print-new-line* (constantly true))
and see if that helps

From a packaged VSIX: > [Extension Host] print-newline false And it is true in the dev extension host.

then that explains it

maybe ask shadow why this is

So the alter-var-root at start of Joyride?

yes, but evaluate it in SCI

👍 1

Yesterday, I was thinking that maybe I should do something like this with *print-newline* yesterday when I found my way there via SCI. But then I thought that maybe it would make print behave funny. But that’s not how this variable works?

This fixes the issue:

(defn eval-string [s]
  (sci/binding [sci/ns @!last-ns
                sci/print-newline true
                sci/print-fn (wrap-print-fn "stdout" @sci/print-fn output/append-eval-out!)
                sci/print-err-fn (wrap-print-fn "stderr" @sci/print-err-fn output/append-eval-err!)]
    (let [code (str s)
          reader (sci/reader code)]
      (loop [res nil]
        (let [form (sci/parse-next (store/get-ctx) reader)]
          (if (= :sci.core/eof form)
            (do
              (vreset! !last-ns @sci/ns)
              (output/append-eval-result! (pr-str res) {:ns (str @sci/ns)})
              res)
            (let [result (try
                           (sci/eval-form (store/get-ctx) form)
                           (catch js/Error e
                             (let [message (or (.-message e) (str e))]
                               (output/append-line-eval-err! message)
                               (throw e))))]
              (recur result))))))))
WDYT?

ah yes. you could also just do:

(sci/alter-var-root sci/print-newline (constantly true))
once at the top (I didn't remember I exposed it) also: don't forget to bug shadow about this because this behavior seems a bit funny

Perhaps shadow evaluates enable console print for you:

(defn enable-console-print!
  "Set *print-fn* to console.log"
  []
  (set! *print-newline* false)
  (set-print-fn!
    (fn []
      (let [xs (js-arguments)]
        (.apply (.-log js/console) js/console (garray/clone xs)))))
  (set-print-err-fn!
    (fn []
      (let [xs (js-arguments)]
        (.apply (.-error js/console) js/console (garray/clone xs)))))
  nil)

but in release it does not?

or SCI is loaded in a different order than when this is evaluated, something like that

if you want SCI to behave as with enable-console-print! just evaluate it before inheriting *print-fn* with alter-var-root

so, put it before this line:

👍 1

Upgrading SCI problems 🧵

✅ 1

The stack trace :

Error: Error: Could not find instance method: catch$
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/joyride/extension.cljs:49:26
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/core.cljc:325:45
    at Object.reject (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl.cljc:113:41)
    at processNextTick (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:288:25)
    at transition (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:360:12)
    at PromiseImpl.reject (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:154:9)
    at Object.complete (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:251:18)
    at Object.complete (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:327:18)
    at resolveTask (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:305:12)
    at processNextTick (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:296:7)
    at transition (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:360:12)
    at PromiseImpl.reject (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:154:9)
    at Object.complete (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:251:18)
    at resolveTask (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:305:12)
    at processNextTick (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:296:7)
    at transition (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:360:12)
    at PromiseImpl.reject (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl/promise.js:154:9)
    at PromiseImpl.promesa$protocols$ICompletable$_reject_BANG_$arity$2 (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/impl.cljc:128:21)
    at Object.promesa$protocols$_reject_BANG_ [as _reject_BANG_] (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/protocols.cljc:50:14)
    at promesa$core$reject_BANG_ (/Users/pez/Projects/joyride/out/js/cljs-runtime/promesa/core.cljc:497:4)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:1372:1
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/evaluator.cljc:99:30
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci.impl.evaluator.js:106:3
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/cljs/core.cljs:5776:36
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/cljs.core.js:20740:3
    at Object.cljs$core$IReduce$_reduce$arity$3 (/Users/pez/Projects/joyride/out/js/cljs-runtime/cljs.core.js:20755:3)
    at cljs$core$reduce.cljs$core$IFn$_invoke$arity$3 (/Users/pez/Projects/joyride/out/js/cljs-runtime/cljs/core.cljs:2619:17)
    at Object.sci$impl$evaluator$eval_try [as eval_try] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/evaluator.cljc:88:9)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:916:7
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:80:21
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:578:17
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:813:27
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at sci$impl$fns$arity_1 (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/fns.cljc:109:18)
    at /Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/analyzer.cljc:1372:1
    at Object.sci$impl$types$eval [as eval] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/types.cljc:86:8)
    at Timeout.sci$impl$fns$arity_0 [as _onTimeout] (/Users/pez/Projects/joyride/out/js/cljs-runtime/sci/impl/fns.cljc:108:18)
    at listOnTimeout (node:internal/timers:588:17)
    at processTimers (node:internal/timers:523:7)

The macro:

(defmacro deftest-async [name opts & body]
  (let [[opts body]
        (if (map? opts)
          [opts body]
          [nil (cons opts body)])]
    `(cljs.test/deftest ~name
       ~@(when-let [pre (:before opts)]
           [pre])
       (cljs.test/async
        ~'done
        (-> (do ~@body)
            (.catch (fn [err#]
                      (cljs.test/is (= 1 0) (str err# (.-stack err#)))))
            (.finally
             (fn []
               ~@(when-let [post (:after opts)]
                   [post])
               (~'done))))))))

If I rewrite the tests so that they use regular deftest and cljs.test/async directly, they do not crash.