diff --git a/lib/smalltalk/tests/programs.sx b/lib/smalltalk/tests/programs.sx index dbffa658..1236350e 100644 --- a/lib/smalltalk/tests/programs.sx +++ b/lib/smalltalk/tests/programs.sx @@ -142,4 +142,81 @@ (st-test "EightQueens init sets size 8" (evp "^ EightQueens new init size") 8) +;; ── quicksort.st ───────────────────────────────────────────────────── +(define + quicksort-source + "Object subclass: #Quicksort + instanceVariableNames: ''! + + !Quicksort methodsFor: 'sort'! + sort: arr ^ self sort: arr from: 1 to: arr size! + + sort: arr from: low to: high + | p | + low < high ifTrue: [ + p := self partition: arr from: low to: high. + self sort: arr from: low to: p - 1. + self sort: arr from: p + 1 to: high]. + ^ arr! + + partition: arr from: low to: high + | pivot i tmp | + pivot := arr at: high. + i := low - 1. + low to: high - 1 do: [:j | + (arr at: j) <= pivot ifTrue: [ + i := i + 1. + tmp := arr at: i. + arr at: i put: (arr at: j). + arr at: j put: tmp]]. + tmp := arr at: i + 1. + arr at: i + 1 put: (arr at: high). + arr at: high put: tmp. + ^ i + 1! !") + +(smalltalk-load quicksort-source) + +(st-test "Quicksort class registered" (st-class-exists? "Quicksort") true) + +(st-test "qsort small array" + (evp "^ Quicksort new sort: #(3 1 2)") + (list 1 2 3)) + +(st-test "qsort with duplicates" + (evp "^ Quicksort new sort: #(3 1 4 1 5 9 2 6 5 3 5)") + (list 1 1 2 3 3 4 5 5 5 6 9)) + +(st-test "qsort already-sorted" + (evp "^ Quicksort new sort: #(1 2 3 4 5)") + (list 1 2 3 4 5)) + +(st-test "qsort reverse-sorted" + (evp "^ Quicksort new sort: #(9 7 5 3 1)") + (list 1 3 5 7 9)) + +(st-test "qsort single element" + (evp "^ Quicksort new sort: #(42)") + (list 42)) + +(st-test "qsort empty" + (evp "^ Quicksort new sort: #()") + (list)) + +(st-test "qsort negatives" + (evp "^ Quicksort new sort: #(-3 -1 -7 0 2)") + (list -7 -3 -1 0 2)) + +(st-test "qsort all-equal" + (evp "^ Quicksort new sort: #(5 5 5 5)") + (list 5 5 5 5)) + +(st-test "qsort sorts in place (returns same array)" + (evp + "| arr q | + arr := #(4 2 1 3). + q := Quicksort new. + q sort: arr. + ^ arr") + (list 1 2 3 4)) + (list st-test-pass st-test-fail) diff --git a/lib/smalltalk/tests/programs/quicksort.st b/lib/smalltalk/tests/programs/quicksort.st new file mode 100644 index 00000000..f1d8a43e --- /dev/null +++ b/lib/smalltalk/tests/programs/quicksort.st @@ -0,0 +1,31 @@ +"Quicksort — Lomuto partition. Sorts an Array in place. Classic-corpus + program for the Smalltalk-on-SX runtime." + +Object subclass: #Quicksort + instanceVariableNames: ''! + +!Quicksort methodsFor: 'sort'! +sort: arr ^ self sort: arr from: 1 to: arr size! + +sort: arr from: low to: high + | p | + low < high ifTrue: [ + p := self partition: arr from: low to: high. + self sort: arr from: low to: p - 1. + self sort: arr from: p + 1 to: high]. + ^ arr! + +partition: arr from: low to: high + | pivot i tmp | + pivot := arr at: high. + i := low - 1. + low to: high - 1 do: [:j | + (arr at: j) <= pivot ifTrue: [ + i := i + 1. + tmp := arr at: i. + arr at: i put: (arr at: j). + arr at: j put: tmp]]. + tmp := arr at: i + 1. + arr at: i + 1 put: (arr at: high). + arr at: high put: tmp. + ^ i + 1! ! diff --git a/plans/smalltalk-on-sx.md b/plans/smalltalk-on-sx.md index 87127ea1..cda223c5 100644 --- a/plans/smalltalk-on-sx.md +++ b/plans/smalltalk-on-sx.md @@ -72,7 +72,7 @@ Core mapping: - [x] Escape past returned-from method raises (the SX-level analogue of `BlockContext>>cannotReturn:`). Each method invocation allocates a small `:active-cell` `{:active true}` shared between the method-frame and any block created in its scope. `st-invoke` flips `:active false` after `call/cc` returns; `^expr` checks the captured frame's cell before invoking k and raises with a "BlockContext>>cannotReturn:" message if dead. Verified by `lib/smalltalk/tests/cannot_return.sx` (5 tests using SX `guard` to catch the raise). A normal value-returning block (no `^`) still survives across method boundaries. - [ ] 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. - - [ ] `quicksort.st` + - [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` - [ ] `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. @@ -108,6 +108,7 @@ Core mapping: _Newest first. Agent appends on every commit._ +- 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. - 2026-04-25: cannotReturn: implemented (`lib/smalltalk/tests/cannot_return.sx`, 5 tests). Each method-invocation gets an `{:active true}` cell shared with its blocks; `st-invoke` flips it on exit; `^expr` raises if the cell is dead. Tests use SX `guard` to catch the raise. Non-`^` blocks unaffected. 364/364 total.