Nibl is a Forth VM that aims to be simple to understand, extend and embed; it currently weighs in at around 2kloc.
Nibl requires a C++ compiler and CMake to build.
cd nibl
mkdir build
cd build
cmake ..
make
./nibl help
Nibl v13
Usage: nibl [command] [file1.nl] [file2.nl]
Commands:
dump Dump VM code
eval Evaluate and exit
repl Start REPL
v Print version and exit
Duplicate a
.
Remove a
.
Swap a
and b
.
Rotate top three values left.
Rotate top three values right.
Nibl only supports integer arithmetics so far.
c
is b
added to a
.
c
is b
subtracted from a
.
c
is a
multiplied by b
.
c
is a
divided by b
.
c
is the remainder from dividing a
by b
.
Strings are enclosed in double quotes.
b
is the string representation of a
.
42 to-str
["42"]
b
is the integer value parsed from a
, or F
on failure.
c
is the remaining string on success.
"42" parse-int
[42 ""]
"foo" parse-int
[F]
Values may be interpolated into strings by embedding %
.
A single %
interpolates the top stack value.
1 2 "foo % bar % baz"
["foo 2 bar 1 baz"]
Arbitrary expressions may be interpolated using %{...}
.
"foo %{1 2 +} bar"
["foo 3 bar"]
\%
may be used to escape interpolation.
42 "foo % bar \%"
["foo 42 bar %"]
Booleans can be either true (T
) or false (F
).
Any value except F
, regardless of type, is true.
b
is true if a
is false, else false.
c
is false if a
is false, else the result of evaluating b
.
T and: 3
[3]
F and: 3
[F]
c
is true if a
is true, else the result of evaluating b
.
T or: 3
[T]
F or: 3
[3]
c
is true if a
and b
are equal, else false.
1 3 =
[F]
3 3 =
[T]
T F =
[F]
T T =
[T]
c
is true if a
is less than b
, else false.
c
is true if a
is greater than b
, else false.
Skip evaluation until ;
or else:
is reached or the program ends when a
is false.
T if: 1 2; 3
[1 2 3]
F if: 1 2; 3
[3]
T if: 1 else: 2; 3
[1 3]
F if: 1 else: 2; 3
[2 3]
20 dup 10 = if: pop 1 else: dup 20 = if: pop 2 else: pop 3;;
[2]
Values may be bound to names using def:
.
def: foo 42
[]
foo
[42]
Functions are first class and may be defined using fun:
, and subsequently called using call
.
fun: 1 2 3;
[Fun(repl@1:1)]
call
[1 2 3]
Every value has one of the following types:
Bool
The type of booleansChar
The type of charactersFun
The type of functionsInt
The type of numbersLib
The type of librariesMacro
The type of macrosMeta
The type of typesPrim
The type of primitivesStr
The type of strings
b
is the type of a
.
1 type-of
[Int]
type-of
[Meta]
References may be captured using &
.
1 2 3 dup
[1 2 3 3]
1 2 3 &dup
[1 2 3 dup]
Evaluate code in file a
.
foo.nl
1 2 3
"foo.nl" load
[1 2 3]
Print a
followed by newline to stdout.
Print a
to stdout and read line from stdin into b
.
Tracing may be toggled using trace
.
trace
0 STOP
[]
1 2 3 dup
2 PUSH_INT1 1
4 PUSH_INT1 2
6 PUSH_INT1 3
8 DUP
10 STOP
[1 2 3 3]
test:
may be used to write tests, it compares the contents of the stack before and after running the code.
1 2 test: 1 2 3;
Test failed, expected: [1 2], actual: [1 2 3]
[]
1 2 3 test: 1 2 3;
Test ok: [1 2 3]
[]
Nibl comes with a regression test suite.
./nibl ../tests.nl
Test ok: [1 2 2]
...
bench:
evaluates its body specified number of repetitions and measures elapsed time in milliseconds.
def: fib fun:
dup 1 > if:
dec dup fib swap
dec fib +;;
[]
1000 bench: 20 fib pop;
[1120]
python bench/fib.py
1004
def: fib fun:
rotr dup 1 > if:
dec rotl dup rotl + rec
else:
1 = if: swap;
pop;;
[]
100000 bench: 100 0 1 fib pop;
[962]
python bench/fibt.py
657