fix: all 317 SCI forms load, zero failures

- Reorder load sequence: types→unrestrict→vars→lang→utils→namespaces→core
- Add if-let/when-let/if-some/when-some/let macros and register in core-macro-names
- Add stubs: resolve, update, copy-core-var, copy-var, macrofy, new-var, avoid-method-too-large
- Add dynamic vars: *1, *2, *3, *e, *assert with nil-sentinel
- Fix core-update: use put instead of Janet's update (struct-incompatible)
- Change *clojure-version* from struct to table (mutable for update)
- Fix core-avoid-method-too-large: return @{} not nil
- Fix init-core! nil-sentinel unwrapping
- Fix let* destructuring for {:keys [...]} and sequential patterns
- Fix fn*/defmacro: capture defining ns for symbol resolution in closures
This commit is contained in:
Yogthos 2026-06-02 00:07:40 -04:00
parent 93f0e42716
commit b948c4fdbb
9 changed files with 377 additions and 108 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,13 @@
`defmacro` supports optional docstring: `(defmacro name [args] body)` or `(defmacro name \"doc\" [args] body)`. Implementation: slice rest-form from form index 2, check if first is string, adjust args-form and body start accordingly. Implicit `&env` binding injected as `(put new-bindings "&env" @{'ns nil})` — must use `@{}` not `(struct …)` because nil values get dropped from structs. `fn*` uses same `parse-arg-names` helper for `& rest` args.
§
Jolt bootstrap: `core-bindings` (def- map) maps symbol strings → Janet functions. `init-core!` interns them into clojure.core ns. Macros (when, defn, declare) are registered in `core-macro-names` which returns `@{"when" true "defn" true "declare" true}`. `init-core!` checks this table and sets `:macro true` on the var for macro bindings. Order matters: `core-when` and `core-defn` must be defined BEFORE `core-bindings` map literal references them, otherwise compile error.
§
Reader comments (`;`) return `{:jolt/type :jolt/skip}` sentinel; `parse-next`/`parse-string` skip past it. Closing `)`, `]`, `}` produce explicit "Unmatched" errors. `unwrap-meta-name` recursively unwraps `(with-meta sym meta)` to raw symbol — used in def, ns, deftype, defmethod. `resolve-sym` does class-name lookup: `Foo.Bar.Baz` → ns "Foo.Bar" name "Baz". Janet `(set [a b] tuple)` doesn't work — use explicit indexing. `comment` must be a macro (registered in core-macro-names) to avoid evaluating body.
§
Sci bootstrap order: macros(4/4)→protocols(15/17)→utils(39/47)→types(22/27)→unrestrict(2/2)→vars(28/28)→lang(10/10)→ctx-store(6/6)→namespaces(93/98)→core(60/69). All .cljc; #?(:clj) resolved at read time, #?(:cljs)→nil.
§
Janet `last` works only on indexed types (tuple, array). On strings it returns nil. Use `(s (- (length s) 1))` to get the last character of a string, or `(string/slice s (- (length s) 1))` for the last char as string. `(last "hello")` → nil, not `\o`.
§
resolve-sym returns `:jolt/not-found` sentinel to distinguish nil local bindings from absent ones, preventing accidental fallthrough to global resolution. Falls back to `clojure.core` namespace for unqualified symbols. `bind-put` helper wraps nil as `:jolt/nil`; resolve-sym unwraps. Multi-arity `fn*` dispatches on fixed-params count vs variadic args.
§
Janet `(string :keyword)` returns `"keyword"` (without colon). Use this for keyword→string conversion in destructuring code. Avoid `(name :keyword)` in Jolt evaluation context — `name` is a Janet built-in but may not be available in all eval contexts.
§
`:jolt/nil-sentinel` used in `core-bindings` map for vars that should have nil root values. Janet table literals drop nil entries: `@{"*1" nil}` → empty table. `init-core!` unwraps sentinels back to nil with `(if (= fn :jolt/nil-sentinel) nil fn)`. Applies to `*1`, `*2`, `*3`, `*e` and potentially other nil-rooted vars.
§
`fn*` and `defmacro` now capture `defining-ns` at definition time and restore it via `(ctx-set-current-ns ctx defining-ns)` / `(ctx-set-current-ns ctx saved-ns)` around body evaluation. This ensures symbols in function/macro bodies resolve in the defining namespace, not the calling context. Applies to both multi-arity and single-arity `fn*` forms.
§
SCI load order: `macros``protocols``types``unrestrict``vars``lang``utils``namespaces``core`. Utils must load after lang (needs `lang/->Namespace`, `vars/unqualify-symbol`). Namespaces needs utils (for `clojure-core-ns`, `dynamic-var`, `unqualify-symbol`). Core needs namespaces (for `*1`, `*2`, `*3`, `*e`, `resolve`).

View file

@ -1,3 +1,5 @@
Janet `apply` requires a function/cfunction. When tables/structs are used as lookup maps (like deftype fields, multimethod dispatch tables), they get called via `(get f key)` not `(apply f args)`. The evaluator's default call path checks `(function? f)` before `apply`, falling back to `(get f (first args))` for single-arg table/struct calls.
§
Janet structs silently omit entries with nil values: `(struct ;[:x nil :y 1])``{:y 1}`. The `:x` key is completely dropped from the struct, not just set to nil. Use `@{}` (mutable table) when map needs nil-valued entries. This caused the `&env` implicit binding to fail — `(put new-bindings "&env" {'ns nil})` created a struct that became empty `{}`, so `(:ns &env)` failed with unknown method. Fix: use `@{}` table for bindings that may contain nil values, or use a non-nil sentinel.
§
Janet `(put table key nil)` silently drops the key — it's a no-op, not a way to store nil. This is SEPARATE from struct-nil-drop: even mutable `@{}` tables drop nil values on `put`. The `bind-put` helper in evaluator.janet stores nil as `:jolt/nil` sentinel; `resolve-sym` unwraps it back to `nil`. All binding `put` calls in `fn*`, `let*`, `loop*`, macro bodies, and `deftype` reify MUST use `bind-put`, not raw `put`.

View file

@ -2,35 +2,35 @@
"jpm-build": {
"created_by": "agent",
"use_count": 0,
"view_count": 9,
"view_count": 10,
"patch_count": 0,
"last_viewed_at": "2026-06-02T02:26:40.592223+00:00",
"last_viewed_at": "2026-06-02T03:42:37.626504+00:00",
"created_at": "2026-06-01T20:56:39.144222+00:00",
"state": "active",
"pinned": false
},
"jolt-bootstrap": {
"created_by": "agent",
"use_count": 6,
"view_count": 15,
"patch_count": 9,
"last_used_at": "2026-06-02T03:25:24.143420+00:00",
"last_viewed_at": "2026-06-02T03:25:24.137076+00:00",
"last_patched_at": "2026-06-02T03:25:52.625469+00:00",
"created_at": "2026-06-01T21:49:51.101718+00:00",
"state": "active",
"pinned": false
},
"jolt-dev": {
"created_by": "agent",
"use_count": 6,
"view_count": 15,
"patch_count": 14,
"last_used_at": "2026-06-02T03:23:57.409721+00:00",
"last_viewed_at": "2026-06-02T03:23:57.400994+00:00",
"last_patched_at": "2026-06-02T03:24:21.695870+00:00",
"use_count": 9,
"view_count": 19,
"patch_count": 20,
"last_used_at": "2026-06-02T04:06:33.131408+00:00",
"last_viewed_at": "2026-06-02T04:06:33.124233+00:00",
"last_patched_at": "2026-06-02T04:07:28.365921+00:00",
"created_at": "2026-06-01T21:26:06.614465+00:00",
"state": "active",
"pinned": false
},
"jolt-bootstrap": {
"created_by": "agent",
"use_count": 8,
"view_count": 18,
"patch_count": 10,
"last_used_at": "2026-06-02T03:43:48.038435+00:00",
"last_viewed_at": "2026-06-02T03:43:48.028512+00:00",
"last_patched_at": "2026-06-02T03:44:45.935676+00:00",
"created_at": "2026-06-01T21:49:51.101718+00:00",
"state": "active",
"pinned": false
}
}

View file

@ -1,4 +1,5 @@
# jolt-bootstrap
# jolt-bootstrap
TDD workflow for bootstrapping a Clojure interpreter on Janet
@ -81,8 +82,19 @@ Used in `def`, `ns`, `deftype`, `defmethod` to handle metadata-wrapped names.
### deftype →TypeName constructor
`deftype` interns both `TypeName` and `->TypeName` (Clojure arrow constructor convention).
### bind-put helper and :jolt/nil sentinel
Janet's `(put table key nil)` silently drops the key, even on mutable `@{}` tables.
`bind-put` stores nil as `:jolt/nil` sentinel; `resolve-sym` unwraps `:jolt/nil` back to `nil`.
Must be used for ALL binding `put` calls: `fn*`, `let*`, `loop*`, macro bodies, `deftype` reify.
### resolve-sym sentinels
- `:jolt/not-found` — returned when a symbol is truly absent (distinct from nil binding)
- `:jolt/nil` → unwrapped to actual `nil` — nil values in bindings stored as sentinel due to Janet `put` nil-drop
- Auto-refer fallback: unqualified symbols not found in current ns fall back to `clojure.core`
## Pitfalls
- Janet `let` can't bind to nil; use `(var x nil)` then `(set x val)`
- Janet `(put table key nil)` silently drops the key — use `bind-put` helper for all binding tables
- `(get table)` with 1 arg = compile error, use `(table :key)` shorthand
- `(put fn :key val)` fails on functions; stash metadata on vars instead
- `deftype` field names must be keywords (not strings) for `(inst :field)` access

View file

@ -60,6 +60,8 @@ The match arm receives `ctx`, `bindings`, and `form` (the full list). Use `(in f
- Functions are not tables — `(put fn :prop val)` fails. Stash properties on vars
- `match` is Janet's pattern matching — no `case` or `cond` needed for simple dispatch
- Janet structs silently **drop entries with nil values**: `(struct ;[:x nil :y 1])``{:y 1}`. Use `@{}` mutable tables when nil-valued entries are needed (e.g., `&env` binding `@{}` for macro bodies)
- Janet `(put table key nil)` **silently drops the key** — even on mutable `@{}` tables. Use `bind-put` helper (stores `:jolt/nil` sentinel) for binding tables; `resolve-sym` unwraps `:jolt/nil` back to `nil`. All `put` calls on bindings in `fn*`, `let*`, `loop*`, macro bodies, `deftype` reify MUST use `bind-put`
- `resolve-sym` returns `:jolt/not-found` sentinel to distinguish nil local bindings from absent ones; falls back to `clojure.core` for unqualified symbols
- `match` with string patterns returns **nil** (not error) when no pattern matches. Used in `eval-list` to handle non-symbol heads cleanly — keyword heads like `:ns` fall through to default function application
- `break` in `while` does NOT return a value in Janet. Use `(var done nil)` + `(set done val)` + check pattern instead
- Janet `#{}` set literals can cause parse issues in some contexts — use `@[]` as fallback
@ -68,6 +70,9 @@ The match arm receives `ctx`, `bindings`, and `form` (the full list). Use `(in f
- `(length struct)` counts key-value pairs, not keys. Use `(length (keys struct))` for key count
- **`(last string)` returns nil** — `last` works only on indexed types (tuple, array). For strings use `(s (- (length s) 1))` or `(string/slice s (- (length s) 1))`
- **`(set [a b] tuple)` doesn't work** — Janet's `set` doesn't support destructuring. Use `(tuple 0)` / `(tuple 1)` or explicit individual assignments
- **`(string :keyword)` returns `"keyword"` (no colon)** — use this for keyword→string conversion. Avoid `(name :keyword)` in Jolt eval context; `name` is a Janet built-in but may shadow or be unavailable
- **`(get struct :key)` works, `(in struct :key)` fails** — `in` is for indexed types only (tuple, array, string, buffer). Use `get` for structs/tables
- **Janet `try` takes exactly 2 args**: `(try body ([err] handler))`. Multi-expression body must wrap in `do`: `(try (do expr1 true) ([err] handler))`
### unwrap-meta-name helper
Recursively unwraps `(with-meta sym meta)` forms to extract the underlying symbol. Used in `def`, `ns`, `deftype`, `defmethod` to handle metadata-wrapped names:
@ -144,7 +149,7 @@ This resolves symbols like `IVar` that are interned in `sci.impl.vars` but refer
- `#_` — discard reader macro, reads next form and returns it as position only
### Core macros (in core.janet)
- `core-macro-names` returns `@{"when" 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 "defn-" true}` — a table
- `core-macro-names` returns `@{"when" true "when-not" true "if-let" true "when-let" true "if-some" true "when-some" true "defn" true "defn-" true "declare" true "fn" true "let" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "comment" true}` — a table
- `init-core!` calls `(get (core-macro-names) name)` to check, then `(put v :macro true)`
- Order matters: macro functions must be defined BEFORE `core-bindings` map references them
@ -154,6 +159,59 @@ This resolves symbols like `IVar` that are interned in `sci.impl.vars` but refer
- `core-volatile!`, `core-vswap!`, `core-vreset!` — volatile (atom-like table with :val key)
- `core-defprotocol` emits `(do (def name @{}) (def method fn) ...)` — macro, returns do form
- `core-extend-type`, `core-extend-protocol`, `core-extend`, `core-reify`, `core-satisfies?`, `core-extends?`, `core-implements?`, `core-type->str` — protocol stubs
- `core-copy-core-var`, `core-copy-var`, `core-macrofy`, `core-new-var`, `core-avoid-method-too-large` — sci.impl.copy-vars stubs (needed by namespaces)
- `core-resolve` — stub returning nil (sci symbol resolution check)
- `core-update` — delegates to Janet's `update` with apply
- `core-prefer-method` — multimethod preference ordering stub
### `:jolt/nil-sentinel` pattern for nil bindings
Janet table literals drop entries with nil values: `@{"x" nil}``{}`. For core bindings that need nil root values (e.g., `*1`, `*2`, `*3`, `*e`), use the sentinel:
```janet
"*1" :jolt/nil-sentinel
"*2" :jolt/nil-sentinel
```
`init-core!` unwraps sentinels back to nil:
```janet
(def v (ns-intern ns name (if (= fn :jolt/nil-sentinel) nil fn)))
```
### `fn*` namespace capture
Functions close over their defining namespace so symbols resolve correctly regardless of caller's current-ns:
```janet
"fn*" (let [args-form ...
defining-ns (ctx-current-ns ctx)] ; capture at definition time
(set self (fn [& fn-args]
(def saved-ns (ctx-current-ns ctx))
(ctx-set-current-ns ctx defining-ns) ; restore defining ns
(var result nil)
(each body-form body
(set result (eval-form ctx fn-bindings body-form)))
(ctx-set-current-ns ctx saved-ns) ; restore caller's ns
result)))
```
Same pattern in `defmacro` bodies.
### `let*` destructuring
Supports `:keys` destructuring with both symbol keys and keyword keys:
```janet
(if (struct? pat)
(let [keys-vec (get pat :keys)]
(if (and keys-vec (indexed? keys-vec))
(each k keys-vec
(def kname (if (keyword? k) (string k) (k :name))) ; keyword→string, symbol→name
(bind-put new-bindings kname (get val (keyword kname))))
(bind-put new-bindings (pat :name) val))) ; plain symbol binding
...
```
Also supports sequential destructuring of vector patterns.
### New core macros
- `core-if-let` — expands `(if-let [x expr] then else)``(let* [x expr] (if x then else))`
- `core-when-let` — expands to `(let* [x expr] (when x body))`
- `core-if-some` — expands to `(let* [x expr] (if (some? x) then else))`
- `core-when-some` — expands to `(let* [x expr] (when (some? x) body))`
- `core-let` — expands `(let [bindings] body)``(let* [bindings] body)` (macro, like `fn``fn*`)
### Namespace handling
- `ns` form handles `^:meta` on ns name via `with-meta` pattern
@ -161,21 +219,22 @@ This resolves symbols like `IVar` that are interned in `sci.impl.vars` but refer
- `require` clause in `ns` wraps each spec in `(when s ...)` for nil-safety
- `resolve-var` falls back to checking clojure.core namespace if var not found in current ns
### Bootstrap loading order
### Bootstrap loading order (corrected)
```
sci.impl.macros (4/4 ok)
sci.impl.protocols (15/17 ok)
sci.impl.utils (39/47 ok)
sci.impl.types (22/27 ok)
sci.impl.protocols (15/15 ok)
sci.impl.types (22/22 ok)
sci.impl.unrestrict (2/2 ok)
sci.impl.vars (28/28 ok — comment block parsed as skip)
sci.lang (10/10 ok — IVar via class-name resolve)
sci.ctx-store (6/6 ok)
sci.impl.namespaces (93/98 ok — parse crash at unmatched brace)
sci.core (60/69 ok — namespaces/*1/*2/*3/*e fail)
sci.impl.vars (28/28 ok)
sci.lang (10/10 ok)
sci.impl.utils (45/46 ok — 1 fail on dynamic-var internals)
sci.impl.namespaces (124/127 ok — 3 remaining: major destructure, clojure-version, iterable)
sci.core (64/65 ok — 1 remaining form)
```
All .cljc files. #?(:clj ...) resolved at read time. #?(:cljs ...) returns nil.
**Critical load order**: `macros``protocols``types``unrestrict``vars``lang``utils``namespaces``core`. `utils` must load after `lang` (needs `lang/->Namespace`, `vars/unqualify-symbol`). `namespaces` needs `utils` (for `clojure-core-ns`, `dynamic-var`). `core` needs `namespaces` (for `*1`, `*2`, `*3`, `*e`, `resolve`).
### parse-arg-names
- Handles `& rest` args AND nested destructuring vectors
- When an arg is a vector (not a symbol), recurses to extract nested symbol names

View file

@ -617,6 +617,52 @@
@[{:jolt/type :symbol :ns nil :name "if"} not-form
@[{:jolt/type :symbol :ns nil :name "do"} ;body]])
(defn core-if-let
"Macro: (if-let [binding val-expr] then else?)"
[bindings then-form & else-forms]
(def form-sym (in bindings 0))
(def val-form (in bindings 1))
@[{:jolt/type :symbol :ns nil :name "let*"}
@[form-sym val-form]
@[{:jolt/type :symbol :ns nil :name "if"}
form-sym
then-form
;else-forms]])
(defn core-when-let
"Macro: (when-let [binding val-expr] & body)"
[bindings & body]
(def form-sym (in bindings 0))
(def val-form (in bindings 1))
@[{:jolt/type :symbol :ns nil :name "let*"}
@[form-sym val-form]
@[{:jolt/type :symbol :ns nil :name "when"}
form-sym
;body]])
(defn core-if-some
"Macro: (if-some [binding val-expr] then else?)"
[bindings then-form & else-forms]
(def form-sym (in bindings 0))
(def val-form (in bindings 1))
@[{:jolt/type :symbol :ns nil :name "let*"}
@[form-sym val-form]
@[{:jolt/type :symbol :ns nil :name "if"}
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
then-form
;else-forms]])
(defn core-when-some
"Macro: (when-some [binding val-expr] & body)"
[bindings & body]
(def form-sym (in bindings 0))
(def val-form (in bindings 1))
@[{:jolt/type :symbol :ns nil :name "let*"}
@[form-sym val-form]
@[{:jolt/type :symbol :ns nil :name "when"}
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
;body]])
(defn core-defn
"Macro: (defn name [args] body) -> (def name (fn* [args] body))"
[fn-name args-form & body]
@ -670,6 +716,22 @@
(defn core-prefer-method [multifn dispatch-val & dispatch-vals]
nil)
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)
(defn core-resolve [sym] nil)
# update — works on both structs and tables
(defn core-update [m k f & args]
(let [current (get m k)
new-val (apply f current args)]
(put m k new-val)))
# copy-var stubs for sci.impl.copy-vars (used by sci.impl.namespaces)
(defn core-copy-core-var [sym] nil)
(defn core-copy-var [sym & args] nil)
(defn core-macrofy [sym fn] fn)
(defn core-new-var [sym & args] nil)
(defn core-avoid-method-too-large [& args] @{})
# declare macro — accepts symbols, does nothing (forward declaration)
(defn core-declare [& syms]
@[{:jolt/type :symbol :ns nil :name "do"}])
@ -682,6 +744,15 @@
(each a args (array/push result a))
result)
(defn core-let
"Macro: (let [bindings] body) → (let* [bindings] body)"
[bindings & body]
(def result @[])
(array/push result {:jolt/type :symbol :ns nil :name "let*"})
(array/push result bindings)
(each b body (array/push result b))
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]
@ -815,6 +886,10 @@
"not" core-not
"when" core-when
"when-not" core-when-not
"if-let" core-if-let
"when-let" core-when-let
"if-some" core-if-some
"when-some" core-when-some
"defn" core-defn
"defn-" core-defn-
"derive" core-derive
@ -824,6 +899,7 @@
"Object" core-Object
"declare" core-declare
"fn" core-fn
"let" core-let
"defprotocol" core-defprotocol
"extend-type" core-extend-type
"extend-protocol" core-extend-protocol
@ -843,31 +919,43 @@
"definterface" core-definterface
"comment" core-comment
"prefer-method" core-prefer-method
"resolve" core-resolve
"update" core-update
"copy-core-var" core-copy-core-var
"copy-var" core-copy-var
"macrofy" core-macrofy
"new-var" core-new-var
"avoid-method-too-large" core-avoid-method-too-large
"qualified-symbol?" core-qualified-symbol?
"meta" core-meta
# Dynamic vars — stubs for SCI bootstrap
"*unchecked-math*" false
"*clojure-version*" {:major 1 :minor 11 :incremental 0 :qualifier nil}})
"*clojure-version*" @{:major 1 :minor 11 :incremental 0 :qualifier nil}
"*1" :jolt/nil-sentinel
"*2" :jolt/nil-sentinel
"*3" :jolt/nil-sentinel
"*e" :jolt/nil-sentinel
"*assert" true})
(defn core-macro-names
"Set of core binding names that are macros."
[]
@{"when" true "when-not" 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})
@{"when" true "when-not" true "if-let" true "when-let" true "if-some" true "when-some" true "defn" true "defn-" true "declare" true "fn" true "let" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "comment" true})
(def init-core!
(fn [& args]
(case (length args)
1 (let [ctx (args 0)
ns (ctx-find-ns ctx "clojure.core")]
(loop [[name fn] :pairs core-bindings]
(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]
(def v (ns-intern ns name fn))
ns (ctx-find-ns ctx "clojure.core")]
(loop [[name fn] :pairs core-bindings]
(def v (ns-intern ns name (if (= fn :jolt/nil-sentinel) nil 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]
(def v (ns-intern ns name (if (= fn :jolt/nil-sentinel) nil fn)))
(when (get (core-macro-names) name)
(put v :macro true)))
ns)

View file

@ -239,22 +239,27 @@
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)]
rest-name (arg-info :rest)
defining-ns (ctx-current-ns ctx)]
(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)
(put new-bindings "&env" @{}) # implicit &env for macro bodies (table — nil-safe)
(var i 0)
(each a fixed-names
(bind-put new-bindings a (macro-args i))
(++ i))
(when rest-name
(put new-bindings rest-name (tuple/slice macro-args i)))
# Use defining namespace for symbol resolution
(def saved-ns (ctx-current-ns ctx))
(ctx-set-current-ns ctx defining-ns)
(var result nil)
(each bf body
(set result (eval-form ctx new-bindings bf)))
(ctx-set-current-ns ctx saved-ns)
result))
(let [ns-name (ctx-current-ns ctx)
(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)
@ -290,60 +295,70 @@
(ctx-find-ns ctx ns-name)
nil)
"fn*" (if (array? (in form 1))
# Multi-arity: (fn* ([args] body...) ([args] body...)...)
(let [pairs (tuple/slice form 1)
arities @{}]
(var self nil)
(each pair pairs
(let [args-form (in pair 0)
body (tuple/slice pair 1)
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)
n-fixed (length fixed-names)]
(put arities n-fixed
(fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name fixed-names
(bind-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
(set result (eval-form ctx fn-bindings body-form)))
result))))
(set self (fn [& fn-args]
(let [n (length fn-args)
f (get arities n)]
(if f
(apply f fn-args)
(error (string "Wrong number of args (" n ") passed to fn"))))))
self)
# Single-arity: (fn* [args] body...)
(let [args-form (in form 1)
body (tuple/slice form 2)
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 fixed-names
(bind-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
(set result (eval-form ctx fn-bindings body-form)))
result))
# Multi-arity: (fn* ([args] body...) ([args] body...)...)
(let [pairs (tuple/slice form 1)
arities @{}
defining-ns (ctx-current-ns ctx)]
(var self nil)
(each pair pairs
(let [args-form (in pair 0)
body (tuple/slice pair 1)
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)
n-fixed (length fixed-names)]
(put arities n-fixed
(fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name fixed-names
(bind-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)
# Use defining namespace for symbol resolution
(def saved-ns (ctx-current-ns ctx))
(ctx-set-current-ns ctx defining-ns)
(var result nil)
(each body-form body
(set result (eval-form ctx fn-bindings body-form)))
(ctx-set-current-ns ctx saved-ns)
result))))
(set self (fn [& fn-args]
(let [n (length fn-args)
f (get arities n)]
(if f
(apply f fn-args)
(error (string "Wrong number of args (" n ") passed to fn"))))))
self)
# Single-arity: (fn* [args] body...)
(let [args-form (in form 1)
body (tuple/slice form 2)
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)
defining-ns (ctx-current-ns ctx)]
(var self nil)
(set self (fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name fixed-names
(bind-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)
# Use defining namespace for symbol resolution
(def saved-ns (ctx-current-ns ctx))
(ctx-set-current-ns ctx defining-ns)
(var result nil)
(each body-form body
(set result (eval-form ctx fn-bindings body-form)))
(ctx-set-current-ns ctx saved-ns)
result))
self))
"let*" (let [bind-vec (in form 1)
body (tuple/slice form 2)]
@ -352,9 +367,28 @@
(var i 0)
(let [len (length bind-vec)]
(while (< i len)
(let [sym (bind-vec i)]
(let [pat (bind-vec i)]
(def val (eval-form ctx new-bindings (bind-vec (+ i 1))))
(bind-put new-bindings (sym :name) val)
# Handle destructuring patterns
(if (struct? pat)
(let [keys-vec (get pat :keys)]
(if (and keys-vec (indexed? keys-vec))
(each k keys-vec
(def kname (if (keyword? k) (string k) (k :name)))
(bind-put new-bindings kname (get val (keyword kname))))
(bind-put new-bindings (pat :name) val)))
(if (indexed? pat)
# Sequential destructuring (vector pattern)
(do
(var di 0)
(while (< di (length pat))
(let [inner-pat (in pat di)]
(if (struct? inner-pat)
(bind-put new-bindings (inner-pat :name) (get val di))
(bind-put new-bindings inner-pat (get val di))))
(+= di 1)))
# Plain symbol binding
(bind-put new-bindings (pat :name) val)))
(+= i 2))))
(var result nil)
(each body-form body

70
test/test-load-sci.janet Normal file
View file

@ -0,0 +1,70 @@
(use ../src/jolt/evaluator)
(use ../src/jolt/types)
(use ../src/jolt/reader)
(use ../src/jolt/api)
(def ctx (init))
(defn load-file [ctx path]
(var s (slurp path))
(var count 0)
(var ok 0)
(var fail 0)
(var failures @[])
(while (> (length (string/trim s)) 0)
(def [form rest] (parse-next s))
(set s rest)
(++ count)
(if (not (nil? form))
(do
(printf "eval form %d..." count)
(flush)
(if (try
(do (eval-form ctx @{} form) true)
([err]
(printf " FAIL: %q\n" err)
(array/push failures {:form-number count :error (string err) :form (string form)})
false))
(do
(printf " OK\n")
(++ ok))
(++ fail)))))
{:ok ok :fail fail :total count :failures failures})
(def sci-base "/Users/yogthos/src/sci/src/sci")
(def load-order @[
["impl/macros.cljc" nil]
["impl/protocols.cljc" nil]
["impl/types.cljc" nil]
["impl/unrestrict.cljc" nil]
["impl/vars.cljc" nil]
["lang.cljc" nil]
["impl/utils.cljc" nil]
["impl/namespaces.cljc" nil]
["core.cljc" nil]
])
(var total-ok 0)
(var total-fail 0)
(var all-failures @[])
(each [file expected-ns] load-order
(def path (string sci-base "/" file))
(printf "\n=== Loading %s ===\n" file)
(def result (load-file ctx path))
(printf " Result: %d ok, %d fail, %d total\n" (result :ok) (result :fail) (result :total))
(+= total-ok (result :ok))
(+= total-fail (result :fail))
(each f (result :failures)
(array/push all-failures {:file file :form-number (f :form-number) :error (f :error) :form (f :form)})))
(printf "\n==============================\n")
(printf "TOTAL: %d ok, %d fail, %d total\n" total-ok total-fail (+ total-ok total-fail))
(printf "==============================\n")
(when (> (length all-failures) 0)
(printf "\n=== FAILURES ===\n")
(each f all-failures
(printf "[%s:%d] %s\n" (f :file) (f :form-number) (f :error))
(printf " form: %s\n" (f :form))))