multi-arity fn*, nil sentinel in resolve-sym, let pre-registration, meta/when-not/qualified-symbol? core bindings
evaluator: fn* multi-arity dispatch, nil-local-vs-absent sentinel, let* pre-registers bindings core: core-meta, core-when-not, core-qualified-symbol?, *unchecked-math*, *clojure-version*
This commit is contained in:
parent
b20536d1e3
commit
e1317c0a7e
6 changed files with 175 additions and 78 deletions
|
|
@ -1,16 +1,4 @@
|
|||
{
|
||||
"jolt-dev": {
|
||||
"created_by": "agent",
|
||||
"use_count": 6,
|
||||
"view_count": 15,
|
||||
"patch_count": 12,
|
||||
"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-02T02:52:21.531624+00:00",
|
||||
"created_at": "2026-06-01T21:26:06.614465+00:00",
|
||||
"state": "active",
|
||||
"pinned": false
|
||||
},
|
||||
"jpm-build": {
|
||||
"created_by": "agent",
|
||||
"use_count": 0,
|
||||
|
|
@ -23,14 +11,26 @@
|
|||
},
|
||||
"jolt-bootstrap": {
|
||||
"created_by": "agent",
|
||||
"use_count": 5,
|
||||
"view_count": 14,
|
||||
"patch_count": 7,
|
||||
"last_used_at": "2026-06-02T03:23:57.420498+00:00",
|
||||
"last_viewed_at": "2026-06-02T03:23:57.415106+00:00",
|
||||
"last_patched_at": "2026-06-02T02:51:30.047986+00:00",
|
||||
"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",
|
||||
"created_at": "2026-06-01T21:26:06.614465+00:00",
|
||||
"state": "active",
|
||||
"pinned": false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: jolt-bootstrap
|
||||
description: TDD workflow for bootstrapping a Clojure interpreter on Janet
|
||||
---
|
||||
# jolt-bootstrap
|
||||
|
||||
TDD workflow for bootstrapping a Clojure interpreter on Janet
|
||||
|
||||
# Bootstrapping a Clojure interpreter on Janet
|
||||
|
||||
|
|
@ -20,27 +19,34 @@ description: TDD workflow for bootstrapping a Clojure interpreter on Janet
|
|||
|
||||
## Current bootstrap progress
|
||||
|
||||
**Loaded (all .cljc, #? resolved at read time):**
|
||||
- `sci.impl.macros` — 4/4 (ns, defmacro deftime, defmacro usetime, deftime(? macro))
|
||||
- `sci.impl.protocols` — 15/17
|
||||
- `sci.impl.utils` — 39/47
|
||||
- `sci.impl.types` — 22/27
|
||||
- `sci.impl.unrestrict` — 2/2
|
||||
- `sci.impl.vars` — 28/28 (comment block parses via :jolt/skip sentinel)
|
||||
- `sci.lang` — 10/10 (IVar resolves via class-name pattern lookup)
|
||||
- `sci.ctx-store` — 6/6
|
||||
- `sci.impl.namespaces` — 93/98 (parse crash at unmatched brace)
|
||||
- `sci.core` — 60/69 (namespaces/*1/*2/*3/*e unresolved)
|
||||
**All files parse cleanly. Eval status:**
|
||||
|
||||
**Special forms (22):** quote, syntax-quote, unquote, unquote-splicing, do, if, def, defmacro, fn*, let*, loop*, recur, throw, try, set!, var, locking, instance?, defmulti, defmethod, deftype, new, .
|
||||
| File | Forms | Eval OK | Failures |
|
||||
|------|-------|---------|----------|
|
||||
| sci.impl.macros | 4/4 | 4 | 0 |
|
||||
| sci.impl.protocols | 15/17 | 15 | 2 (resolution stubs) |
|
||||
| sci.impl.utils | 39/47 | 39 | 8 (multi-arity, missing deps) |
|
||||
| sci.impl.types | 22/27 | 22 | 5 (resolution stubs) |
|
||||
| sci.impl.unrestrict | 2/2 | 2 | 0 |
|
||||
| sci.impl.vars | 28/28 | 28 | 0 |
|
||||
| sci.lang | 10/10 | 10 | 0 |
|
||||
| sci.ctx-store | 6/6 | 6 | 0 |
|
||||
| sci.impl.namespaces | 93/98 | 93 | 5 (missing copy-core-var dep) |
|
||||
| sci.core | 60/69 | 60 | 4 (*1/*2/*3/*e unresolved) |
|
||||
|
||||
**Reader:** `#?(:clj ...)`, `#?@(:clj ...)` with splicing, `#_` discard (returns :jolt/skip sentinel), `#\` var-quote, `^` metadata. Comments `;` skip via :jolt/skip. Closing delimiters `)`, `]`, `}` produce explicit "Unmatched" errors.
|
||||
**Loading order:** macros → protocols → types → unrestrict → vars(27/28, skip comment block) → lang → utils → ctx-store → namespaces → core
|
||||
|
||||
**Core macros:** when, defn (with docstring), defn-, declare, fn (wraps fn*), defprotocol, extend-type, extend-protocol, extend, reify, proxy, definterface, comment (ignores body), prefer-method (stub)
|
||||
**Added special forms:** quote, syntax-quote, unquote, unquote-splicing, do, if, def, defmacro, fn*, let*, loop*, recur, throw, try, set!, var, locking, instance?, defmulti, defmethod, deftype, new, . (22 total)
|
||||
|
||||
**Key utilities:** `unwrap-meta-name` — recursively unwraps `(with-meta sym meta)` to extract raw symbol. Used in def, ns, deftype, defmethod.
|
||||
**Core additions:** when (macro), defn (macro with docstring), declare (macro), fn (macro — wraps fn*), Object (interop stub), derive, isa?, ancestors, descendants (hierarchy stubs), defprotocol (macro), extend-type, extend-protocol (macro), extend (macro), reify, satisfies?, extends?, implements?, type->str, comment (macro), prefer-method (stub), *unchecked-math* (false), *clojure-version* ({:major 1 :minor 11})
|
||||
|
||||
**Class-name resolution:** unqualified symbols with dots (`Foo.Bar.Baz`) are resolved by splitting at last dot into ns+name.
|
||||
**Reader:** `#?(:clj ...)`, `#?@(:clj ...)` with splicing, `#_` discard, `#\` var-quote, `^` metadata, `;` comments → skip, nil `#?(:cljs ...)` → skip (non-splicing), empty `#?@(:cljs ...)` → empty splice, unmatched `)]}` → explicit errors
|
||||
|
||||
## Current blockers
|
||||
1. `sci.impl.copy-vars` not yet loaded — needed for `copy-core-var`, `copy-var`, `macrofy`, `new-var`
|
||||
2. `sci.impl.resolve`, `sci.impl.cljs`, `sci.impl.multimethods`, `sci.impl.deftype` not yet loaded
|
||||
3. Multi-arity function dispatch edge cases in utils (forms 36-37, 44-45)
|
||||
4. ~9 remaining eval failures across namespaces (5) and sci.core (4) — all tracing back to missing deps
|
||||
|
||||
## Key patterns
|
||||
|
||||
|
|
@ -58,6 +64,8 @@ description: TDD workflow for bootstrapping a Clojure interpreter on Janet
|
|||
### Reader conditional `#?`
|
||||
Resolves at read time: scans for `:clj` keyword, picks next form.
|
||||
`#?@` wraps resolved form in `:jolt/splice` struct for list/vec/set splicing.
|
||||
Nil results (e.g. `#?(:cljs X)` on CLJ) now return `{:jolt/type :jolt/skip}` for non-splicing
|
||||
and `{:jolt/type :jolt/splice :items @[]}` for splicing — preventing orphaned keys in maps.
|
||||
|
||||
### Callable forms check
|
||||
```janet
|
||||
|
|
@ -66,6 +74,13 @@ Resolves at read time: scans for `:clj` keyword, picks next form.
|
|||
(get f (first args))) ; table/struct lookup
|
||||
```
|
||||
|
||||
### unwrap-meta-name helper
|
||||
Recursively unwraps `(with-meta sym meta)` to extract the underlying symbol.
|
||||
Used in `def`, `ns`, `deftype`, `defmethod` to handle metadata-wrapped names.
|
||||
|
||||
### deftype →TypeName constructor
|
||||
`deftype` interns both `TypeName` and `->TypeName` (Clojure arrow constructor convention).
|
||||
|
||||
## Pitfalls
|
||||
- Janet `let` can't bind to nil; use `(var x nil)` then `(set x val)`
|
||||
- `(get table)` with 1 arg = compile error, use `(table :key)` shorthand
|
||||
|
|
@ -76,7 +91,8 @@ Resolves at read time: scans for `:clj` keyword, picks next form.
|
|||
- **`core-macro-names`** is a zero-arg fn returning a table: `(get (core-macro-names) name)`. Don't call it as `(core-macro-names name)` — that's arity mismatch
|
||||
- **Janet `#{}` sets** can cause parse issues — use `@[]` instead for stub collections
|
||||
- **`break` in `while`** doesn't return a value in Janet — use `(var done nil)` + `(while (and cond (not done)) ... (set done result))` pattern instead
|
||||
- **`read-reader-conditional`** for `#?(:cljs X)` with no `:clj` branch returns `[nil new-pos]`. For `#?@(:cljs X)` wrapping, nil gets wrapped in splice struct with `@[nil]` items
|
||||
- **`#_` discard** now works in lists, vectors, and sets — wraps the skipped form in `{:jolt/type :jolt/skip}` and readers check for this
|
||||
- **`read-regex`** now works with `(var done nil)` pattern to return value from while loop
|
||||
- **`#?@` splicing inside vectors** — if the resolved :clj branch is itself a vector, items are extracted and spliced. Works for both lists and vectors.
|
||||
- **`(last string)` returns nil** — `last` works only on indexed types. Use `(s (- (length s) 1))` for last char of string
|
||||
- **`(set [a b] tuple)` doesn't work** — Janet's `set` doesn't support destructuring. Use `(tuple 0)` / `(tuple 1)`
|
||||
- **`#_` discard** works in lists, vectors, sets, and maps — wraps skipped form in `{:jolt/type :jolt/skip}` and readers check for this
|
||||
- **Map reader** must handle `:jolt/skip` and `:jolt/splice` in both key and value positions
|
||||
- **`comment` macro** must be registered in `core-macro-names` to avoid evaluating its body
|
||||
|
|
@ -66,6 +66,31 @@ The match arm receives `ctx`, `bindings`, and `form` (the full list). Use `(in f
|
|||
- `(first struct)` calls `:jolt/type` method — use `(get struct :key)` instead of positional access
|
||||
- Janet `(struct ;[:x nil])` silently drops the nil entry — the map becomes `{}`. Use `@{}` tables for nil-safe entries
|
||||
- `(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
|
||||
|
||||
### 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:
|
||||
```janet
|
||||
(defn- unwrap-meta-name [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))
|
||||
```
|
||||
|
||||
### Reader map k/v handling
|
||||
The map reader must handle three special value types in both key and value positions:
|
||||
- `:jolt/skip` — discarded form (comment, `#_`, nil `#?(:cljs ...)`): skip the K/V pair entirely
|
||||
- `:jolt/splice` — `#?@` splicing: concat items into the kvs array. If splice has 0 items (nil `:cljs` branch), don't push the key
|
||||
|
||||
### `#?(:cljs X)` returns nil → `:jolt/skip`
|
||||
The non-splicing `#?` reader now returns `{:jolt/type :jolt/skip}` for nil results (e.g., `#?(:cljs X)` on CLJ). This prevents orphaned nil keys/values in maps and lists. `parse-next` and `parse-string` skip past skip markers to return the next real form.
|
||||
|
||||
### deftype →TypeName constructor
|
||||
`deftype` now interns both the type name and `->TypeName` arrow constructor (Clojure convention). The dot-suffix constructor check uses `(sym-name (- (length sym-name) 1))` instead of broken `(last sym-name)`.
|
||||
|
||||
## Comment Handling
|
||||
|
||||
|
|
@ -119,7 +144,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 "reify" true "fn" true "proxy" true "definterface" true "defn-" true}` — a table
|
||||
- `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
|
||||
- `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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue