Home | Getting Started | Language Guide | Builtins | FFI | Macros | Examples
fy is a concatenative, stack-based language. Programs are sequences of words separated by whitespace. Each word either pushes a value onto the data stack or pops values, does something, and pushes results back.
All computation flows through a single data stack. Values are 64-bit integers (i64). Strings and quotes are heap-allocated and referenced via tagged pointers.
2 3 + ( push 2, push 3, pop both and push 5 )
dup * ( duplicate 5, multiply: 25 )
. ( pop and print: 25 )Stack effects are written in Forth notation: ( before -- after ).
Decimal integers, optionally negative:
42 ( push 42 )
-7 ( push -7 )
0 ( push 0 )Numbers with a decimal point:
3.14 ( push 3.14 as f64, bitcast into Value )
-0.5 ( negative float )Floats use a separate set of arithmetic words: f+, f-, f*, f/, f., etc. See Builtins.
Double-quoted, with backslash escapes:
"hello" ( push a heap string )
"line one\nline two" ( \n = newline )
"tab\there" ( \t = tab )
"null\0byte" ( \0 = null byte )
"escaped\"quote" ( \" = literal quote )Supported escapes: \n \r \t \\ \" \0.
Single quote followed by a character pushes its ASCII value:
'A ( push 65 )
'\n ( push 10 — but note: this is just the character 'n' after '\', not a newline )Parenthesized text is a comment. Nesting is supported:
( this is a comment )
( outer ( inner ) still comment )
2 3 + ( add two numbers ) .: square dup * ;
: abs dup 0 < [ 0 swap - ] [ ] ifte ;Everything between : and ; is compiled. The word becomes callable by name. Words can call themselves recursively:
: fib dup 1 <= [ drop 1 ] [ dup 1- fib swap 2 - fib + ] ifte ;Mutual recursion works — words can call any previously or forward-defined word.
:: answer 42 ;
:: pi 3.14159 ;
:: lib "/usr/lib/libSystem.B.dylib" dl-open ;The body after the name is compiled, executed once at compile time, and the result is baked into the word as a literal. This is essential for FFI handles and computed constants.
Quotes are first-class code blocks delimited by [ ... ]:
[ 1 + ] ( a quote that adds 1 )
[ dup * ] ( a quote that squares )Quotes can be:
- Executed with
do - Passed to combinators (
ifte,map,reduce,filter,each,dotimes,repeat) - Stored on the stack and manipulated as data
- Contain any code including nested quotes
\word creates a single-word quote:
\+ ( equivalent to [ + ] )
[1 2 3] \. each ( print each element )Quotes can bind stack values to named locals:
1 2 3 [ | a b c | a b + c * ] do ( (1 + 2) * 3 = 9 )The | name1 name2 ... | header pops values from the stack (right-to-left: last name gets TOS) and makes them available by name within the quote body. Locals are immutable and lexically scoped.
5 [ | x |
x 1+
x 1-
*
] do . ( 6 * 4 = 24 )condition [ then-body ] [ else-body ] iftePops the condition and two quotes. Executes the then-quote if condition is non-zero, else-quote if zero:
: abs dup 0 < [ 0 swap - ] [ ] ifte ;
5 abs . ( 5 )
-3 abs . ( 3 )[ 2 3 + ] do . ( 5 )condition [ body ] do?Executes the quote only if condition is non-zero. Does nothing if zero:
1 [ "yes" s. .nl ] do? ( prints "yes" )
0 [ "no" s. .nl ] do? ( prints nothing )count [ body ] dotimesExecutes the body count times:
5 [ "hello" s. .nl ] dotimesinitial [ body ] repeatLoops while TOS is non-zero. The body should leave a new condition on TOS:
5 [ dup . 1- dup ] repeat drop ( prints 5 4 3 2 1 )Inside a word definition, recur jumps back to the beginning of the current word, enabling efficient tail-recursive loops.
| Word | Effect | Description |
|---|---|---|
dup |
a -- a a |
Duplicate top |
dup2 |
a b -- a b a b |
Duplicate top pair |
drop |
a -- |
Discard top |
drop2 |
a b -- |
Discard top two |
swap |
a b -- b a |
Swap top two |
over |
a b -- a b a |
Copy second to top |
over2 |
c d a b -- c d a b c d |
Copy third+fourth |
nip |
a b -- b |
Drop second |
tuck |
a b -- b a b |
Copy top below second |
rot |
a b c -- b c a |
Rotate three |
-rot |
a b c -- c a b |
Reverse rotate |
depth |
-- n |
Current stack depth |
>r |
x -- |
Move to retain stack |
r> |
-- x |
Move from retain stack |
r@ |
-- x |
Copy from retain stack |
dip |
x f -- x |
Execute f, preserving x |
The retain stack (return stack) is a secondary stack for temporary storage. Use >r / r> to shuttle values there and back.
dip is a powerful combinator: it saves TOS, executes the quote/word beneath it, then restores TOS:
1 2 3 [ + ] dip ( 1+2=3, then 3 restored → stack: 3 3 )These words take quotes (or word references via \word) and operate over collections:
| Word | Effect | Description |
|---|---|---|
map |
list f -- list' |
Apply f to each element |
reduce |
acc list f -- result |
Fold with accumulator |
filter |
list f -- list' |
Keep elements where f returns non-zero |
each |
list f -- 0 |
Apply f for side effects |
[1 2 3 4] [dup *] map ( [1 4 9 16] )
0 [1 2 3 4 5] [+] reduce ( 15 )
[1 2 3 4 5] [ 3 > ] filter ( [4 5] )
[1 2 3] \. each ( prints 1 2 3 )include "mathlib.fy"Reads and compiles the file. All definitions enter the current scope.
import "raylib"Reads raylib.fy, compiling all definitions with a raylib: prefix:
import "raylib"
raylib:InitWindowPaths are resolved relative to the importing file. The .fy extension is appended automatically if missing. Each file is imported at most once (dedup guard).
Define C-compatible memory layouts:
struct: Point
f32 x
f32 y
;This generates:
Point.size— struct size in bytesPoint.alloc— allocate zeroed memory for one PointPoint.new— pop field values from stack, allocate, and initializePoint.x@/Point.x!— read/write thexfieldPoint.y@/Point.y!— read/write theyfield
Field types: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, ptr.
1.0 2.0 Point.new ( allocate and init: x=1.0, y=2.0 )
Point.x@ f. ( prints 1.0 )
3.0 swap Point.y! ( set y to 3.0 )See FFI Guide for using structs with C libraries.
fy values are untyped 64-bit words. The runtime distinguishes three kinds via tag bits:
| Type | Description |
|---|---|
| Integer | Raw i64, tag bit 0 |
| String | Heap-allocated byte array |
| Quote | Heap-allocated code block |
Type-checking words: int?, string?, quote?, word?.
Floats are f64 values bitcast into the i64 representation — they share the integer tag but are manipulated with f+, f-, etc.
fy has a mark-and-sweep garbage collector for heap objects (strings and quotes). Trigger it manually with gc. The GC walks the data stack and roots (compiler-created literals) to find reachable objects.
- Builtins Reference — complete word list
- FFI Guide — call C libraries
- Macros — compile-time metaprogramming
- Examples — real programs