From 5a1b9989b618976cb4881446bb1977361e8400d0 Mon Sep 17 00:00:00 2001 From: Kolt McBride Date: Sat, 12 Jul 2025 23:58:10 -0700 Subject: [PATCH] Add experimental quicksort example --- README.md | 24 ++++++++++ docs/INSTRUCTION_SET.md | 98 +++++++++++++++++++++++++++++++++++++++++ quicksort.fra | 32 ++++++++++++++ sort.fra | 1 - 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 docs/INSTRUCTION_SET.md create mode 100644 quicksort.fra diff --git a/README.md b/README.md index fdcc67d..3b735a9 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,27 @@ python3 benchmark_sort.py The script generates a random list of 100 numbers and times the sorting process using 1, 2, 4 and 8 threads. Each run executes roughly 200k iterations of the engine to ensure the list is fully sorted, and the elapsed time is printed. + +### Experimental QuickSort + +The repository also includes an experimental quicksort program written in +Fraglets. The rules are defined in `quicksort.fra` and illustrate how to +implement a recursive algorithm using the instruction set. You can run it +similar to the selection sort example by parsing the file and executing the +engine: + +```bash +python3 - <<'EOF' +import fraglets +f = fraglets.fraglets() +for line in open('quicksort.fra'): + line = line.strip() + if line and not line.startswith('#'): + f.parse(line) +f.run(20000, 10000, True) +print('sorted:', f.get_sorted()) +EOF +``` + +This implementation is not fully optimised but demonstrates a different +approach to sorting with fraglets. diff --git a/docs/INSTRUCTION_SET.md b/docs/INSTRUCTION_SET.md new file mode 100644 index 0000000..ab3ecd7 --- /dev/null +++ b/docs/INSTRUCTION_SET.md @@ -0,0 +1,98 @@ +# Fraglets Instruction Summary + +This document summarizes the instruction set used by the Fraglets engine. It is derived from the `frag-instrset-20070924.txt` reference and the example programs included in this repository. + +Fraglets are lists of tokens. The first token of a list acts as the instruction tag. Rules consume the instruction tag and transform the remaining tokens. When two fraglets "match," the rule bodies can combine or generate new fraglets. + +## 1. Core Instructions + +| Instruction | Effect | +|-------------|--------| +| `dup` | `[dup t a tail]` → `[t a a tail]` – duplicate a single symbol | +| `exch` | `[exch t a b tail]` → `[t b a tail]` – swap two tags | +| `fork` | `[fork a b tail]` → `[a tail]`, `[b tail]` – copy a fraglet with two different prefixes | +| `match` | `[match a tail1]` and `[a tail2]` → `[tail1 tail2]` – concatenate fraglets when their first symbols match | +| `matchp` | `[matchp a tail1]` and `[a tail2]` → `[matchp a tail1]`, `[tail1 tail2]` – catalytic match; the rule persists | +| `nop` | `[nop tail]` → `[tail]` – no operation | +| `nul` | `[nul tail]` → `[]` – destroy a fraglet | +| `pop2` | `[pop2 h t a b tail]` → `[h a]`, `[t b tail]` – pop the head of a list into a separate fraglet | +| `split` | `[split seq1 * seq2]` → `[seq1]`, `[seq2]` – break a fraglet at the first `*` | + +## 2. Communication and Timing + +| Instruction | Effect | +|-------------|--------| +| `broadcast` | `[broadcast seg tail]` → `n[tail]` – copy `tail` to all neighbours on segment `seg` | +| `delay` | `[delay n tail]` → `[tail]` after waiting `n` cycles | +| `send` | `[send seg dest tail]` → `dest[tail]` – send to a node on segment `seg`; special `stdout` and `stderr` destinations print `tail` | + +## 3. Logic and Arithmetic + +| Instruction | Effect | +|-------------|--------| +| `abs` | `[abs tag n tail]` → `[tag |n| tail]` | +| `div` | `[div tag n1 n2 tail]` → `[tag n1/n2 tail]`; removed if `n2` is zero | +| `empty` | `[empty yes no tail]` → `[yes]` if `tail` is empty, else `[no tail]` | +| `eq` | `[eq yes no n m tail]` → `[yes n m tail]` if equal, else `[no n m tail]` | +| `length` | `[length t1 tail]` → `[t1 |tail| tail]` | +| `lt` | `[lt yes no n m tail]` → `[yes n m tail]` if `n < m`, else `[no n m tail]` | +| `mod` | `[mod tag n1 n2 tail]` → `[tag (n1 % n2) tail]` | +| `mult` | `[mult tag n1 n2 tail]` → `[tag (n1 * n2) tail]` | +| `pow` | `[pow tag n1 n2 tail]` → `[tag (n1 ^ n2) tail]` | +| `sub` | `[sub tag n1 n2 tail]` → `[tag (n1 - n2) tail]` | +| `sum` | `[sum tag n1 n2 tail]` → `[tag (n1 + n2) tail]` | + +## 4. Reserved Keywords + +`*` – used by `split` to mark the separation point +`stdout`, `stderr` – pseudo destinations for `send` +`stdin`, `out`, `_`, `__` – reserved for future extensions + +## 5. Experimental Extensions + +| Instruction | Effect | +|-------------|--------| +| `anycast` | `[anycast seg tail]` → `n[tail]` – copy to at most one neighbour | +| `copy` | `[copy tail]` → `[tail]2` | +| `pop` | `[pop h a b c]` → `[h b c]` (proposed rename to `tail`) | +| `printsym` | `[printsym sym tail]` → `[tail]` while printing `sym` | +| `wait` | `[wait a b c]` → `[a b c]` after 10 cycles; prefer `delay` | +| `splitat` | `[splitat | a b c | x y z]` → `[a b c]`, `[x y z]` – split at a marker | +| `newnode` | `m[newnode ch n tail]` → `a n ch`, `n[tail]` – create child node | +| `inject` | `n1[inject n2 tail]` → `n2[tail]` (child) | +| `expel` | `n2[expel tail]` → `n1[tail]` (parent) | +| `newname` | `[newname tag s1 s2 tail]` → `[tag s1s2 tail]` | + +Deprecated instruction: `diff` (use `sub` and `abs` instead). + +## Example – Selection Sort + +The repository includes `sort.fra`, which implements a selection sort using these instructions. Key rules: + +```fraglets +[matchp sort empty finish continue] +[matchp continue split remain * getmin] +[matchp min split match remain sort * split match sorted match tosorted sorted * tosorted] +``` + +The `getmin` function repeatedly uses `matchp`, `lt`, and `pop2` to find the smallest number and store the rest in a `remain` list. + +To run the example via Python: + +```bash +python3 -m pip install . # build the cFraglets module +python3 test_sort.py +``` + +This parses `sort.fra`, executes the fraglet engine for a fixed number of iterations, and verifies that the resulting list is sorted. + +Refer to `README.md` for build instructions and `test_sort.py` for a complete usage example. + +## Experimental QuickSort + +`quicksort.fra` demonstrates how the same primitives can implement a recursive +quicksort. The program uses persistent rules to split the list around a pivot, +recursively sort the `less` and `greater` partitions and finally merge the +results. It serves as a more advanced example of fraglet programming but has not +been extensively tuned. + diff --git a/quicksort.fra b/quicksort.fra new file mode 100644 index 0000000..ce3758d --- /dev/null +++ b/quicksort.fra @@ -0,0 +1,32 @@ +# experimental quicksort implementation +# [qsort ] -> [sorted ] + +[sorted] + +# Entry rule: if the list is empty we're done, otherwise continue +[matchp qsort empty qs_finish qs_partition] + +# Split first element as pivot and the rest of the list +[matchp qs_partition pop2 pivot qs_part] + +# Partition rest into [less] and [greater] lists +[matchp qs_part empty qs_recur qs_compare] +[matchp qs_compare pop2 cur qs_part2] +[matchp qs_part2 lt qs_lt qs_ge pivot cur] +[matchp qs_lt match less less] +[matchp qs_ge match greater greater] + +# After processing current element, continue partitioning +[matchp qs_lt match qs_part qs_compare] +[matchp qs_ge match qs_part qs_compare] + +# When done partitioning, recursively sort less and greater +[matchp qs_recur fork less_sort greater_sort] +[matchp less_sort match less qsort] +[matchp greater_sort match greater qsort] + +# Merge results: sorted less list, pivot, sorted greater list +[matchp qs_finish split match less sorted * split match greater sorted * match pivot match sorted match tosorted sorted * tosorted] + +# example invocation +[qsort 5 1 4 2 3] diff --git a/sort.fra b/sort.fra index e5b8256..7ac0cc2 100644 --- a/sort.fra +++ b/sort.fra @@ -36,4 +36,3 @@ [sort 203 -200 989 -446 -927 962 -485 714 -351 226 -791 55 448 -32 -477 261 529 38 922 -419 822 -395 -757 -951 757 -521 88] -# [pop2 pop2 pop2] \ No newline at end of file