Fix arg evaluation order + host interop gaps so reitit/selmer/honeysql run

Shaking the ring-app example's real library stack out against jolt surfaced a
batch of divergences from JVM Clojure, the biggest being evaluation order.

backend_scheme: call and recur arguments were emitted as bare Scheme operands,
so Chez's unspecified (right-to-left) order won out. Clojure evaluates left to
right, which selmer's reader loop relies on: (recur (add-node ... rdr) (read-char
rdr)) consumed a char early and dropped the first chars of every {{tag}}. Bind
operands to fresh temps in a let* (only when two or more can have side effects,
so hot calls over locals/consts stay un-wrapped). emit-ordered already did this
for collection literals; generalize it.

host-contract: syntax-quote now resolves the alias part of a qualified symbol
(impl/foo -> clojure.tools.logging.impl/foo) instead of leaving it bare, which
limped along via short-name matching until two loaded namespaces (reitit.impl,
clojure.tools.logging.impl) shared the short name and it broke.

collections: key-hash masks with bitwise-and, not fxand — jolt-hash is set!-
decorated per type (records return their own hash) and Chez's equal-hash can be a
bignum, so a key's hash isn't always a fixnum.

seq: even?/odd? handle bignums (JVM accepts any integer; the fxand crashed).

records: Keyword/Symbol .sym/.getName/.toString (honeysql's :clj branch reads
(.sym k)); Throwable .getMessage/.toString over a Chez condition.

host-static: __register-class-ctor!/__register-class-statics! so a host shim
(reitit.trie-jolt) can mirror a Java class.

natives-str: String.intern returns the string.

sqlite: jdbc.core fetch/fetch-one kebab-case column keys (the jolt-lang/db
convention; created_at -> :created-at).

io: a relative io/file path resolves against JOLT_PWD (the user's cwd), not the
repo root the launcher cd'd to — matches JVM cwd semantics, so config.edn loads.

cli: render an uncaught jolt throw (ex-info message + ex-data, or a condition)
instead of Chez's opaque "non-condition value" dump.
This commit is contained in:
Yogthos 2026-06-22 05:26:09 -04:00
parent 10e3a00777
commit 6ab65a30e3
12 changed files with 744 additions and 573 deletions

View file

@ -125,10 +125,29 @@
(def-var! "jdbc.core" "connection" (lambda (url) (sql-open (jdbc-strip-scheme url))))
(def-var! "jdbc.core" "execute!"
(lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) (sql-query c sql ps) jolt-nil)))
;; jdbc.core (jolt-lang/db) returns kebab-cased column keys, so an `id_seq`
;; column reads as :id-seq — the Clojure jdbc convention the templates/queries
;; expect. (The lower jolt.sqlite/query keeps the raw column names.)
(define (kebab-keyword k)
(if (keyword-t? k)
(let ((nm (keyword-t-name k)))
(if (memv #\_ (string->list nm))
(keyword (keyword-t-ns k) (list->string (map (lambda (c) (if (char=? c #\_) #\- c)) (string->list nm))))
k))
k))
(define (kebab-row row)
(let loop ((s (jolt-seq row)) (m (jolt-hash-map)))
(if (jolt-nil? s) m
(let ((e (jolt-first s)))
(loop (jolt-seq (jolt-rest s)) (jolt-assoc m (kebab-keyword (jolt-nth e 0)) (jolt-nth e 1)))))))
(define (kebab-rows rows)
(let loop ((i 0) (out (jolt-vector)))
(if (>= i (pvec-count rows)) out
(loop (+ i 1) (jolt-conj out (kebab-row (pvec-nth-d rows i jolt-nil)))))))
(def-var! "jdbc.core" "fetch"
(lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) (sql-query c sql ps))))
(lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) (kebab-rows (sql-query c sql ps)))))
(def-var! "jdbc.core" "fetch-one"
(lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec)))
(let ((rows (sql-query c sql ps)))
(if (> (pvec-count rows) 0) (pvec-nth-d rows 0 jolt-nil) jolt-nil)))))
(if (> (pvec-count rows) 0) (kebab-row (pvec-nth-d rows 0 jolt-nil)) jolt-nil)))))
(def-var! "jdbc.core" "last-insert-id" (lambda (c) (sqlite3_last_insert_rowid (sql-conn-db c))))