smalltalk: mandelbrot + literal-array mutability fix
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:
@@ -150,9 +150,15 @@
|
||||
((= ty "lit-true") true)
|
||||
((= ty "lit-false") false)
|
||||
((= ty "lit-array")
|
||||
(map
|
||||
(fn (e) (smalltalk-eval-ast e frame))
|
||||
(get ast :elements)))
|
||||
;; map returns an immutable list — Smalltalk arrays must be
|
||||
;; mutable so that `at:put:` works. Build via append! so each
|
||||
;; literal yields a fresh mutable list.
|
||||
(let ((out (list)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn (e) (append! out (smalltalk-eval-ast e frame)))
|
||||
(get ast :elements))
|
||||
out)))
|
||||
((= ty "lit-byte-array") (get ast :elements))
|
||||
((= ty "self") (get frame :self))
|
||||
((= ty "super") (get frame :self))
|
||||
|
||||
@@ -219,4 +219,80 @@
|
||||
^ arr")
|
||||
(list 1 2 3 4))
|
||||
|
||||
;; ── mandelbrot.st ────────────────────────────────────────────────────
|
||||
(define
|
||||
mandel-source
|
||||
"Object subclass: #Mandelbrot
|
||||
instanceVariableNames: ''!
|
||||
|
||||
!Mandelbrot methodsFor: 'iteration'!
|
||||
escapeAt: cx and: cy maxIter: maxIter
|
||||
| zx zy zx2 zy2 i |
|
||||
zx := 0. zy := 0.
|
||||
zx2 := 0. zy2 := 0.
|
||||
i := 0.
|
||||
[(zx2 + zy2 < 4) and: [i < maxIter]] whileTrue: [
|
||||
zy := (zx * zy * 2) + cy.
|
||||
zx := zx2 - zy2 + cx.
|
||||
zx2 := zx * zx.
|
||||
zy2 := zy * zy.
|
||||
i := i + 1].
|
||||
^ i!
|
||||
|
||||
inside: cx and: cy maxIter: maxIter
|
||||
^ (self escapeAt: cx and: cy maxIter: maxIter) >= maxIter! !
|
||||
|
||||
!Mandelbrot methodsFor: 'grid'!
|
||||
countInsideRangeX: x0 to: x1 stepX: dx rangeY: y0 to: y1 stepY: dy maxIter: maxIter
|
||||
| x y count |
|
||||
count := 0.
|
||||
y := y0.
|
||||
[y <= y1] whileTrue: [
|
||||
x := x0.
|
||||
[x <= x1] whileTrue: [
|
||||
(self inside: x and: y maxIter: maxIter) ifTrue: [count := count + 1].
|
||||
x := x + dx].
|
||||
y := y + dy].
|
||||
^ count! !")
|
||||
|
||||
(smalltalk-load mandel-source)
|
||||
|
||||
(st-test "Mandelbrot class registered" (st-class-exists? "Mandelbrot") true)
|
||||
|
||||
;; The origin is the cusp of the cardioid — z stays at 0 forever.
|
||||
(st-test "origin is in the set"
|
||||
(evp "^ Mandelbrot new inside: 0 and: 0 maxIter: 50") true)
|
||||
|
||||
;; (-1, 0) — z₀=0, z₁=-1, z₂=0, … oscillates and stays bounded.
|
||||
(st-test "(-1, 0) is in the set"
|
||||
(evp "^ Mandelbrot new inside: -1 and: 0 maxIter: 50") true)
|
||||
|
||||
;; (1, 0) — escapes after 2 iterations: 0 → 1 → 2, |z|² = 4 ≥ 4.
|
||||
(st-test "(1, 0) escapes quickly"
|
||||
(evp "^ Mandelbrot new escapeAt: 1 and: 0 maxIter: 50") 2)
|
||||
|
||||
;; (2, 0) — escapes immediately: 0 → 2, |z|² = 4 ≥ 4 already.
|
||||
(st-test "(2, 0) escapes after 1 step"
|
||||
(evp "^ Mandelbrot new escapeAt: 2 and: 0 maxIter: 50") 1)
|
||||
|
||||
;; (-2, 0) — z₀=0; iter 1: z₁=-2, |z|²=4, condition `< 4` fails → exits at i=1.
|
||||
(st-test "(-2, 0) escapes after 1 step"
|
||||
(evp "^ Mandelbrot new escapeAt: -2 and: 0 maxIter: 50") 1)
|
||||
|
||||
;; (10, 10) — far outside, escapes on the first step.
|
||||
(st-test "(10, 10) escapes after 1 step"
|
||||
(evp "^ Mandelbrot new escapeAt: 10 and: 10 maxIter: 50") 1)
|
||||
|
||||
;; Coarse 5x5 grid (-2..2 in 1-step increments, no half-steps to keep
|
||||
;; this fast). Membership of (-1,0), (0,0), (-1,-1)? We expect just
|
||||
;; (0,0) and (-1,0) at maxIter 30.
|
||||
;; Actually let's count exact membership at this resolution.
|
||||
(st-test "tiny 3x3 grid count"
|
||||
(evp
|
||||
"^ Mandelbrot new countInsideRangeX: -1 to: 1 stepX: 1
|
||||
rangeY: -1 to: 1 stepY: 1
|
||||
maxIter: 30")
|
||||
;; In-set points (bounded after 30 iters): (0,-1) (-1,0) (0,0) (0,1) → 4.
|
||||
4)
|
||||
|
||||
(list st-test-pass st-test-fail)
|
||||
|
||||
36
lib/smalltalk/tests/programs/mandelbrot.st
Normal file
36
lib/smalltalk/tests/programs/mandelbrot.st
Normal file
@@ -0,0 +1,36 @@
|
||||
"Mandelbrot — escape-time iteration of z := z² + c starting at z₀ = 0.
|
||||
Returns the number of iterations before |z|² exceeds 4, capped at
|
||||
maxIter. Classic-corpus program for the Smalltalk-on-SX runtime."
|
||||
|
||||
Object subclass: #Mandelbrot
|
||||
instanceVariableNames: ''!
|
||||
|
||||
!Mandelbrot methodsFor: 'iteration'!
|
||||
escapeAt: cx and: cy maxIter: maxIter
|
||||
| zx zy zx2 zy2 i |
|
||||
zx := 0. zy := 0.
|
||||
zx2 := 0. zy2 := 0.
|
||||
i := 0.
|
||||
[(zx2 + zy2 < 4) and: [i < maxIter]] whileTrue: [
|
||||
zy := (zx * zy * 2) + cy.
|
||||
zx := zx2 - zy2 + cx.
|
||||
zx2 := zx * zx.
|
||||
zy2 := zy * zy.
|
||||
i := i + 1].
|
||||
^ i!
|
||||
|
||||
inside: cx and: cy maxIter: maxIter
|
||||
^ (self escapeAt: cx and: cy maxIter: maxIter) >= maxIter! !
|
||||
|
||||
!Mandelbrot methodsFor: 'grid'!
|
||||
countInsideRangeX: x0 to: x1 stepX: dx rangeY: y0 to: y1 stepY: dy maxIter: maxIter
|
||||
| x y count |
|
||||
count := 0.
|
||||
y := y0.
|
||||
[y <= y1] whileTrue: [
|
||||
x := x0.
|
||||
[x <= x1] whileTrue: [
|
||||
(self inside: x and: y maxIter: maxIter) ifTrue: [count := count + 1].
|
||||
x := x + dx].
|
||||
y := y + dy].
|
||||
^ count! !
|
||||
@@ -73,7 +73,7 @@ Core mapping:
|
||||
- [ ] Classic programs in `lib/smalltalk/tests/programs/`:
|
||||
- [x] `eight-queens.st` — backtracking N-queens search in `lib/smalltalk/tests/programs/eight-queens.st`. The `.st` source supports any board size; tests verify 1, 4, 5 queens (1, 2, 10 solutions respectively). 6+ queens are correct but too slow on the spec interpreter (call/cc + dict-based ivars per send) — they'll come back inside the test runner once the JIT lands. The 8-queens canonical case will run in production.
|
||||
- [x] `quicksort.st` — Lomuto-partition in-place quicksort in `lib/smalltalk/tests/programs/quicksort.st`. Verified by 9 tests: small/duplicates/sorted/reverse-sorted/single/empty/negatives/all-equal/in-place-mutation. Exercises Array `at:`/`at:put:` mutation, recursion, `to:do:` over varying ranges.
|
||||
- [ ] `mandelbrot.st`
|
||||
- [x] `mandelbrot.st` — escape-time iteration of `z := z² + c` in `lib/smalltalk/tests/programs/mandelbrot.st`. Verified by 7 tests: known in-set points (origin, (-1,0)), known escapers ((1,0)→2, (-2,0)→1, (10,10)→1, (2,0)→1), and a 3x3 grid count. Caught a real bug along the way: literal `#(...)` arrays were evaluated via `map` (immutable), making `at:put:` raise; switched to `append!` so each literal yields a fresh mutable list — quicksort tests now actually mutate as intended.
|
||||
- [ ] `life.st` (Conway's Life, glider gun)
|
||||
- [x] `fibonacci.st` (recursive + Array-memoised) — `lib/smalltalk/tests/programs/fibonacci.st`. Loaded from chunk-format source by new `smalltalk-load` helper; verified by 13 tests in `lib/smalltalk/tests/programs.sx` (recursive `fib:`, memoised `memoFib:` up to 30, instance independence, class-table integrity). Source is currently duplicated as a string in the SX test file because there's no SX file-read primitive; conformance.sh will dedupe by piping the .st file directly.
|
||||
- [ ] `lib/smalltalk/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md`
|
||||
@@ -108,6 +108,7 @@ Core mapping:
|
||||
|
||||
_Newest first. Agent appends on every commit._
|
||||
|
||||
- 2026-04-25: classic-corpus #4 mandelbrot (`tests/programs/mandelbrot.st`, 7 tests). Escape-time iterator + grid counter. Discovered + fixed an immutable-list bug in `lit-array` eval — `map` produced an immutable list so `at:put:` raised; rebuilt via `append!`. Quicksort tests had been silently dropping ~7 cases due to that bug; now actually mutate. 399/399 total.
|
||||
- 2026-04-25: classic-corpus #3 quicksort (`tests/programs/quicksort.st`, 9 tests). Lomuto partition; verified across duplicates, already-sorted/reverse-sorted, empty, single, negatives, all-equal, plus in-place mutation. 385/385 total.
|
||||
- 2026-04-25: classic-corpus #2 eight-queens (`tests/programs/eight-queens.st`, 5 tests). Backtracking search; verified for boards of size 1, 4, 5. Larger boards are correct but too slow on the spec interpreter without JIT — `(EightQueens new size: 6) solve` is ~38s, 8-queens minutes. 382/382 total.
|
||||
- 2026-04-25: classic-corpus #1 fibonacci (`tests/programs/fibonacci.st` + `tests/programs.sx`, 13 tests). Added `smalltalk-load` chunk loader, class-side `subclass:instanceVariableNames:` (and longer Pharo variants), `Array new:` size, `methodsFor:`/`category:` no-ops, `st-split-ivars`. 377/377 total.
|
||||
|
||||
Reference in New Issue
Block a user