forthy2 is a higher level Forth-remix in C++.
method fib(0) _
method fib(1) _
method fib(Int) {-1! $copy fib $swap -1! fib +}
forthy2 requires a C++17 compiler and CMake to build.
$ git clone https://github.com/codr7/forthy2.git
$ cd forthy2
$ mkdir build
$ cd build
$ cmake ..
$ make
$ rlwrap ./forthy2
forthy2
Press Return on empty row to evaluate.
Empty input clears stack and Ctrl+D exits.
42
42
Examples from this document, as well as tests and benchmarks; should do the right thing and run clean in Valgrind. Performance is currently hovering around Python3, I expect that to keep improving for a while.
Operations may be prefixed with $
to target the primary stack.
copy
repeats the top item,
42 $copy
42 42
while drop
removes it.
1 2 3 $drop
1 2
swap
swaps the top two items.
1 2 3 $swap
1 3 2
truffle
may be used to simplify and speed up more elaborate transformations, the result from evaluating the second part replaces the specified suffix.
1 3 5 7
$truffle(a b c _; b a .+ c)
3 6
Unknown identifiers signal compile time errors.
1 3 5 $truffle(a b c; d)
Error at row 1, col 15:
Unknown id: d
Additional stacks may be created by enclosing items in parens.
(1 3 5)
(1 3 5)
;
may be used to collect remaining values as a nested stack.
(1; 3 5)
(1 (3 5))
len
returns the number of items,
(1 3 5 7) len
4
while push
adds a new item on top and pop
removes and returns it.
(1 3 5) .push 7
(1 3 5 7) 4
$drop pop
(1 3 5) 7
Popping empty stacks returns _
.
() pop
() _
forthy2 is strictly block scoped, definitions are tied to the containing scope and invisible from the outside.
{let foo 42}
foo
Error at row 2, col 0:
Unknown id: foo
Booleans come in two flavors, T
and F
.
T not
F
All values have boolean representations, non-zero integers are T
etc.
42 bool
T
and
/or
both pop the first argument off the stack and return the last evaluated value.
T and 42
42
F or 42
42
Booleans support explicit conversion to integers.
T int F int
1 0
Integers default to decimal notation.
42
42
Prefixing with 0b
switches to binary,
0b101010
42
and 0x
hexadecimal.
0x2a
42
_
may be used as separator to increase readability.
1_000_000
1000000
Fix points literals infer precision,
0.30
0.30
while manual construction requires explicit precision.
42 3 fix
42.000
Fix point operations preserve left hand precision.
0.23 .+ 0.1
0.33
Methods and macros may be called infix by adding .
between first argument and operation.
6.* 7
42
is
may be used to check if two values share the same memory address,
T.is T
T
42.is 42
F
while =
returns true if they are equal.
(1 2 3).= (1 2 3)
T
Macros and methods support capturing references using &
.
&*[Int Int]
Method@0x24f59b0
6 $swap 7 $swap call
42
Scope-references evaluate to anonymous functions.
&{42}
Lambda@0x24f73c8
call
42
Pairs may be created at read time using :
,
1:3
1:3
or at run time using pair
;
1.pair 3
1:3
and split using unpair
.
1:3 unpair
1 3
if
/else
both take a condition on the stack and a body to execute when the condition is true/false.
42 if 'ok
'ok
0 else 'ok
'ok
while
evaluates its body until it returns false.
3 while {-1! $copy} $drop
2 1 0
for
pops a sequence from the stack and evaluates its body once for every value. A missing body leaves the entire sequence on the stack unchanged. The following example builds a list of integers.
(3 for _)
(0 1 2)
repeat
pops a number from the stack and evaluates its body that many times.
3 repeat 42
42 42 42
All values are iterable, but most only contain one item. iter
may be used to obtain an iterator.
3 iter
IntIter@0x1003e70
for _
0 1 2
Iterators may be manually consumed using pop
.
('foo 42) iter pop
StackIter@0x1636e70 'foo
$drop pop
StackIter@0x1636e70 42
$drop pop
StackIter@0x1636e70 _
map
takes an input followed by a function as arguments, and returns an iterator for values obtained by applying the function to the input.
(1 3 5).map &+1!
Iter@0x23ca810
for _
2 4 6
filter
takes an input follows by a predicate as arguments, and returns an iterator for input values for which the predicate returns true.
(1 3 5).filter &{.> 2}
Filter@0x248bca0
for _
3 5
zip
takes two inputs and returns an iterator for resulting pairs,
('foo 'bar).zip (1 3 5)
Zip@0x124d580
for _
'foo:1 'bar:3
while unzip
takes an input of pairs and returns two stacks containing left and right values.
('foo:1 'bar:3) unzip
('foo 'bar) (1 3)
type
may be called to get the type of any value.
42 type
Int
Nil
only has one value, the missing value.
_ type
Nil
A
is the root type, from which all types except Nil
are derived. The type hierarchy may be queried using isa
; which returns the direct parent type if any, and _
otherwise.
Nil.isa A
_
Nil:able types may be created by suffixing type names with ?
.
Nil.isa A?
A?
let
may be used to bind names to values at compile time.
{
let foo 42
foo
}
42
Values are evaluated before bound.
{
let foo {6.* 7}
foo
}
42
Referencing unbound names signals compile time errors,
{
let foo 42
bar
}
Error at row 2, col 2:
Unknown id: bar
as do attempts to shadow bindings within the same scope,
let foo 1
let foo 3
Error at row 2, col 1:
Dup binding: foo
while shadowing within child scopes is permitted.
let foo 1
{let foo 3}
foo
1
Any number of methods may use the same name as long as they take the same number of arguments but different types.
method foo(Bool) {$drop 'bool}
method foo(Int) {$drop 'int}
foo T
'bool
foo 42
'int
Literal arguments allow dispatching on specific values rather than types.
method foo(42) _
foo 42
42
Method implementations are block-scoped just like any other definition.
{
method foo(7)
foo 7
}
7
foo 7
'int
Lambdas and methods both support forwarding calls without creating additional frames, commonly known as tail call optimization. recall
takes an optional target as argument and forwards the current call. Specifying _
as target calls the current lambda/method recursively. Execution picks up after the current call on return, which is why 'done2
is not evaluated in the following example.
method foo(T) 'done1
method foo(A) {$drop T recall foo 'done2}
42 foo
'done1
Anything may be quoted by prefixing with '
.
Quoted literals are evaluated as usual;
'42
42
while identifiers turn into symbols,
'let
'let
and scopes become forms.
'{6.* 7}
'{6.* 7}
Scopes may be turned into lambdas using compile
.
'{6.* 7} compile
Lambda@0x252dab0
call
42
Scope bodies are regular stacks of forms.
'{1 3 5} body
'{1 3 5} ('1 '3 '5)
drop
'{1 3} ('1 '3)
$drop compile call
1 3
Scopes may contain placeholders for splicing external values. Values may be specified inline, or popped from stack using $
.
42 '{%./ %}
42 '{%./ %}
splice($ 7)
'{42./ 7}
compile call
6
Macros are methods that expand their result at compile time as opposed to push on stack at run time.
macro foo(Int) {'{42./ %} splice($)}
foo 7
6
say
may be used to pretty-print any value to STDOUT
followed by newline.
'hello say
hello
dump
is similar, but prints raw values without formatting.
'hello dump
'hello
Time may be created using provided constructors (hours
/mins
/secs
/msecs
/usecs
/nsecs
) and support arithmetics and conversion.
2 hours
Time@7200000000000
.- {30 mins}
Time@5400000000000
mins
90
dump-stack
dumps the current stack to STDOUT
without modifying it.
1 2 3 dump-stack
(1 2 3)
1 2 3
check
may be used to trigger informative errors when the specified condition doesn't hold.
check {6.* 7 .is 41}
Error at row 1, col 0:
Check failed: {6.* 7 .is 41}
-debug
may be passed on the command line to abort on error.
$ valgrind build/forthy2 -debug
forthy2
Press Return on empty row to evaluate.
Empty input clears stack and Ctrl+D exits.
foo
terminate called after throwing an instance of 'forthy2::ESys'
what(): Error at row 1, col 0:
Unknown id: foo
Process terminating with default action of signal 6 (SIGABRT)
at 0x591C428: raise (raise.c:54)
by 0x591E029: abort (abort.c:89)
by 0x50D00F4: __gnu_cxx::__verbose_terminate_handler() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24)
by 0x50CDCE5: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24)
by 0x50CDD30: std::terminate() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24)
by 0x50CDFC8: __cxa_rethrow (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24)
by 0x429CCD: repl(forthy2::Cx&) (main.cpp:26)
by 0x42A31C: main (main.cpp:75)
clock
may be used to measure the time it takes to evaluate a form N times.
1000000 clock {}
Time@1503489
usecs
1503
Some type checks may be skipped in return for a 30% speed up by passing -unsafe
on the command line. Unsafe in this context means potentially crashing on failed dynamic C++ type casts as opposed to checking types manually and throwing more informative errors.
$ build/forthy2 bench/fib_rec.f2
60
$ build/forthy2 -unsafe bench/fib_rec.f2
46
forthy2 supports incremental, time sliced manual garbage collection.
mark
takes an optional max time and returns the total time if completed, _
otherwise.
_ mark nsecs
23257
sweep
provides an identical API to the second phase,
_ sweep nsecs
1746
and mark-sweep
allows performing both phases as one.
_ mark-sweep nsecs
26120
Please consider a donation if you would like to support the project, every contribution helps.