smalltalk: BlockContext value family + 19 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:
92
lib/smalltalk/tests/blocks.sx
Normal file
92
lib/smalltalk/tests/blocks.sx
Normal file
@@ -0,0 +1,92 @@
|
||||
;; BlockContext>>value family tests.
|
||||
;;
|
||||
;; The runtime already implements value, value:, value:value:, value:value:value:,
|
||||
;; value:value:value:value:, and valueWithArguments: in st-block-dispatch.
|
||||
;; This file pins each variant down with explicit tests + closure semantics.
|
||||
|
||||
(set! st-test-pass 0)
|
||||
(set! st-test-fail 0)
|
||||
(set! st-test-fails (list))
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(define ev (fn (src) (smalltalk-eval src)))
|
||||
(define evp (fn (src) (smalltalk-eval-program src)))
|
||||
|
||||
;; ── 1. The value/valueN family ──
|
||||
(st-test "value: zero-arg block" (ev "[42] value") 42)
|
||||
(st-test "value: one-arg block" (ev "[:a | a + 1] value: 10") 11)
|
||||
(st-test "value:value: two-arg" (ev "[:a :b | a * b] value: 3 value: 4") 12)
|
||||
(st-test "value:value:value: three" (ev "[:a :b :c | a + b + c] value: 1 value: 2 value: 3") 6)
|
||||
(st-test "value:value:value:value: four"
|
||||
(ev "[:a :b :c :d | a + b + c + d] value: 1 value: 2 value: 3 value: 4") 10)
|
||||
|
||||
;; ── 2. valueWithArguments: ──
|
||||
(st-test "valueWithArguments: zero-arg"
|
||||
(ev "[99] valueWithArguments: #()") 99)
|
||||
(st-test "valueWithArguments: one-arg"
|
||||
(ev "[:x | x * x] valueWithArguments: #(7)") 49)
|
||||
(st-test "valueWithArguments: many"
|
||||
(ev "[:a :b :c | a , b , c] valueWithArguments: #('foo' '-' 'bar')") "foo-bar")
|
||||
|
||||
;; ── 3. Block returns last expression ──
|
||||
(st-test "block last-expression result" (ev "[1. 2. 3] value") 3)
|
||||
(st-test "block with temps initial state"
|
||||
(ev "[| t u | t := 5. u := t * 2. u] value") 10)
|
||||
|
||||
;; ── 4. Closure over outer locals ──
|
||||
(st-test
|
||||
"block reads outer let temps"
|
||||
(evp "| n | n := 5. ^ [n * n] value")
|
||||
25)
|
||||
(st-test
|
||||
"block writes outer locals (mutating)"
|
||||
(evp "| n | n := 10. [:x | n := n + x] value: 5. ^ n")
|
||||
15)
|
||||
|
||||
;; ── 5. Block sees later mutation of captured local ──
|
||||
(st-test
|
||||
"block re-reads outer local on each invocation"
|
||||
(evp
|
||||
"| n b r1 r2 |
|
||||
n := 1. b := [n].
|
||||
r1 := b value.
|
||||
n := 99.
|
||||
r2 := b value.
|
||||
^ r1 + r2")
|
||||
100)
|
||||
|
||||
;; ── 6. Re-entrant invocations ──
|
||||
(st-test
|
||||
"calling same block twice independent results"
|
||||
(evp
|
||||
"| sq |
|
||||
sq := [:x | x * x].
|
||||
^ (sq value: 3) + (sq value: 4)")
|
||||
25)
|
||||
|
||||
;; ── 7. Nested blocks ──
|
||||
(st-test
|
||||
"nested block closes over both scopes"
|
||||
(evp
|
||||
"| a |
|
||||
a := [:x | [:y | x + y]].
|
||||
^ ((a value: 10) value: 5)")
|
||||
15)
|
||||
|
||||
;; ── 8. Block as method argument ──
|
||||
(st-class-define! "BlockUser" "Object" (list))
|
||||
(st-class-add-method! "BlockUser" "apply:to:"
|
||||
(st-parse-method "apply: aBlock to: x ^ aBlock value: x"))
|
||||
|
||||
(st-test
|
||||
"method invokes block argument"
|
||||
(evp "^ BlockUser new apply: [:n | n * n] to: 9")
|
||||
81)
|
||||
|
||||
;; ── 9. numArgs + class ──
|
||||
(st-test "numArgs zero" (ev "[] numArgs") 0)
|
||||
(st-test "numArgs three" (ev "[:a :b :c | a] numArgs") 3)
|
||||
(st-test "block class is BlockClosure"
|
||||
(str (ev "[1] class name")) "BlockClosure")
|
||||
|
||||
(list st-test-pass st-test-fail)
|
||||
@@ -66,7 +66,7 @@ Core mapping:
|
||||
### Phase 3 — blocks + non-local return (THE SHOWCASE)
|
||||
- [x] Method invocation captures a `^k` (the return continuation) and binds it as the block's escape. `st-invoke` wraps body in `(call/cc (fn (k) ...))`; the frame's `:return-k` is set to k. Block creation copies `(get frame :return-k)` onto the block. Block invocation sets the new frame's `:return-k` to the block's saved one — so non-local return reaches *back through* any number of intermediate block invocations.
|
||||
- [x] `^expr` from inside a block invokes that captured `^k`. The "return" AST type evaluates the expression then calls `(k v)` on the frame's :return-k. Verified: `detect:in:` style early-exit, multi-level nested blocks, ^ from inside `to:do:`/`whileTrue:`, ^ from a block passed to a *different* method (Caller→Helper) returns from Caller.
|
||||
- [ ] `BlockContext>>value`, `value:`, `value:value:`, …, `valueWithArguments:`
|
||||
- [x] `BlockContext>>value`, `value:`, `value:value:`, `value:value:value:`, `value:value:value:value:`, `valueWithArguments:`. Implemented in `st-block-dispatch` + `st-block-apply` (eval iteration); pinned by 19 dedicated tests in `lib/smalltalk/tests/blocks.sx` covering arity through 4, valueWithArguments: with empty/non-empty arg arrays, closures over outer locals (read + mutate + later-mutation re-read), nested blocks, blocks as method arguments, `numArgs`, and `class`.
|
||||
- [ ] `whileTrue:` / `whileTrue` / `whileFalse:` / `whileFalse` as ordinary block sends — runtime intrinsifies the loop in the bytecode JIT
|
||||
- [ ] `ifTrue:` / `ifFalse:` / `ifTrue:ifFalse:` as block sends, similarly intrinsified
|
||||
- [ ] Escape past returned-from method raises `BlockContext>>cannotReturn:`
|
||||
@@ -108,6 +108,7 @@ Core mapping:
|
||||
|
||||
_Newest first. Agent appends on every commit._
|
||||
|
||||
- 2026-04-25: BlockContext value family pinned (`lib/smalltalk/tests/blocks.sx`, 19 tests). Each value/valueN/valueWithArguments: variant verified plus closure semantics (read, write, later-mutation re-read), nested blocks, and block-as-arg. 320/320 total.
|
||||
- 2026-04-25: **THE SHOWCASE** — non-local return via captured method-return continuations + 14 NLR tests (`lib/smalltalk/tests/nlr.sx`). `st-invoke` wraps body in `call/cc`; blocks copy creating method's `^k`; `^expr` invokes that k. Verified across nested blocks, `to:do:` / `whileTrue:`, blocks passed to different methods (Caller→Helper escapes back to Caller), inner-vs-outer method nesting. Sentinel-based return removed. 301/301 total.
|
||||
- 2026-04-25: `super` send + 9 tests (`lib/smalltalk/tests/super.sx`). `st-super-send` walks from defining-class's superclass; class-side aware; primitives → DNU fallback. Also fixed top-level `| temps |` parsing in `st-parse` (the absence of which was silently aborting earlier eval/dnu tests — counts go from 274 → 287, with previously-skipped tests now actually running).
|
||||
- 2026-04-25: `doesNotUnderstand:` + 12 DNU tests (`lib/smalltalk/tests/dnu.sx`). Bootstrap installs `Message` (with selector/arguments accessors). Primitives signal `:unhandled` instead of erroring; `st-dnu` builds a Message and walks `doesNotUnderstand:` lookup. User Object DNU intercepts unknown sends to native receivers (Number, String, Block) too. 267/267 total.
|
||||
|
||||
Reference in New Issue
Block a user