bootstrap: SCI core deps loading with 284/304 forms passing

reader: #?@ empty splice fix (nil→@[]), #? nil→:jolt/skip, map reader handles #_/#?@ in K/V
evaluator: unwrap-meta-name helper, deftype interns ->Name too, dot-suffix fix
core: comment/prefer-method stubs, *unchecked-math*/*clojure-version* dynamic vars
This commit is contained in:
Yogthos 2026-06-01 23:24:13 -04:00
parent cdcf569506
commit b20536d1e3
31 changed files with 1348 additions and 65 deletions

View file

@ -8,6 +8,7 @@
# ============================================================
(defn core-nil? [x] (nil? x))
(defn core-not [x] (if x false true))
(defn core-some? [x] (not (nil? x)))
(defn core-string? [x] (string? x))
(defn core-number? [x] (number? x))
@ -592,6 +593,110 @@
# Initialization — intern everything into a context's namespace
# ============================================================
(defn core-when
"Macro: (when test & body) -> (if test (do body...))"
[test & body]
(def arr (array ;body))
(array/insert arr 0 {:jolt/type :symbol :ns nil :name "do"})
@[{:jolt/type :symbol :ns nil :name "if"}
test
arr])
(defn core-defn
"Macro: (defn name [args] body) -> (def name (fn* [args] body))"
[fn-name args-form & body]
(def fn-form @[])
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
(array/push fn-form args-form)
(each b body (array/push fn-form b))
@[{:jolt/type :symbol :ns nil :name "def"}
fn-name
fn-form])
# defn- stub — expands to defn
(defn core-defn- [& args] @[{:jolt/type :symbol :ns nil :name "do"}])
# Hierarchy stubs for sci bootstrap
(def core-derive (fn [& args] nil))
(def core-isa? (fn [& args] false))
(def core-ancestors (fn [& args] @[]))
(def core-descendants (fn [& args] @[]))
# Java interop stubs
(def core-Object (fn [] (struct ;[:jolt/type :jolt/java-object])))
# Volatile stubs (minimal — use table as volatile box)
(defn core-volatile! [v] @{:val v})
(defn core-vswap! [vol f & args]
(def new-val (apply f (vol :val) args))
(put vol :val new-val)
new-val)
(defn core-vreset! [vol val] (put vol :val val) val)
# Proxy stub — returns nil form (macro, args not evaluated)
(defn core-proxy [& args] nil)
# Thread stubs
(def core-Thread (fn [& args] (struct ;[:jolt/type :jolt/thread])))
(def core-ThreadLocal (fn [& args] (struct ;[:jolt/type :jolt/thread-local])))
(def core-IllegalStateException (fn [& args] (struct ;[:jolt/type :jolt/exception])))
# definterface stub — JVM-only, emits def form
(defn core-definterface [name-sym & body]
@[{:jolt/type :symbol :ns nil :name "def"}
name-sym
@{}])
# comment macro — ignores body, returns nil
(defn core-comment [& body]
nil)
# prefer-method stub — multimethod preference ordering
(defn core-prefer-method [multifn dispatch-val & dispatch-vals]
nil)
# declare macro — accepts symbols, does nothing (forward declaration)
(defn core-declare [& syms]
@[{:jolt/type :symbol :ns nil :name "do"}])
(defn core-fn
"Macro: (fn [args] body) → (fn* [args] body)"
[& args]
(def result @[])
(array/push result {:jolt/type :symbol :ns nil :name "fn*"})
(each a args (array/push result a))
result)
# Protocol stubs — defined in sci.impl.protocols, needed in clojure.core
# defprotocol must be a macro to avoid evaluating its args
(defn core-defprotocol [protocol-name & sigs]
# Emit (do (def protocol-name {}) (def method1 fn) (def method2 fn) ...)
(def result @[])
(array/push result {:jolt/type :symbol :ns nil :name "do"})
# First (def protocol-name {})
(def d @[])
(array/push d {:jolt/type :symbol :ns nil :name "def"})
(array/push d protocol-name)
(array/push d @{})
(array/push result d)
# Then (def method-name (fn [& args] nil)) for each sig
(each sig sigs
(def method-sym (first sig))
(def d @[])
(array/push d {:jolt/type :symbol :ns nil :name "def"})
(array/push d method-sym)
(array/push d (fn [& args] nil))
(array/push result d))
result)
(def core-extend-type (fn [& args] nil))
(defn core-extend-protocol [& args] @[{:jolt/type :symbol :ns nil :name "do"}])
(def core-extend (fn [& args] nil))
(def core-reify (fn [& args] nil))
(def core-satisfies? (fn [& args] nil))
(def core-extends? (fn [& args] false))
(def core-implements? (fn [& args] false))
(def core-type->str (fn [& args] ""))
(def- core-bindings
"Map of symbol name → function for all core functions."
@{"nil?" core-nil?
@ -691,7 +796,45 @@
"atom?" core-atom?
"deref" core-deref
"reset!" core-reset!
"swap!" core-swap!})
"swap!" core-swap!
"not" core-not
"when" core-when
"defn" core-defn
"defn-" core-defn-
"derive" core-derive
"isa?" core-isa?
"ancestors" core-ancestors
"descendants" core-descendants
"Object" core-Object
"declare" core-declare
"fn" core-fn
"defprotocol" core-defprotocol
"extend-type" core-extend-type
"extend-protocol" core-extend-protocol
"extend" core-extend
"reify" core-reify
"satisfies?" core-satisfies?
"extends?" core-extends?
"implements?" core-implements?
"type->str" core-type->str
"volatile!" core-volatile!
"vswap!" core-vswap!
"vreset!" core-vreset!
"proxy" core-proxy
"Thread" core-Thread
"ThreadLocal" core-ThreadLocal
"IllegalStateException" core-IllegalStateException
"definterface" core-definterface
"comment" core-comment
"prefer-method" core-prefer-method
# Dynamic vars — stubs for SCI bootstrap
"*unchecked-math*" false
"*clojure-version*" {:major 1 :minor 11 :incremental 0 :qualifier nil}})
(defn core-macro-names
"Set of core binding names that are macros."
[]
@{"when" true "defn" true "defn-" true "declare" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "fn" true "proxy" true "definterface" true "comment" true})
(def init-core!
(fn [& args]
@ -699,11 +842,15 @@
1 (let [ctx (args 0)
ns (ctx-find-ns ctx "clojure.core")]
(loop [[name fn] :pairs core-bindings]
(ns-intern ns name fn))
(def v (ns-intern ns name fn))
(when (get (core-macro-names) name)
(put v :macro true)))
ns)
2 (let [ctx (args 0) ns-name (args 1)
ns (ctx-find-ns ctx ns-name)]
(loop [[name fn] :pairs core-bindings]
(ns-intern ns name fn))
(def v (ns-intern ns name fn))
(when (get (core-macro-names) name)
(put v :macro true)))
ns)
(error "Wrong number of args passed to: init-core!"))))

View file

@ -11,8 +11,11 @@
[name]
(or (= name "quote") (= name "syntax-quote") (= name "unquote")
(= name "unquote-splicing") (= name "do") (= name "if")
(= name "def") (= name "fn*") (= name "let*") (= name "loop*")
(= name "recur")))
(= name "def") (= name "defmacro") (= name "fn*") (= name "let*") (= name "loop*")
(= name "recur") (= name "throw") (= name "try")
(= name "set!") (= name "var") (= name "locking")
(= name "instance?") (= name "defmulti") (= name "defmethod")
(= name "deftype") (= name "new") (= name ".")))
(var eval-form nil)
@ -51,13 +54,18 @@
(array/push kvs (syntax-quote* ctx bindings (get form k)))) (struct ;kvs))
form))
(defn- resolve-var
(defn resolve-var
[ctx bindings sym-s]
(let [name (sym-s :name) ns (sym-s :ns)]
(if (not (nil? ns))
(let [target-ns (ctx-find-ns ctx ns)] (ns-find target-ns name))
(if (get bindings name) nil
(let [current-ns (ctx-current-ns ctx) ns (ctx-find-ns ctx current-ns)] (ns-find ns name))))))
(let [current-ns (ctx-current-ns ctx)
ns (ctx-find-ns ctx current-ns)
v (ns-find ns name)]
(if v v
(let [core-ns (ctx-find-ns ctx "clojure.core")]
(ns-find core-ns name))))))))
(defn- sym-name-str
[sym-s]
@ -103,20 +111,94 @@
core-v (ns-find core-ns name)]
(if core-v
(var-get core-v)
# Fall back to Janet's global environment
(let [root-env (fiber/getenv (fiber/current))
entry (in root-env (symbol name))]
(if (not (nil? entry))
(if (table? entry) (entry :value) entry)
(error (string "Unable to resolve symbol: " name)))))))))))))
# Try class-name resolution: Foo.Bar.Baz -> ns "Foo.Bar", name "Baz"
(let [dot-idx (string/find "." name)]
(if dot-idx
(let [last-dot (do
(var idx dot-idx)
(var next-dot (string/find "." name (+ idx 1)))
(while (not (nil? next-dot))
(set idx next-dot)
(set next-dot (string/find "." name (+ idx 1))))
idx)
class-ns (string/slice name 0 last-dot)
class-name (string/slice name (+ last-dot 1))]
(let [target-ns (ctx-find-ns ctx class-ns) tv (ns-find target-ns class-name)]
(if tv (var-get tv) tv)))
# Fall back to Janet's global environment
(let [root-env (fiber/getenv (fiber/current))
entry (in root-env (symbol name))]
(if (not (nil? entry))
(if (table? entry) (entry :value) entry)
(error (string "Unable to resolve symbol: " name)))))))))))))))
(defn- parse-arg-names
"Parse a parameter vector, handling & rest args.
Returns {:fixed [names...] :rest name-or-nil :all [names...]}"
[args-form]
(var fixed @[])
(var rest-name nil)
(var i 0)
(while (< i (length args-form))
(let [a (in args-form i)]
(if (and (struct? a) (= :symbol (a :jolt/type)) (= "&" (a :name)))
(do
(+= i 1)
(if (< i (length args-form))
(do
(set rest-name ((in args-form i) :name))
(+= i 1))
(error "& without argument in parameter list")))
(do
(if (and (struct? a) (= :symbol (a :jolt/type)))
(array/push fixed (a :name))
# destructuring form: recurse into it
(when (indexed? a)
(var di 0)
(while (< di (length a))
(def inner (in a di))
(if (and (struct? inner) (= :symbol (inner :jolt/type)) (= "&" (inner :name)))
(do
(+= di 1)
(if (< di (length a))
(do
(set rest-name ((in a di) :name))
(+= di 1))
(error "& without argument in parameter list")))
(do
(if (and (struct? inner) (= :symbol (inner :jolt/type)))
(array/push fixed (inner :name))
# nested destructuring - extract names
(when (indexed? inner)
(each sym inner
(when (and (struct? sym) (= :symbol (sym :jolt/type)))
(array/push fixed (sym :name))))))
(+= di 1))))))
(+= i 1)))))
(var all @[])
(each n fixed (array/push all n))
(if rest-name (array/push all rest-name))
{:fixed (tuple/slice (tuple ;fixed)) :rest rest-name :all (tuple/slice (tuple ;all))})
# Dispatch a special form by its string name.
(defn- unwrap-meta-name
"Recursively unwrap (with-meta sym meta) forms to extract the underlying symbol.
Returns the symbol struct, or the original form if it's not a with-meta wrapper."
[form]
(if (and (array? form) (> (length form) 0)
(struct? (in form 0))
(= :symbol ((in form 0) :jolt/type))
(= "with-meta" ((in form 0) :name)))
(unwrap-meta-name (in form 1))
form))
# Dispatch a special form by its string name. Each branch is a standalone
# expression that returns the value directly — no cond, no nested if chains.
# We use a local function per form and call the matching one.
(defn- eval-list
[ctx bindings form]
(def first-form (first form))
(def name (first-form :name))
# Safe name extraction: non-symbol heads (e.g. keywords) fall through to default
(def name (if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
(first-form :name)
nil))
(match name
"quote" (in form 1)
"syntax-quote" (syntax-quote* ctx bindings (in form 1))
@ -134,13 +216,44 @@
(if (and (not (nil? test-val)) (not (= false test-val)))
(eval-form ctx bindings (in form 2))
(if (> (length form) 3) (eval-form ctx bindings (in form 3)) nil)))
"def" (let [name-sym (in form 1)
"def" (let [raw-name (in form 1)
name-sym (unwrap-meta-name raw-name)
val (eval-form ctx bindings (in form 2))
ns-name (ctx-current-ns ctx)
ns (ctx-find-ns ctx ns-name)]
(ns-intern ns (name-sym :name) val)
(var-get (ns-intern ns (name-sym :name))))
"ns" (let [ns-name (sym-name-str (in form 1))
"defmacro" (let [name-sym (in form 1)
rest-form (tuple/slice form 2)
# optional docstring
has-doc? (and (> (length rest-form) 0) (string? (first rest-form)))
args-form (if has-doc? (in rest-form 1) (first rest-form))
body (tuple/slice rest-form (if has-doc? 2 1))
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)]
(def macro-fn (fn [& macro-args]
(var new-bindings @{})
(table/setproto new-bindings bindings)
(put new-bindings "&env" @{}) # implicit &env for macro bodies (table — nil-safe)
(var i 0)
(each a fixed-names
(put new-bindings a (macro-args i))
(++ i))
(when rest-name
(put new-bindings rest-name (tuple/slice macro-args i)))
(var result nil)
(each bf body
(set result (eval-form ctx new-bindings bf)))
result))
(let [ns-name (ctx-current-ns ctx)
ns (ctx-find-ns ctx ns-name)]
(def v (ns-intern ns (name-sym :name) macro-fn))
(put v :macro true)
(var-get v)))
"ns" (let [raw-name (in form 1)
name-sym (unwrap-meta-name raw-name)
ns-name (sym-name-str name-sym)
clauses (tuple/slice form 2)]
(ctx-set-current-ns ctx ns-name)
(ctx-find-ns ctx ns-name)
@ -153,9 +266,9 @@
(let [specs (tuple/slice clause 1)
slen (length specs)]
(var j 0)
(while (< j slen)
(let [s (in specs j)]
(eval-require ctx s))
(while (< j slen)
(let [s (in specs j)]
(when s (eval-require ctx s)))
(++ j))
(set i (+ i 1)))
(do (set result clause) (++ i))))))
@ -170,15 +283,19 @@
nil)
"fn*" (let [args-form (in form 1)
body (tuple/slice form 2)
arg-names (map |($ :name) args-form)]
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)]
(var self nil)
(set self (fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name arg-names
(each arg-name fixed-names
(put fn-bindings arg-name (fn-args i))
(++ i))
(when rest-name
(put fn-bindings rest-name (tuple/slice fn-args i)))
(put fn-bindings :jolt/loop-fn self)
(var result nil)
(each body-form body
@ -228,19 +345,184 @@
(error "recur used outside of loop* or fn*")
(let [args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
(apply loop-fn args))))
"throw" (let [val (eval-form ctx bindings (in form 1))]
(error {:jolt/type :jolt/exception :value val}))
"try" (let [body-form (in form 1)
clauses (tuple/slice form 2)
n (length clauses)]
(var catch-sym nil)
(var catch-body nil)
(var finally-body nil)
(var i 0)
(while (< i n)
(let [clause (in clauses i)]
(if (and (array? clause) (> (length clause) 0))
(let [head (first clause)]
(if (and (struct? head) (= :symbol (head :jolt/type)))
(match (head :name)
"catch" (do
(set catch-sym (in clause 2))
(set catch-body (tuple/slice clause 3)))
"finally" (set finally-body (tuple/slice clause 1)))))))
(++ i))
(defn run-finally [f]
(when f
(each fb f (eval-form ctx bindings fb))))
(if catch-sym
(try
(eval-form ctx bindings body-form)
([err]
(var new-bindings @{})
(table/setproto new-bindings bindings)
(put new-bindings (catch-sym :name) err)
(var result nil)
(each cb catch-body
(set result (eval-form ctx new-bindings cb)))
(run-finally finally-body)
result))
(if finally-body
(try
(eval-form ctx bindings body-form)
([err]
(run-finally finally-body)
(error err)))
(eval-form ctx bindings body-form))))
"set!" (let [target-sym (in form 1)
val (eval-form ctx bindings (in form 2))
v (resolve-var ctx bindings target-sym)]
(if v
(do (var-set v val) val)
# Auto-create var if it doesn't exist (e.g., *warn-on-reflection*)
(let [ns-name (ctx-current-ns ctx)
ns (ctx-find-ns ctx ns-name)]
(def new-v (ns-intern ns (target-sym :name) val))
val)))
"var" (let [target-sym (in form 1)
v (resolve-var ctx bindings target-sym)]
(if v v (error (string "Unable to resolve var: " (sym-name-str target-sym) " in var"))))
"locking" (eval-form ctx bindings (in form 2))
"instance?" (let [type-sym (in form 1)
val (eval-form ctx bindings (in form 2))]
(match (type-sym :name)
"Number" (number? val)
"String" (string? val)
"Boolean" (or (= true val) (= false val))
"Keyword" (keyword? val)
"Object" true
false))
"defmulti" (let [name-sym (in form 1)
dispatch-fn (eval-form ctx bindings (in form 2))
ns (ctx-find-ns ctx (ctx-current-ns ctx))
methods @{}
mm-fn (fn [& args]
(let [dv (apply dispatch-fn args)
method (get methods dv)]
(if method
(apply method args)
(error (string "No method in multimethod "
(name-sym :name) " for dispatch value: "
dv)))))]
(def v (ns-intern ns (name-sym :name) mm-fn))
(put v :jolt/methods methods)
(var-get v))
"defmethod" (let [mm-sym (in form 1)
dispatch-val (eval-form ctx bindings (in form 2))
arg-vec (in form 3)
body (tuple/slice form 4)
# Extract names, handling metadata-wrapped symbols
extract-name (fn [arg]
(let [arg (unwrap-meta-name arg)]
(arg :name)))
arg-names (tuple/slice (map extract-name arg-vec))
mm-var (resolve-var ctx bindings mm-sym)
# Auto-create multimethod if it doesn't exist
mm-var (if mm-var mm-var
(let [ns (ctx-find-ns ctx (ctx-current-ns ctx))
dummy-fn (fn [& args] nil)]
(def v (ns-intern ns (mm-sym :name) dummy-fn))
(put v :jolt/methods @{})
v))
methods (get mm-var :jolt/methods)
impl (fn [& args]
(var new-bindings @{})
(table/setproto new-bindings bindings)
(var i 0)
(each a arg-names
(put new-bindings a (args i))
(++ i))
(var result nil)
(each bf body
(set result (eval-form ctx new-bindings bf)))
result)]
(put methods dispatch-val impl)
mm-var)
"deftype" (let [raw-name (in form 1)
type-name (unwrap-meta-name raw-name)
fields-vec (in form 2)
field-names (map
(fn [f]
# Handle ^:meta and ^Type annotations — extract the actual name
(let [f (unwrap-meta-name f)]
(if (and (struct? f) (= :symbol (f :jolt/type)))
(keyword (f :name))
(error (string "Unsupported deftype field: " (string f))))))
fields-vec)
ns-name (ctx-current-ns ctx)
type-tag (string ns-name "." (type-name :name))]
(defn ctor [& args]
(var inst @{:jolt/deftype type-tag})
(var i 0)
(each fn field-names
(put inst fn (args i))
(++ i))
inst)
(let [ns (ctx-find-ns ctx ns-name)
ctor-name (type-name :name)
arrow-name (string "->" ctor-name)]
(ns-intern ns ctor-name ctor)
(ns-intern ns arrow-name ctor)
(var-get (ns-intern ns ctor-name))))
"new" (let [type-sym (in form 1)
args (map |(eval-form ctx bindings $) (tuple/slice form 2))
ctor (eval-form ctx bindings type-sym)]
(apply ctor args))
"." (let [target (eval-form ctx bindings (in form 1))
member-sym (in form 2)
member-name (member-sym :name)]
(if (> (length form) 3)
# method call: (. obj method args...)
(let [args (map |(eval-form ctx bindings $) (tuple/slice form 3))]
(if (target :jolt/deftype)
(let [method-key (keyword member-name)]
(apply (get target method-key) target ;args))
(error (string "Cannot call method " member-name " on non-deftype"))))
# field access: (. obj field)
(get target (keyword member-name))))
# default: function application — check for macros
(if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
(let [v (resolve-var ctx bindings first-form)]
(if (and v (var-macro? v))
(let [macro-fn (var-get v)
args (tuple/slice form 1)]
(eval-form ctx bindings (apply macro-fn args)))
(let [f (eval-form ctx bindings first-form)
(let [sym-name (first-form :name)]
# Handle ClassName. constructor syntax
(if (and (> (length sym-name) 0) (= (sym-name (- (length sym-name) 1)) 46))
(let [type-name (string/slice sym-name 0 (- (length sym-name) 1))
type-sym {:jolt/type :symbol :ns (first-form :ns) :name type-name}
ctor (eval-form ctx bindings type-sym)
args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
(apply f args))))
(apply ctor args))
(let [v (resolve-var ctx bindings first-form)]
(if (and v (var-macro? v))
(let [macro-fn (var-get v)
args (tuple/slice form 1)]
(eval-form ctx bindings (apply macro-fn args)))
(let [f (eval-form ctx bindings first-form)
args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
(apply f args))))))
(let [f (eval-form ctx bindings first-form)
args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
(apply f args)))))
(if (function? f)
(apply f args)
(if (keyword? f)
(get (first args) f)
(error (string "Cannot call " (type f) " as a function"))))))))
(set eval-form (fn [ctx bindings form]
(cond

View file

@ -19,16 +19,17 @@
(printf "#'%s/%s" (ctx-current-ns ctx) ((var-name v) :name))
(print v))))
(print "Jolt — Clojure on Janet")
(print "Type (exit) to quit.\n")
(defn main [&]
(print "Jolt — Clojure on Janet")
(print "Type (exit) to quit.\n")
(var running true)
(while running
(let [line (read-line (string (ctx-current-ns ctx) "=> "))]
(if (nil? line) (set running false)
(if (= line "(exit)") (set running false)
(if (not (= "" line))
(try
(print-value (eval-string ctx line))
([err]
(eprint "Error: " err))))))))
(var running true)
(while running
(let [line (read-line (string (ctx-current-ns ctx) "=> "))]
(if (nil? line) (set running false)
(if (= line "(exit)") (set running false)
(if (not (= "" line))
(try
(print-value (eval-string ctx line))
([err]
(eprint "Error: " err)))))))))

View file

@ -202,7 +202,13 @@
(if (= (s pos) 41) # )
[items (+ pos 1)]
(let [[form new-pos] (read-form s pos)]
(read-list-items s new-pos (array/push items form))))))
# skip #_ discarded forms
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
(read-list-items s new-pos items)
# splice #?@ items into the list
(if (and (struct? form) (= :jolt/splice (form :jolt/type)))
(read-list-items s new-pos (array/concat items (form :items)))
(read-list-items s new-pos (array/push items form))))))))
(read-list-items s (+ pos 1) @[]))
(defn read-vector [s pos]
@ -214,7 +220,11 @@
(if (= (s pos) 93) # ]
[(tuple/slice (tuple ;items)) (+ pos 1)]
(let [[form new-pos] (read-form s pos)]
(read-vec-items s new-pos (array/push items form))))))
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
(read-vec-items s new-pos items)
(if (and (struct? form) (= :jolt/splice (form :jolt/type)))
(read-vec-items s new-pos (array/concat items (form :items)))
(read-vec-items s new-pos (array/push items form))))))))
(read-vec-items s (+ pos 1) @[]))
(defn read-map [s pos]
@ -225,10 +235,21 @@
(error "Unterminated map"))
(if (= (s pos) 125) # }
[(struct ;kvs) (+ pos 1)]
(let [[key new-pos] (read-form s pos)
pos (skip-whitespace s new-pos)
[val new-pos2] (read-form s pos)]
(read-kvs s new-pos2 (-> kvs (array/push key) (array/push val)))))))
(let [[key new-pos] (read-form s pos)]
(if (and (struct? key) (= :jolt/skip (key :jolt/type)))
(read-kvs s new-pos kvs)
(if (and (struct? key) (= :jolt/splice (key :jolt/type)))
(read-kvs s new-pos (array/concat kvs (key :items)))
(let [pos (skip-whitespace s new-pos)
[val new-pos2] (read-form s pos)]
(if (and (struct? val) (= :jolt/skip (val :jolt/type)))
(read-kvs s new-pos2 kvs)
(if (and (struct? val) (= :jolt/splice (val :jolt/type)))
# Only push key if splice contributes items
(if (> (length (val :items)) 0)
(do (array/push kvs key) (read-kvs s new-pos2 (array/concat kvs (val :items))))
(read-kvs s new-pos2 kvs))
(read-kvs s new-pos2 (-> kvs (array/push key) (array/push val))))))))))))
(read-kvs s (+ pos 1) @[]))
(defn read-set [s pos]
@ -240,7 +261,11 @@
(if (= (s pos) 125) # }
[{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} (+ pos 1)]
(let [[form new-pos] (read-form s pos)]
(read-set-items s new-pos (array/push items form))))))
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
(read-set-items s new-pos items)
(if (and (struct? form) (= :jolt/splice (form :jolt/type)))
(read-set-items s new-pos (array/concat items (form :items)))
(read-set-items s new-pos (array/push items form))))))))
(read-set-items s (+ pos 2) @[]))
(defn read-char-name-end [s pos]
@ -260,15 +285,53 @@
[(array/insert form 0 (sym "fn*")) new-pos]))
(defn read-reader-conditional [s pos]
# pos is at #, next char is ?
(let [[form new-pos] (read-form s (+ pos 2))]
[{:jolt/type :jolt/reader-conditional :clauses form} new-pos]))
# pos is at #, next char is ? or ?@
(def splice? (and (< (+ pos 2) (length s)) (= (s (+ pos 2)) 64))) # @ = 64
(def form-start (if splice? (+ pos 3) (+ pos 2)))
(let [[form new-pos] (read-form s form-start)]
(if (array? form)
(do
(var result nil)
(var i 0)
(while (< i (length form))
(if (= (in form i) :clj)
(do
(set result (in form (+ i 1)))
(set i (length form)))
(++ i)))
(if splice?
# #?@ splicing: resolve :clj branch, wrap for splice
(let [items (if (nil? result)
@[]
(if (or (array? result) (tuple? result))
result
@[result]))]
[{:jolt/type :jolt/splice :items items} new-pos])
# #? non-splicing: skip nil results (from :cljs branches on CLJ)
(if (nil? result)
[{:jolt/type :jolt/skip} new-pos]
[result new-pos])))
[{:jolt/type :jolt/reader-conditional :clauses form} new-pos])))
(defn read-var-quote [s pos]
# pos is at #, next char is '
(let [[form new-pos] (read-form s (+ pos 2))]
[(array (sym "var") form) new-pos]))
(defn read-regex [s pos]
# pos is at #, next char is "
# Read until unescaped closing "
(var i (+ pos 2))
(var done nil)
(while (and (< i (length s)) (not done))
(if (= (s i) 92) # backslash — skip next char
(+= i 2)
(if (= (s i) 34) # closing quote
(set done [(struct ;[:jolt/type :jolt/tagged :tag :regex :form (string/slice s (+ pos 2) i)])
(+ i 1)])
(++ i))))
(if done done (error "Unterminated regex literal")))
(defn read-dispatch [s pos]
# pos is at #
(if (>= (+ pos 1) (length s))
@ -279,8 +342,9 @@
(= c 40) (read-anon-fn s pos) # #(
(= c 63) (read-reader-conditional s pos) # #?
(= c 95) (let [[_ new-pos] (read-form s (+ pos 2))] # #_
(read-form s new-pos))
[{:jolt/type :jolt/skip} new-pos])
(= c 39) (read-var-quote s pos) # #'
(= c 34) (read-regex s pos) # #"regex
# unknown dispatch — tagged literal
(let [end (read-symbol-name s pos pos)
tag (string/slice s pos end)
@ -311,7 +375,7 @@
# comment
(= c 59)
(let [line-end (read-until-newline s pos)]
(read-form s line-end))
[{:jolt/type :jolt/skip} line-end])
# dispatch
(= c 35)
@ -325,6 +389,16 @@
(= c 40)
(read-list s pos)
# unmatched closing delimiters
(= c 41)
(error (string "Unmatched closing paren at " pos))
(= c 93)
(error (string "Unmatched closing bracket at " pos))
(= c 125)
(error (string "Unmatched closing brace at " pos))
# vector
(= c 91)
(read-vector s pos)
@ -373,11 +447,17 @@
(defn parse-string
"Parse a Clojure source string and return the first form."
[s]
(let [[form _] (read-form s 0)]
form))
(let [[form pos] (read-form s 0)]
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
(parse-string (string/slice s pos))
form)))
(defn parse-next
"Parse the first form from a string. Returns [form remaining-string]."
[s]
(let [[form pos] (read-form s 0)]
[form (string/slice s pos)]))
(defn- parse-next-loop [pos]
(let [[form new-pos] (read-form s pos)]
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
(parse-next-loop new-pos)
[form (string/slice s new-pos)])))
(parse-next-loop 0))