Knot is a small stack-based language. Programs are parsed into instructions, executed by a VM, and printed from the final stack.
Run it with:
cargo run -- your_program.knotKnot is built around a stack and a few value types:
TandFfor booleans.- Numbers with automatic promotion across several numeric forms.
- Native functions stored in the global environment.
- Bodies, which are code values you can apply with
.. - Sequences, which collect values produced inside
[...]. - Objects, which collect captured locals from
{...}.
The VM looks up words in the current frame first, then in globals.
If a word is not found, it resolves to ().
() is the empty value.
It is also the fallback when the stack underflows or a lookup fails.
Booleans are T and F.
Only T is treated as true by conditional apply.
Everything else counts as false.
T (1 2 +.) ? ; 3
F (1 2 +.) ? ; ()
The numeric tower currently supports:
Nat: unsigned natural numbers, like0,1,2.Int: signed integers, like-1.Rat: rationals, written asa/b.Real: floating point numbers, like3.14.Complex: complex numbers, written asre i im, like1.5i2.1.
Numbers promote automatically when you mix them. In practice, arithmetic tries to keep the most precise form that makes sense.
Examples:
0
-1
1/3
3.1416
1.5i2.1
Funcis a native Rust function.Bodyis Knot code stored as a value.
Bodies are created by parentheses and are applied with ..
(2 *) . ; multiplies the top value by 2
Square brackets create a sequence frame.
When the frame ends, every value produced inside it is packed into a Seq.
[ 1 2 +. ]
Curly braces create an object frame. Captures inside the frame become fields in the resulting object.
{ 1 @a 2 @b }
Knot is whitespace-driven. Most things are words, and words become values or builtins when the VM runs.
T,F- numbers:
0,-1,1/3,3.1416,1.5i2.1 - parentheses:
( ... )for a body - brackets:
[ ... ]for a sequence - braces:
{ ... }for an object
. applies the value on the stack.
1 2 +.
This pushes 1, pushes 2, pushes the + function, then applies it.
? conditionally applies the top callable value.
The stack order is:
condition callable ?
If the condition is T, the callable runs.
If the condition is F, both values are discarded.
T (1 2 +.) ?
F (1 2 +.) ?
@name captures the top stack value into the current local scope.
@(a b c) captures multiple values in order from bottom to top.
That means the bottommost captured value goes into a, then b, then c.
1 2 3 @(a b c)
This gives a=1, b=2, c=3.
Captures are local to the current frame.
The current builtins are:
+addition-subtraction*multiplication/division%remainder^powernegunary negationnotboolean or bitwise not&bitwise and|bitwise or<<left shift>>right shift
They are applied like any other callable value:
1 2 +.
10 neg.
5 1 <<.
The VM runs one instruction at a time. Frames are used for bodies, sequences, and objects.
Bodyframes behave like callable code blocks.Seqframes collect the values produced inside the brackets.Objframes collect locals into a map.
When a body frame exits, its top value becomes the new top value in the previous frame. That is how a body returns a result to its caller.
This program adds 1 and 2, then applies a body that multiplies by 2:
1 2 +. (2 *.) .
And this swaps the top two values from 1 2 to 2 1:
1 2 (@(a b) b a) .
The language is still small and evolving.
Some ideas from IDEA.md are not implemented yet, especially sets and the extra comparison/logical operators mentioned there.
The README here describes the current code in this repository, not the full design sketch.