smalltalk: class table + bootstrap hierarchy + 54 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
291
lib/smalltalk/runtime.sx
Normal file
291
lib/smalltalk/runtime.sx
Normal file
@@ -0,0 +1,291 @@
|
||||
;; Smalltalk runtime — class table, bootstrap hierarchy, type→class mapping,
|
||||
;; instance construction. Method dispatch / eval-ast live in a later layer.
|
||||
;;
|
||||
;; Class record shape:
|
||||
;; {:name "Foo"
|
||||
;; :superclass "Object" ; or nil for Object itself
|
||||
;; :ivars (list "x" "y") ; instance variable names declared on this class
|
||||
;; :methods (dict selector→method-record)
|
||||
;; :class-methods (dict selector→method-record)}
|
||||
;;
|
||||
;; A method record is the AST returned by st-parse-method, plus a :defining-class
|
||||
;; field so super-sends can resolve from the right place. (Methods are registered
|
||||
;; via runtime helpers that fill the field.)
|
||||
;;
|
||||
;; The class table is a single dict keyed by class name. Bootstrap installs the
|
||||
;; canonical hierarchy. Test code resets it via (st-bootstrap-classes!).
|
||||
|
||||
(define st-class-table {})
|
||||
|
||||
(define st-class-table-clear! (fn () (set! st-class-table {})))
|
||||
|
||||
(define
|
||||
st-class-define!
|
||||
(fn
|
||||
(name superclass ivars)
|
||||
(begin
|
||||
(set!
|
||||
st-class-table
|
||||
(assoc
|
||||
st-class-table
|
||||
name
|
||||
{:name name
|
||||
:superclass superclass
|
||||
:ivars ivars
|
||||
:methods {}
|
||||
:class-methods {}}))
|
||||
name)))
|
||||
|
||||
(define
|
||||
st-class-get
|
||||
(fn (name) (if (has-key? st-class-table name) (get st-class-table name) nil)))
|
||||
|
||||
(define
|
||||
st-class-exists?
|
||||
(fn (name) (has-key? st-class-table name)))
|
||||
|
||||
(define
|
||||
st-class-superclass
|
||||
(fn
|
||||
(name)
|
||||
(let
|
||||
((c (st-class-get name)))
|
||||
(cond ((= c nil) nil) (else (get c :superclass))))))
|
||||
|
||||
;; Walk class chain root-to-leaf? No, follow superclass chain leaf-to-root.
|
||||
;; Returns list of class names starting at `name` and ending with the root.
|
||||
(define
|
||||
st-class-chain
|
||||
(fn
|
||||
(name)
|
||||
(let ((acc (list)) (cur name))
|
||||
(begin
|
||||
(define
|
||||
ch-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and (not (= cur nil)) (st-class-exists? cur))
|
||||
(begin
|
||||
(append! acc cur)
|
||||
(set! cur (st-class-superclass cur))
|
||||
(ch-loop)))))
|
||||
(ch-loop)
|
||||
acc))))
|
||||
|
||||
;; Inherited + own ivars in declaration order from root to leaf.
|
||||
(define
|
||||
st-class-all-ivars
|
||||
(fn
|
||||
(name)
|
||||
(let ((chain (reverse (st-class-chain name))) (out (list)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(cn)
|
||||
(let
|
||||
((c (st-class-get cn)))
|
||||
(when
|
||||
(not (= c nil))
|
||||
(for-each (fn (iv) (append! out iv)) (get c :ivars)))))
|
||||
chain)
|
||||
out))))
|
||||
|
||||
;; Method install. The defining-class field is stamped on the method record
|
||||
;; so super-sends look up from the right point in the chain.
|
||||
(define
|
||||
st-class-add-method!
|
||||
(fn
|
||||
(cls-name selector method-ast)
|
||||
(let
|
||||
((cls (st-class-get cls-name)))
|
||||
(cond
|
||||
((= cls nil) (error (str "st-class-add-method!: unknown class " cls-name)))
|
||||
(else
|
||||
(let
|
||||
((m (assoc method-ast :defining-class cls-name)))
|
||||
(begin
|
||||
(set!
|
||||
st-class-table
|
||||
(assoc
|
||||
st-class-table
|
||||
cls-name
|
||||
(assoc
|
||||
cls
|
||||
:methods
|
||||
(assoc (get cls :methods) selector m))))
|
||||
selector)))))))
|
||||
|
||||
(define
|
||||
st-class-add-class-method!
|
||||
(fn
|
||||
(cls-name selector method-ast)
|
||||
(let
|
||||
((cls (st-class-get cls-name)))
|
||||
(cond
|
||||
((= cls nil) (error (str "st-class-add-class-method!: unknown class " cls-name)))
|
||||
(else
|
||||
(let
|
||||
((m (assoc method-ast :defining-class cls-name)))
|
||||
(begin
|
||||
(set!
|
||||
st-class-table
|
||||
(assoc
|
||||
st-class-table
|
||||
cls-name
|
||||
(assoc
|
||||
cls
|
||||
:class-methods
|
||||
(assoc (get cls :class-methods) selector m))))
|
||||
selector)))))))
|
||||
|
||||
;; Method lookup: walk superclass chain starting at `cls-name`.
|
||||
;; class-side? = true searches :class-methods, false searches :methods.
|
||||
;; Returns the method record (with :defining-class) or nil.
|
||||
(define
|
||||
st-method-lookup
|
||||
(fn
|
||||
(cls-name selector class-side?)
|
||||
(let
|
||||
((found nil))
|
||||
(begin
|
||||
(define
|
||||
ml-loop
|
||||
(fn
|
||||
(cur)
|
||||
(when
|
||||
(and (= found nil) (not (= cur nil)) (st-class-exists? cur))
|
||||
(let
|
||||
((c (st-class-get cur)))
|
||||
(let
|
||||
((dict (if class-side? (get c :class-methods) (get c :methods))))
|
||||
(cond
|
||||
((has-key? dict selector) (set! found (get dict selector)))
|
||||
(else (ml-loop (get c :superclass)))))))))
|
||||
(ml-loop cls-name)
|
||||
found))))
|
||||
|
||||
;; SX value → Smalltalk class name. Native types are not boxed.
|
||||
(define
|
||||
st-class-of
|
||||
(fn
|
||||
(v)
|
||||
(cond
|
||||
((= v nil) "UndefinedObject")
|
||||
((= v true) "True")
|
||||
((= v false) "False")
|
||||
((integer? v) "SmallInteger")
|
||||
((number? v) "Float")
|
||||
((string? v) "String")
|
||||
((symbol? v) "Symbol")
|
||||
((list? v) "Array")
|
||||
((and (dict? v) (has-key? v :type) (= (get v :type) "st-instance"))
|
||||
(get v :class))
|
||||
((and (dict? v) (has-key? v :type) (= (get v :type) "block"))
|
||||
"BlockClosure")
|
||||
((and (dict? v) (has-key? v :st-block?) (get v :st-block?))
|
||||
"BlockClosure")
|
||||
((dict? v) "Dictionary")
|
||||
((lambda? v) "BlockClosure")
|
||||
(else "Object"))))
|
||||
|
||||
;; Construct a fresh instance of cls-name. Ivars (own + inherited) start as nil.
|
||||
(define
|
||||
st-make-instance
|
||||
(fn
|
||||
(cls-name)
|
||||
(cond
|
||||
((not (st-class-exists? cls-name))
|
||||
(error (str "st-make-instance: unknown class " cls-name)))
|
||||
(else
|
||||
(let
|
||||
((iv-names (st-class-all-ivars cls-name)) (ivars {}))
|
||||
(begin
|
||||
(for-each (fn (n) (set! ivars (assoc ivars n nil))) iv-names)
|
||||
{:type "st-instance" :class cls-name :ivars ivars}))))))
|
||||
|
||||
(define
|
||||
st-instance?
|
||||
(fn
|
||||
(v)
|
||||
(and (dict? v) (has-key? v :type) (= (get v :type) "st-instance"))))
|
||||
|
||||
(define
|
||||
st-iv-get
|
||||
(fn
|
||||
(inst name)
|
||||
(let ((ivs (get inst :ivars)))
|
||||
(if (has-key? ivs name) (get ivs name) nil))))
|
||||
|
||||
(define
|
||||
st-iv-set!
|
||||
(fn
|
||||
(inst name value)
|
||||
(let
|
||||
((new-ivars (assoc (get inst :ivars) name value)))
|
||||
(assoc inst :ivars new-ivars))))
|
||||
|
||||
;; Inherits-from check: is `descendant` either equal to `ancestor` or a subclass?
|
||||
(define
|
||||
st-class-inherits-from?
|
||||
(fn
|
||||
(descendant ancestor)
|
||||
(let ((found false) (cur descendant))
|
||||
(begin
|
||||
(define
|
||||
ih-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and (not found) (not (= cur nil)) (st-class-exists? cur))
|
||||
(cond
|
||||
((= cur ancestor) (set! found true))
|
||||
(else
|
||||
(begin
|
||||
(set! cur (st-class-superclass cur))
|
||||
(ih-loop)))))))
|
||||
(ih-loop)
|
||||
found))))
|
||||
|
||||
;; Bootstrap the canonical class hierarchy. Reset and rebuild.
|
||||
(define
|
||||
st-bootstrap-classes!
|
||||
(fn
|
||||
()
|
||||
(begin
|
||||
(st-class-table-clear!)
|
||||
;; Root
|
||||
(st-class-define! "Object" nil (list))
|
||||
;; Class side machinery
|
||||
(st-class-define! "Behavior" "Object" (list "superclass" "methodDict" "format"))
|
||||
(st-class-define! "ClassDescription" "Behavior" (list "instanceVariables" "organization"))
|
||||
(st-class-define! "Class" "ClassDescription" (list "name" "subclasses"))
|
||||
(st-class-define! "Metaclass" "ClassDescription" (list "thisClass"))
|
||||
;; Pseudo-variable types
|
||||
(st-class-define! "UndefinedObject" "Object" (list))
|
||||
(st-class-define! "Boolean" "Object" (list))
|
||||
(st-class-define! "True" "Boolean" (list))
|
||||
(st-class-define! "False" "Boolean" (list))
|
||||
;; Magnitudes
|
||||
(st-class-define! "Magnitude" "Object" (list))
|
||||
(st-class-define! "Number" "Magnitude" (list))
|
||||
(st-class-define! "Integer" "Number" (list))
|
||||
(st-class-define! "SmallInteger" "Integer" (list))
|
||||
(st-class-define! "LargePositiveInteger" "Integer" (list))
|
||||
(st-class-define! "Float" "Number" (list))
|
||||
(st-class-define! "Character" "Magnitude" (list "value"))
|
||||
;; Collections
|
||||
(st-class-define! "Collection" "Object" (list))
|
||||
(st-class-define! "SequenceableCollection" "Collection" (list))
|
||||
(st-class-define! "ArrayedCollection" "SequenceableCollection" (list))
|
||||
(st-class-define! "Array" "ArrayedCollection" (list))
|
||||
(st-class-define! "String" "ArrayedCollection" (list))
|
||||
(st-class-define! "Symbol" "String" (list))
|
||||
(st-class-define! "OrderedCollection" "SequenceableCollection" (list "array" "firstIndex" "lastIndex"))
|
||||
(st-class-define! "Dictionary" "Collection" (list))
|
||||
;; Blocks / contexts
|
||||
(st-class-define! "BlockClosure" "Object" (list))
|
||||
"ok")))
|
||||
|
||||
;; Initialise on load. Tests can re-bootstrap to reset state.
|
||||
(st-bootstrap-classes!)
|
||||
@@ -59,10 +59,12 @@ EPOCHS
|
||||
(epoch 2)
|
||||
(load "lib/smalltalk/parser.sx")
|
||||
(epoch 3)
|
||||
(load "lib/smalltalk/tests/tokenize.sx")
|
||||
(load "lib/smalltalk/runtime.sx")
|
||||
(epoch 4)
|
||||
(load "$FILE")
|
||||
(load "lib/smalltalk/tests/tokenize.sx")
|
||||
(epoch 5)
|
||||
(load "$FILE")
|
||||
(epoch 6)
|
||||
(eval "(list st-test-pass st-test-fail)")
|
||||
EPOCHS
|
||||
fi
|
||||
@@ -108,10 +110,12 @@ EPOCHS
|
||||
(epoch 2)
|
||||
(load "lib/smalltalk/parser.sx")
|
||||
(epoch 3)
|
||||
(load "lib/smalltalk/tests/tokenize.sx")
|
||||
(load "lib/smalltalk/runtime.sx")
|
||||
(epoch 4)
|
||||
(load "$FILE")
|
||||
(load "lib/smalltalk/tests/tokenize.sx")
|
||||
(epoch 5)
|
||||
(load "$FILE")
|
||||
(epoch 6)
|
||||
(eval "(map (fn (f) (get f :name)) st-test-fails)")
|
||||
EPOCHS
|
||||
fi
|
||||
|
||||
182
lib/smalltalk/tests/runtime.sx
Normal file
182
lib/smalltalk/tests/runtime.sx
Normal file
@@ -0,0 +1,182 @@
|
||||
;; Smalltalk runtime tests — class table, type→class mapping, instances.
|
||||
;;
|
||||
;; Reuses helpers (st-test, st-deep=?) from tokenize.sx. Counters reset
|
||||
;; here so this file's summary covers runtime tests only.
|
||||
|
||||
(set! st-test-pass 0)
|
||||
(set! st-test-fail 0)
|
||||
(set! st-test-fails (list))
|
||||
|
||||
;; Fresh hierarchy for every test file.
|
||||
(st-bootstrap-classes!)
|
||||
|
||||
;; ── 1. Bootstrap installed expected classes ──
|
||||
(st-test "Object exists" (st-class-exists? "Object") true)
|
||||
(st-test "Behavior exists" (st-class-exists? "Behavior") true)
|
||||
(st-test "Metaclass exists" (st-class-exists? "Metaclass") true)
|
||||
(st-test "True/False/UndefinedObject"
|
||||
(and
|
||||
(st-class-exists? "True")
|
||||
(st-class-exists? "False")
|
||||
(st-class-exists? "UndefinedObject"))
|
||||
true)
|
||||
(st-test "SmallInteger / Float / Symbol exist"
|
||||
(and
|
||||
(st-class-exists? "SmallInteger")
|
||||
(st-class-exists? "Float")
|
||||
(st-class-exists? "Symbol"))
|
||||
true)
|
||||
(st-test "BlockClosure exists" (st-class-exists? "BlockClosure") true)
|
||||
|
||||
;; ── 2. Superclass chain ──
|
||||
(st-test "Object has no superclass" (st-class-superclass "Object") nil)
|
||||
(st-test "Behavior super = Object" (st-class-superclass "Behavior") "Object")
|
||||
(st-test "True super = Boolean" (st-class-superclass "True") "Boolean")
|
||||
(st-test "Symbol super = String" (st-class-superclass "Symbol") "String")
|
||||
(st-test
|
||||
"String chain"
|
||||
(st-class-chain "String")
|
||||
(list "String" "ArrayedCollection" "SequenceableCollection" "Collection" "Object"))
|
||||
(st-test
|
||||
"SmallInteger chain"
|
||||
(st-class-chain "SmallInteger")
|
||||
(list "SmallInteger" "Integer" "Number" "Magnitude" "Object"))
|
||||
|
||||
;; ── 3. inherits-from? ──
|
||||
(st-test "True inherits from Boolean" (st-class-inherits-from? "True" "Boolean") true)
|
||||
(st-test "True inherits from Object" (st-class-inherits-from? "True" "Object") true)
|
||||
(st-test "True inherits from True" (st-class-inherits-from? "True" "True") true)
|
||||
(st-test
|
||||
"True does not inherit from Number"
|
||||
(st-class-inherits-from? "True" "Number")
|
||||
false)
|
||||
(st-test
|
||||
"Object does not inherit from Number"
|
||||
(st-class-inherits-from? "Object" "Number")
|
||||
false)
|
||||
|
||||
;; ── 4. type→class mapping ──
|
||||
(st-test "class-of nil" (st-class-of nil) "UndefinedObject")
|
||||
(st-test "class-of true" (st-class-of true) "True")
|
||||
(st-test "class-of false" (st-class-of false) "False")
|
||||
(st-test "class-of int" (st-class-of 42) "SmallInteger")
|
||||
(st-test "class-of zero" (st-class-of 0) "SmallInteger")
|
||||
(st-test "class-of negative int" (st-class-of -3) "SmallInteger")
|
||||
(st-test "class-of float" (st-class-of 3.14) "Float")
|
||||
(st-test "class-of string" (st-class-of "hi") "String")
|
||||
(st-test "class-of symbol" (st-class-of (quote foo)) "Symbol")
|
||||
(st-test "class-of list" (st-class-of (list 1 2)) "Array")
|
||||
(st-test "class-of empty list" (st-class-of (list)) "Array")
|
||||
(st-test "class-of lambda" (st-class-of (fn (x) x)) "BlockClosure")
|
||||
(st-test "class-of dict" (st-class-of {:a 1}) "Dictionary")
|
||||
|
||||
;; ── 5. User class definition ──
|
||||
(st-class-define! "Account" "Object" (list "balance" "owner"))
|
||||
(st-class-define! "SavingsAccount" "Account" (list "rate"))
|
||||
|
||||
(st-test "Account exists" (st-class-exists? "Account") true)
|
||||
(st-test "Account super = Object" (st-class-superclass "Account") "Object")
|
||||
(st-test
|
||||
"SavingsAccount chain"
|
||||
(st-class-chain "SavingsAccount")
|
||||
(list "SavingsAccount" "Account" "Object"))
|
||||
(st-test
|
||||
"SavingsAccount own ivars"
|
||||
(get (st-class-get "SavingsAccount") :ivars)
|
||||
(list "rate"))
|
||||
(st-test
|
||||
"SavingsAccount inherited+own ivars"
|
||||
(st-class-all-ivars "SavingsAccount")
|
||||
(list "balance" "owner" "rate"))
|
||||
|
||||
;; ── 6. Instance construction ──
|
||||
(define a1 (st-make-instance "Account"))
|
||||
(st-test "instance is st-instance" (st-instance? a1) true)
|
||||
(st-test "instance class" (get a1 :class) "Account")
|
||||
(st-test "instance ivars start nil" (st-iv-get a1 "balance") nil)
|
||||
(st-test
|
||||
"instance has all expected ivars"
|
||||
(sort (keys (get a1 :ivars)))
|
||||
(sort (list "balance" "owner")))
|
||||
(define a2 (st-iv-set! a1 "balance" 100))
|
||||
(st-test "iv-set! returns updated copy" (st-iv-get a2 "balance") 100)
|
||||
(st-test "iv-set! does not mutate original" (st-iv-get a1 "balance") nil)
|
||||
(st-test "class-of instance" (st-class-of a1) "Account")
|
||||
|
||||
(define s1 (st-make-instance "SavingsAccount"))
|
||||
(st-test
|
||||
"subclass instance has all inherited ivars"
|
||||
(sort (keys (get s1 :ivars)))
|
||||
(sort (list "balance" "owner" "rate")))
|
||||
|
||||
;; ── 7. Method install + lookup ──
|
||||
(st-class-add-method!
|
||||
"Account"
|
||||
"balance"
|
||||
(st-parse-method "balance ^ balance"))
|
||||
(st-class-add-method!
|
||||
"Account"
|
||||
"deposit:"
|
||||
(st-parse-method "deposit: amount balance := balance + amount. ^ self"))
|
||||
|
||||
(st-test
|
||||
"method registered"
|
||||
(has-key? (get (st-class-get "Account") :methods) "balance")
|
||||
true)
|
||||
|
||||
(st-test
|
||||
"method lookup direct"
|
||||
(= (st-method-lookup "Account" "balance" false) nil)
|
||||
false)
|
||||
|
||||
(st-test
|
||||
"method lookup walks superclass"
|
||||
(= (st-method-lookup "SavingsAccount" "deposit:" false) nil)
|
||||
false)
|
||||
|
||||
(st-test
|
||||
"method lookup unknown selector"
|
||||
(st-method-lookup "Account" "frobnicate" false)
|
||||
nil)
|
||||
|
||||
(st-test
|
||||
"method lookup records defining class"
|
||||
(get (st-method-lookup "SavingsAccount" "balance" false) :defining-class)
|
||||
"Account")
|
||||
|
||||
;; SavingsAccount overrides deposit:
|
||||
(st-class-add-method!
|
||||
"SavingsAccount"
|
||||
"deposit:"
|
||||
(st-parse-method "deposit: amount ^ super deposit: amount + 1"))
|
||||
|
||||
(st-test
|
||||
"subclass override picked first"
|
||||
(get (st-method-lookup "SavingsAccount" "deposit:" false) :defining-class)
|
||||
"SavingsAccount")
|
||||
|
||||
(st-test
|
||||
"Account still finds its own deposit:"
|
||||
(get (st-method-lookup "Account" "deposit:" false) :defining-class)
|
||||
"Account")
|
||||
|
||||
;; ── 8. Class-side methods ──
|
||||
(st-class-add-class-method!
|
||||
"Account"
|
||||
"new"
|
||||
(st-parse-method "new ^ super new"))
|
||||
(st-test
|
||||
"class-side lookup"
|
||||
(= (st-method-lookup "Account" "new" true) nil)
|
||||
false)
|
||||
(st-test
|
||||
"instance-side does not find class method"
|
||||
(st-method-lookup "Account" "new" false)
|
||||
nil)
|
||||
|
||||
;; ── 9. Re-bootstrap resets table ──
|
||||
(st-bootstrap-classes!)
|
||||
(st-test "after re-bootstrap Account gone" (st-class-exists? "Account") false)
|
||||
(st-test "after re-bootstrap Object stays" (st-class-exists? "Object") true)
|
||||
|
||||
(list st-test-pass st-test-fail)
|
||||
@@ -56,7 +56,7 @@ Core mapping:
|
||||
- [x] Unit tests in `lib/smalltalk/tests/parse.sx`
|
||||
|
||||
### Phase 2 — object model + sequential eval
|
||||
- [ ] Class table + bootstrap: `Object`, `Behavior`, `Class`, `Metaclass`, `UndefinedObject`, `Boolean`/`True`/`False`, `Number`/`Integer`/`Float`, `String`, `Symbol`, `Array`, `Block`
|
||||
- [x] Class table + bootstrap (`lib/smalltalk/runtime.sx`): canonical hierarchy installed (`Object`, `Behavior`, `ClassDescription`, `Class`, `Metaclass`, `UndefinedObject`, `Boolean`/`True`/`False`, `Magnitude`/`Number`/`Integer`/`SmallInteger`/`Float`/`Character`, `Collection`/`SequenceableCollection`/`ArrayedCollection`/`Array`/`String`/`Symbol`/`OrderedCollection`/`Dictionary`, `BlockClosure`). User class definition via `st-class-define!`, methods via `st-class-add-method!` (stamps `:defining-class` for super), method lookup walks chain, ivars accumulated through superclass chain, native SX value types map to Smalltalk classes via `st-class-of`.
|
||||
- [ ] `smalltalk-eval-ast`: literals, variable reference, assignment, message send, cascade, sequence, return
|
||||
- [ ] Method lookup: walk class → superclass; cache hit-class on `(class, selector)`
|
||||
- [ ] `doesNotUnderstand:` fallback constructing `Message` object
|
||||
@@ -108,6 +108,7 @@ Core mapping:
|
||||
|
||||
_Newest first. Agent appends on every commit._
|
||||
|
||||
- 2026-04-25: class table + bootstrap (`lib/smalltalk/runtime.sx`, `lib/smalltalk/tests/runtime.sx`). Canonical hierarchy, type→class mapping for native SX values, instance construction, ivar inheritance, method install with `:defining-class` stamp, instance- and class-side method lookup walking the superclass chain. 54 new tests, 185/185 total.
|
||||
- 2026-04-25: chunk-stream parser + pragmas + 21 chunk/pragma tests (`lib/smalltalk/tests/parse_chunks.sx`). `st-read-chunks` (with `!!` doubling), `st-parse-chunks` state machine for `methodsFor:` batches incl. class-side. Pragmas with multiple keyword pairs, signed numeric / string / symbol args, in either pragma-then-temps or temps-then-pragma order. 131/131 tests pass.
|
||||
- 2026-04-25: expression-level parser + 47 parse tests (`lib/smalltalk/parser.sx`, `lib/smalltalk/tests/parse.sx`). Full message precedence (unary > binary > keyword), cascades, blocks with params/temps, literal/byte arrays, assignment chain, method headers (unary/binary/keyword). Chunk-format `! !` driver deferred to a follow-up box. 110/110 tests pass.
|
||||
- 2026-04-25: tokenizer + 63 tests (`lib/smalltalk/tokenizer.sx`, `lib/smalltalk/tests/tokenize.sx`, `lib/smalltalk/test.sh`). All token types covered except scaled decimals `1.5s2` (deferred). `#(` and `#[` emit open tokens; literal-array contents lexed as ordinary tokens for the parser to interpret.
|
||||
|
||||
Reference in New Issue
Block a user