109 lines
No EOL
5.4 KiB
Markdown
109 lines
No EOL
5.4 KiB
Markdown
# jolt-dev
|
|
|
|
Jolt development workflow — build, test, special form patterns, Janet gotchas
|
|
|
|
## Build & Test
|
|
|
|
```bash
|
|
cd /Users/yogthos/src/jolt
|
|
jpm build # produces build/jolt (gitignored)
|
|
jpm test # runs all tests (test-load-sci times out occasionally)
|
|
janet test/foo.janet # run a single test file from project root
|
|
```
|
|
|
|
Build artifacts are in `build/` which is `.gitignore`d.
|
|
|
|
## Architecture
|
|
|
|
Jolt is a **Janet-hosted SCI** — Jolt's reader + evaluator form the runtime, SCI provides the standard library. Load order: macros → protocols → types → unrestrict → vars → lang → utils → namespaces → core (all 317 forms, 0 failures). Source: `vendor/sci/src/sci/` (git submodule).
|
|
|
|
`sci.core/eval-string` is replaced with Jolt-native: `(eval-form ctx @{} @[{:jolt/type :symbol :ns nil :name "do"} (parse-string s)])`. This bypasses SCI's interpreter/parser/analyzer pipeline. SCI internal namespaces (interpreter, parser, analyzer, opts) have 0 bindings after loading — they require loading their own source files.
|
|
|
|
## SCI Submodule
|
|
|
|
SCI is at `vendor/sci` (git submodule, github.com/borkdude/sci). Load order: macros → protocols → types → unrestrict → vars → lang → utils → namespaces → core
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/jolt/
|
|
types.janet — Var, Namespace, Context, symbol helpers
|
|
reader.janet — recursive descent parser for Clojure syntax
|
|
evaluator.janet — tree-walking interpreter (eval-form, eval-list, syntax-quote*)
|
|
core.janet — clojure.core bindings: predicates, math, collections, macros, stubs
|
|
api.janet — public API: init, eval-string, eval-string*
|
|
main.janet — REPL entry point
|
|
|
|
test/
|
|
evaluator-test.janet — special form tests
|
|
reader-test.janet — parser tests
|
|
core-test.janet — core library tests
|
|
macro-test.janet — syntax-quote and macro tests
|
|
namespace-test.janet — ns, require, in-ns tests
|
|
types-test.janet — Var and Namespace tests
|
|
api-test.janet — public API tests
|
|
bootstrap-test.janet — loads sci.impl.macros
|
|
test-load-sci.janet — loads all sci files + tests eval-string
|
|
```
|
|
|
|
## Janet Gotchas
|
|
|
|
- **`try` form**: `(try body ([err] handler))` — the `([err] handler)` must be ONE line. Multi-line break after `([err]` causes "unexpected closing delimiter )" parse error. Correct: `(try (do-stuff) ([err] nil))`.
|
|
- **`try` separate from `([err]`**: `(try body-form ([err] handler))` is valid — `body-form` and `([err]` on same line.
|
|
- **`(string :kw)`** converts keyword to string. Janet has no `name` function.
|
|
- **`(put {:x 1} :y 2)`** errors — structs don't support `put`. Use `@{}` tables for mutable maps.
|
|
- **`(put table key nil)`** silently drops the key. Use `bind-put` + `:jolt/nil-sentinel` pattern.
|
|
- **`var`** declares mutable locals; `def`/`let` are immutable.
|
|
- **`(set [a b] tuple)`** doesn't work — use explicit indexing `(tuple 0)` / `(tuple 1)`.
|
|
- **`(last "string")`** returns nil — works only on indexed types.
|
|
- **Multi-arity `defn` in Janet**: Use `(fn [& args] (case (length args) ...))` pattern. `defn-?` doesn't exist.
|
|
- **`indexed?` vs `array?`**: Reader produces tuples for vectors, arrays for lists. Check `indexed?` for vector patterns.
|
|
- **`match`** returns nil on no match — used in `eval-list` for non-symbol head fallthrough.
|
|
- **`(length struct)`** counts pairs, not keys. Use `(length (keys struct))`.
|
|
- **`(first struct)`** calls `:jolt/type` method — use `(get struct :key)`.
|
|
|
|
## Special Form Checklist
|
|
|
|
To add a new special form:
|
|
1. Add the name to `special-symbol?` in `src/jolt/evaluator.janet`
|
|
2. Add a match arm in `eval-list`
|
|
3. Add tests in `test/evaluator-test.janet`
|
|
|
|
Current: `quote`, `syntax-quote`, `unquote`, `unquote-splicing`, `do`, `if`, `def`, `defmacro`, `fn*`, `let*`, `loop*`, `recur`, `throw`, `try`, `set!`, `var`, `locking`, `instance?`, `defmulti`, `defmethod`, `deftype`, `new`, `.`
|
|
|
|
## Core Macros and Functions
|
|
|
|
### gensym
|
|
```janet
|
|
(def gensym_counter @{:val 0})
|
|
(defn gensym [&opt prefix-string]
|
|
(default prefix-string "G__")
|
|
(def n (get gensym_counter :val))
|
|
(put gensym_counter :val (+ n 1))
|
|
{:jolt/type :symbol :ns nil :name (string prefix-string n)})
|
|
```
|
|
|
|
### core-name
|
|
Returns name string of keyword, symbol, or string. Uses `(string kw)` for keywords, `.name` field for symbols.
|
|
|
|
### Registered macros in core-macro-names
|
|
`when`, `when-not`, `if-let`, `when-let`, `if-some`, `when-some`, `doto`, `defn`, `defn-`, `declare`, `fn`, `let`, `defrecord`, `defprotocol`, `extend-type`, `extend-protocol`, `extend`, `reify`, `proxy`, `definterface`, `comment`
|
|
|
|
### defrecord stub
|
|
Generates `->TypeName` positional constructor. Expands to `(do (def TypeName (fn* [fields] ...)) (def ->TypeName ...))`.
|
|
|
|
### doto macro
|
|
Uses `gensym` for the object symbol. Expands to `(let* [sym obj] (. sym method args)... sym)`.
|
|
|
|
### defmacro details
|
|
- Supports optional docstring: `(defmacro name [args] body)` or `(defmacro name "doc" [args] body)`
|
|
- Implicit `&env` binding: `(put new-bindings "&env" @{})` — table, not struct (nil-safe)
|
|
- Capture defining namespace for symbol resolution in macro bodies
|
|
|
|
## Janet Table Nil-Drop Workaround
|
|
|
|
`(put table key nil)` silently drops the key. Workflow:
|
|
1. `bind-put` helper in `evaluator.janet` stores nil as `:jolt/nil` sentinel
|
|
2. `resolve-sym` unwraps `:jolt/nil` back to `nil`
|
|
3. Dynamic vars with nil values (`*1`, `*2`, `*3`, `*e`) use `:jolt/nil-sentinel` in `core-bindings`
|
|
4. `init-core!` checks for `:jolt/nil-sentinel` and passes actual nil to `ns-intern` |