Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions docs/agents/vm.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# CLEAR VM & Gradual Typing (Scheme Backend)
# CLEAR VM & Gradual Typing (Historical Scheme Backend)

Historical note: this document describes the older Mal/S-expression VM design.
That interpreter has been restored under `examples/mal`. The active MiniVM work
lives under `examples/minivm` and uses bytecode/register-machine paths.

## Overview

CLEAR's primary target is high-performance Zig/Native code. However, for rapid prototyping, scripting, and environments where a full compilation step is undesirable, CLEAR supports a **VM Mode**. This mode lowers CLEAR source to S-expression Scheme and executes it on a specialized interpreter written in CLEAR itself (`examples/scheme/interpreter.cht`).
CLEAR's primary target is high-performance Zig/Native code. However, for rapid prototyping, scripting, and environments where a full compilation step is undesirable, CLEAR explored a **VM Mode**. This historical mode lowered CLEAR source to S-expression Scheme and executed it on a specialized interpreter written in CLEAR itself (`examples/mal/interpreter.cht`).

## Why This Matters

Expand Down Expand Up @@ -65,7 +69,7 @@ The GC vs. arena gap is an implementation detail invisible to the programmer.

## Current State: The Interpreter

`examples/scheme/interpreter.cht` is a Mal Level 4 implementation (~489 lines) written in CLEAR. It currently supports:
`examples/mal/interpreter.cht` is a Mal Level 4 implementation written in CLEAR. It supports:

- Lexer/parser for S-expressions
- `def!`, `let*`, `fn*`, `do`, `if`
Expand Down Expand Up @@ -230,7 +234,7 @@ No `CALL_CC`. No `EVAL`. No `MACRO_EXPAND`. The opcode set is closed because the
**None.** All required compiler features are already implemented. The interpreter should compile and run its 21 tests today. Verify with:

```bash
./clear test examples/scheme/interpreter.cht
./clear test examples/mal/interpreter.cht
```

### Phase 1: Interpreter Maturation (~15-25 commits)
Expand Down
32 changes: 32 additions & 0 deletions examples/brnfk/brnfk-corpus-tests.cht
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
REQUIRE "brnfk.cht";

FN testPath(name: String) RETURNS !String ->
RETURN "../../examples/brnfk/tests/" + name;
END

FN expectedSquares() RETURNS !String ->
MUTABLE lines: String[]@list = [];
MUTABLE i = 0;
WHILE i <= 100 DO
lines.append((i * i).toString());
lines.append("\n");
i += 1;
END
RETURN lines.join("");
END

FN main() RETURNS Void ->
ASSERT runFile(testPath("null.b") OR RAISE, "") == "", "null.b produces no output";
ASSERT runFile(testPath("rot13.b") OR RAISE, "~mlk zyx") == "~zyx mlk", "rot13.b transforms known input";
ASSERT runFile(testPath("xmastree.b") OR RAISE, "") == "*\n", "xmastree.b smoke output";
ASSERT runFile(testPath("squares.b") OR RAISE, "") == expectedSquares(), "squares.b prints 0..100 squared";

ioProbe = ">,>+++++++++,>+++++++++++[<++++++<++++++<+>>>-]<<.>.<<-.>.>.<<.";
ASSERT runWithLimits(ioProbe, "\n", 30_000, 100_000) == "LK\nLK\n", "Cristofani newline/EOF probe";

obscureProbe = "[]++++++++++[>>+>+>++++++[<<+<+++>>>-]<<<<-]" +
"\"A*$\";?@![#>>+<<]>[>>]<<<<[>++<[-]]>.>.";
ASSERT runWithLimits(obscureProbe, "", 30_000, 1_000_000) == "H\n", "Cristofani obscure parser probe";

RETURN;
END
52 changes: 52 additions & 0 deletions examples/brnfk/brnfk-tests.cht
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
REQUIRE "brnfk.cht";

FN programErrors(program: String) RETURNS Bool ->
runWithLimits(program, "", 8, 1000) OR RAISE;
RETURN FALSE;

CATCH Input
RETURN TRUE;

DEFAULT
RETURN TRUE;
END

FN programErrorsWithTape(program: String, tapeSize: Int64) RETURNS Bool ->
runWithLimits(program, "", tapeSize, 1000) OR RAISE;
RETURN FALSE;

CATCH Input
RETURN TRUE;

DEFAULT
RETURN TRUE;
END

FN main() RETURNS Void ->
ASSERT incByte(255) == 0, "+ wraps 255 to 0";
ASSERT decByte(0) == 255, "- wraps 0 to 255";
ASSERT byteToString(65) == "A", "byteToString renders printable ASCII";
ASSERT stringToByte("A") == 65, "stringToByte reads printable ASCII";
ASSERT stringToByte("\n") == 10, "stringToByte reads newline";

ASSERT commands("a+b>c[.-],") == "+>[.-],", "parser ignores comments";

ASSERT run("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.", "") == "A", "prints A";
ASSERT run(",.", "Z") == "Z", "echoes one byte";
ASSERT run("+++++++++++++++++++++++++++++++++,. ", "") == "!", "EOF leaves cell unchanged";
ASSERT run("+++++[>+++++++++++++<-]>.", "") == "A", "loop multiplies into next cell";

hello = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++." +
"<<+++++++++++++++.>.+++.------.--------.>+.>.";
ASSERT run(hello, "") == "Hello World!\n", "hello world program";

ASSERT runWithLimits("++>+++<", "", 8, 1000) == "", "limited runner returns output";

ASSERT programErrors("[") == TRUE, "unmatched [ errors";
ASSERT programErrors("]") == TRUE, "unmatched ] errors";
ASSERT programErrors("<") == TRUE, "pointer underflow errors";
ASSERT programErrorsWithTape(">>", 2) == TRUE, "pointer overflow errors";
ASSERT programErrorsWithTape("+[]", 8) == TRUE, "step limit errors";

RETURN;
END
178 changes: 178 additions & 0 deletions examples/brnfk/brnfk.cht
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
FN printableAscii() RETURNS String ->
RETURN " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
END

FN incByte(n: Int64) RETURNS Int64 ->
IF n >= 255 THEN
RETURN 0;
END
RETURN n + 1;
END

FN decByte(n: Int64) RETURNS Int64 ->
IF n <= 0 THEN
RETURN 255;
END
RETURN n - 1;
END

FN byteToString(n: Int64) RETURNS !String ->
IF n == 10 THEN
RETURN "\n";
END
IF n >= 32 && n <= 126 THEN
RETURN "" + printableAscii().charAt(n - 32);
END
RAISE "brnfk cannot render non-printable byte ${n.toString()} as String";
END

FN stringToByte(ch: String) RETURNS !Int64 ->
IF ch == "\n" THEN
RETURN 10;
END

ascii = printableAscii();
MUTABLE i = 0;
WHILE i < ascii.length() DO
IF ascii.charAt(i) == ch THEN
RETURN i + 32;
END
i += 1;
END

RAISE "brnfk input contains unsupported non-printable character";
END

FN isCommand(ch: String) RETURNS Bool ->
RETURN ch == ">" || ch == "<" || ch == "+" || ch == "-" ||
ch == "." || ch == "," || ch == "[" || ch == "]";
END

FN commands(program: String) RETURNS !String ->
MUTABLE parts: String[]@list = [];
MUTABLE i = 0;
WHILE i < program.length() DO
ch = program.charAt(i);
IF isCommand(ch) THEN
parts.append(ch);
END
i += 1;
END
RETURN parts.join("");
END

FN makeTape(size: Int64) RETURNS !Int64[]@list ->
IF size <= 0 THEN
RAISE "brnfk tape size must be positive";
END

MUTABLE tape: Int64[]@list = [];
tape.reserve(size);
MUTABLE i = 0;
WHILE i < size DO
tape.append(0);
i += 1;
END
RETURN tape;
END

FN makeJumpTable(program: String) RETURNS !Int64[]@list ->
MUTABLE jumps: Int64[]@list = [];
jumps.reserve(program.length());
MUTABLE i = 0;
WHILE i < program.length() DO
jumps.append(-1);
i += 1;
END

MUTABLE stack: Int64[]@list = [];
i = 0;
WHILE i < program.length() DO
ch = program.charAt(i);
IF ch == "[" THEN
stack.append(i);
ELSE_IF ch == "]" THEN
open = stack.pop() OR -1;
IF open < 0 THEN
RAISE "brnfk unmatched ] at instruction ${i.toString()}";
END
jumps[open] = i;
jumps[i] = open;
END
i += 1;
END

IF stack.any?() THEN
open = stack.pop() OR -1;
RAISE "brnfk unmatched [ at instruction ${open.toString()}";
END

RETURN jumps;
END

FN run(program: String, input: String) RETURNS !String ->
RETURN runWithLimits(program, input, 30_000, 10_000_000);
END

FN runFile(path: String, input: String) RETURNS !String ->
RETURN runFileWithLimits(path, input, 30_000, 10_000_000);
END

FN runFileWithLimits(path: String, input: String, tapeSize: Int64, maxSteps: Int64) RETURNS !String ->
program = readFile(path) OR RAISE;
RETURN runWithLimits(program, input, tapeSize, maxSteps);
END

FN runWithLimits(program: String, input: String, tapeSize: Int64, maxSteps: Int64) RETURNS !String ->
code = commands(program);
jumps = makeJumpTable(code);
MUTABLE tape = makeTape(tapeSize);
MUTABLE pointer = 0;
MUTABLE ip = 0;
MUTABLE inputPos = 0;
MUTABLE steps = 0;
MUTABLE output: String[]@list = [];

WHILE ip < code.length() DO
IF steps >= maxSteps THEN
RAISE "brnfk exceeded max step count";
END

op = code.charAt(ip);
IF op == ">" THEN
pointer += 1;
IF pointer >= tape.length() THEN
RAISE "brnfk pointer moved past tape end";
END
ELSE_IF op == "<" THEN
IF pointer == 0 THEN
RAISE "brnfk pointer moved before tape start";
END
pointer -= 1;
ELSE_IF op == "+" THEN
tape[pointer] = incByte(tape[pointer]);
ELSE_IF op == "-" THEN
tape[pointer] = decByte(tape[pointer]);
ELSE_IF op == "." THEN
output.append(byteToString(tape[pointer]));
ELSE_IF op == "," THEN
IF inputPos < input.length() THEN
tape[pointer] = stringToByte(input.charAt(inputPos));
inputPos += 1;
END
ELSE_IF op == "[" THEN
IF tape[pointer] == 0 THEN
ip = jumps[ip];
END
ELSE_IF op == "]" THEN
IF tape[pointer] != 0 THEN
ip = jumps[ip];
END
END

ip += 1;
steps += 1;
END

RETURN output.join("");
END
8 changes: 8 additions & 0 deletions examples/brnfk/tests/392quine.b
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
->++>+++>+>+>+++>>>>>>>>>>>>>>>>>>>>+>+>++>+++>++>>+++>+>>>>>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>+>+>>+++>>+++>>>>>+++>+>>>>>>>>>++>+++>+++>+>>+++>>>+++>+>++>+++>>>+>+>
++>+++>+>+>>+++>>>>>>>+>+>>>+>+>++>+++>+++>+>>+++>>>+++>+>++>+++>++>>+>+>++>+++>
+>+>>+++>>>>>+++>+>>>>>++>+++>+++>+>>+++>>>+++>+>+++>+>>+++>>+++>>++[[>>+[>]++>+
+[<]<-]>+[>]<+<+++[<]<+]>+[>]++++>++[[<++++++++++++++++>-]<+++++++++.<]
[Slight modification of Erik Bosman's ingenious 410-byte quine.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/]
8 changes: 8 additions & 0 deletions examples/brnfk/tests/400quine.b
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
->++>+++>+>+>+++>>>>>>>>>>>>>>>>>>>>>>+>+>++>+++>++>>+++>+>>>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>+>+>>+++>>>>+++>>>+++>+>>>>>>>++>+++>+++>+>+++>+>>+++>>>+++>+>++>+++>
>>+>+>+>+>++>+++>+>+>>+++>>>>>>>+>+>>>+>+>++>+++>+++>+>>+++>+++>+>+++>+>++>+++>+
+>>+>+>++>+++>+>+>>+++>>>+++>+>>>++>+++>+++>+>>+++>>>+++>+>+++>+>>+++>>+++>>+[[>
>+[>]+>+[<]<-]>>[>]<+<+++[<]<<+]>+[>>]+++>+[+[<++++++++++++++++>-]<++++++++++.<]
[Slight modification of Erik Bosman's ingenious 410-byte quine.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/]
15 changes: 15 additions & 0 deletions examples/brnfk/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Brainfuck.org corpus
====================

These `.b` programs were downloaded from https://www.brainfuck.org/.
The site attributes the programs to Daniel B. Cristofani and marks the
contents as Creative Commons Attribution-ShareAlike 4.0.

`brnfk-corpus-tests.cht` runs a deterministic subset directly from these
files (`null.b`, `rot13.b`, `xmastree.b`, `squares.b`) and also encodes the
stable implementation-test snippets from `tests.b`.

Some files in this corpus are intentionally interactive, open-ended,
stress-oriented, or produce non-printable bytes. They are vendored here as
reference programs, but they are not all part of the fast automated CLEAR
test.
Loading
Loading