Skip to content

stjordanis/forthy2

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logo

intro

forthy2 is a higher level Forth-remix in C++.

method fib(0)   _
method fib(1)   _
method fib(Int) {-1! $copy fib $swap -1! fib +}

setup

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

status

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.

stacks

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

() _

scopes

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

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

numbers

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

dot notation

Methods and macros may be called infix by adding . between first argument and operation.

  6.* 7

42

identity & equality

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

references

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

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

branching

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

loops

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

iterators

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)

types

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?

bindings

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

methods

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

recalls

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

quoting

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

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

io

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

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

debugging

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)

benchmarks

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

garbage collection

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

license

MIT

support

Please consider a donation if you would like to support the project, every contribution helps.

Donate using Liberapay

About

a Forth (for you) too

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 98.5%
  • Other 1.5%