Skip to content

Instantly share code, notes, and snippets.

@postspectacular
Created March 16, 2025 13:10
Show Gist options
  • Save postspectacular/7d0ae7d90e0eafb69e60fb97e2838221 to your computer and use it in GitHub Desktop.
Save postspectacular/7d0ae7d90e0eafb69e60fb97e2838221 to your computer and use it in GitHub Desktop.
L-System based FizzBuzz implementations (using https://thi.ng/lsys)
import type { Fn2 } from "@thi.ng/api";
import {
DEFAULT,
expand,
interpret,
type LSysSymbol,
type RuleImplementations,
} from "@thi.ng/lsys";
// iterative rule expansion
const fizzbuzz = expand(
{
start: ["_1"],
_1: ["1", "_2"],
_2: ["2", "_3"],
_3: ["fizz", "_4"],
_4: ["4", "_5"],
_5: ["buzz", "_6"],
_6: ["fizz", "_7"],
_7: ["7", "_8"],
_8: ["8", "_9"],
_9: ["fizz", "_10"],
_10: ["buzz", "_11"],
_11: ["11", "_12"],
_12: ["fizz", "_13"],
_13: ["13", "_14"],
_14: ["14", "_15"],
_15: ["fizz buzz", "end"],
},
"start",
100
);
console.log([...fizzbuzz].join(" "));
// 1 2 fizz 4 buzz fizz 7 8 fizz 10 11 fizz 13 14 fizz buzz end
// alternative approach:
// attaching a L-System iterpreter implementing a simple stack machine VM
// for Forth-like execution (incl. quotations)
// VM state is a 2-tuple of `[current stack, scopes]`
type VmCtx = [any[], any[]];
// VM operators (i.e. mapping L-System symbols to actions)
const VM: RuleImplementations<VmCtx> = {
// any unmatches symbol will be pushed on stack
[DEFAULT]: ([stack]: any[], x: any) => stack.push(x),
// quoation handling
"[": (ctx) => (ctx[1].push(ctx[0]), (ctx[0] = [])),
"]": (ctx) => {
const parent = ctx[1].pop();
parent.push(ctx[0]);
ctx[0] = parent;
},
};
// all other VM ops are deferrable (i.e. will only execute in the root scope,
// otherwise the symbol is quoted and just pushed on current stack...)
const DEFERED_OPS: RuleImplementations<VmCtx> = {
// print TOS: ( x -- )
".": (ctx) => console.log(ctx[0].pop()!),
// 2-branch conditional: ( test truthy falsy -- )
ifelse: (ctx) => {
const [falsy, truthy] = [ctx[0].pop(), ctx[0].pop()];
interpret(ctx, VM, ctx[0].pop() ? truthy : falsy);
},
// duplicate TOS: ( x -- x x )
dup: ([stack]) => stack.push(stack[stack.length - 1]),
// remove TOS: ( x -- )
drop: ([stack]) => stack.pop(),
// swap two topmost values: ( x y -- y x )
swap: ([stack]) => stack.splice(stack.length - 2, 0, stack.pop()),
// compute modulo: ( x y -- x%y )
mod: ([stack]) => {
const x = stack.pop()!;
stack.push(stack.pop()! % x);
},
// concatenate strings: ( x y -- x+y )
concat: ([stack]) => {
const [b, a] = [stack.pop(), stack.pop()];
stack.push(a + b);
},
};
// helper function to allow op be quotable
const defer =
(id: string, fn: Fn2<VmCtx, LSysSymbol, void>) =>
(ctx: VmCtx, sym: LSysSymbol) =>
!ctx[1].length ? fn(ctx, sym) : ctx[0].push(id);
// inject all deferred ops
for (let [id, op] of Object.entries(DEFERED_OPS)) {
VM[id] = defer(id, op);
}
// L-System ruleset (aka source code) for VM
const fizzbuzzVM = [
...expand(
{
start: ["_1"],
empty: ["[", "", "]"],
_fizz: ["[", "fizz", "]"],
_buzz: ["[", "buzz", "]"],
label: ["[", ".", "drop", "]"],
counter: ["[", "drop", ".", "]"],
"fizz?": [3, "mod", "empty", "_fizz", "ifelse"],
"buzz?": [5, "mod", "empty", "_buzz", "ifelse"],
fizzbuzz: [
"dup",
"dup",
"fizz?",
"swap",
"buzz?",
"concat",
"dup",
"label",
"counter",
"ifelse",
],
_1: [1, "fizzbuzz", "_2"],
_2: [2, "fizzbuzz", "_3"],
_3: [3, "fizzbuzz", "_4"],
_4: [4, "fizzbuzz", "_5"],
_5: [5, "fizzbuzz", "_6"],
_6: [6, "fizzbuzz", "_7"],
_7: [7, "fizzbuzz", "_8"],
_8: [8, "fizzbuzz", "_9"],
_9: [9, "fizzbuzz", "_10"],
_10: [10, "fizzbuzz", "_11"],
_11: [11, "fizzbuzz", "_12"],
_12: [12, "fizzbuzz", "_13"],
_13: [13, "fizzbuzz", "_14"],
_14: [14, "fizzbuzz", "_15"],
_15: [15, "fizzbuzz", "end"],
},
"start",
100
),
];
// output expanded rules
console.log(fizzbuzzVM.join(" "));
// 1 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 2 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 3 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 4 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 5 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 6 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 7 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 8 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 9 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 10 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 11 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 12 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 13 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 14 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse 15 dup dup 3 mod [ ] [ fizz ] ifelse swap 5 mod [ ] [ buzz ] ifelse concat dup [ . drop ] [ drop . ] ifelse end
// execute
interpret([[], []], VM, fizzbuzzVM);
// Output:
// 1
// 2
// fizz
// 4
// buzz
// fizz
// 7
// 8
// fizz
// buzz
// 11
// fizz
// 13
// 14
// fizzbuzz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment