F♭ (pronounced F-flat) is a toy language.
This is a small F♭ tutorial that should take no more than a few minutes to complete.
The code snippets below are meant to demonstrate input and outputs in the fflat repl. Lines starting with F♭>
are user input followed by output. The output by the repl is the current stack. The stack is always displayed in brackets. For example:
F♭> 3 2 + 'Hello World' println // user input
Hello World // text printed to the console
[ 5 ] // the current stack
For most examples it is assumed that the current stack is clear. The stack can be cleared between examples by typing clr
. .clear
will reset the entire environment.
// Single-line comments start with two slashes.
/* Multiline comments start with slash-star,
and end with star-slash */
White space does not matter, comma (,) is whitespace. Brackets are always considered to have whitespace padding.
F♭> ['these','are',['equivalent']]
[ [ 'these' 'are' [ 'equivalent' ] ] ]
F♭> [
'these'
'are'
[ 'equivalent' ]
]
[ [ 'these' 'are' [ 'equivalent' ] ] ]
Strings are created with '
, "
or \
`.
F♭> 'abc'
[ 'abc' ]
F♭> 'λx:(μα.α→α).xx' // can include Unicode characters
[ 'λx:(μα.α→α).xx' ]
F♭> "f-flat === f\u266D" // double quotes to use encoded strings
[ 'f-flat === f♭' ]
F♭> `1 + 2 is $(1 2 +)` // backticks for string templates
[ '1 + 2 is 3' ]
F♭> 15 // a number
[ 15 ]
F♭> 20% // a percentage
[ 0.2 ]
F♭> 1_000_000 // Use underscores to improve readability!
[ 1000000 ]
// Binary, octal, and hex values ca be entered using 0b, 0o, and 0x notation.
F♭> 0b111 // binary
[ 7 ]
F♭> 0o111 // octal
[ 73 ]
F♭> 0x111 // hexadecimal
[ 273 ]
F♭> true
[ true ]
F♭> false
[ false ]
F♭> null // used to indicate a deliberate non-value (neither true nor false)
[ null ]
F♭> thing // a word that is executed immediatly unless enclosed in an lazy quote
[ ??? ]
F♭> thing: // this is a key, it is not execuated unless explicitly evaluated
[ thing: ]
F♭> :thing // a word that is executed immediatly even if it is enclosed in an lazy array
[ :thing ]
F♭> #thing // a unique symbol
[ #thing ]
F♭> (1 2) :complex // Complex numbers
[ 1+2i ]
F♭> "1/1/1990" :date // dates
[ Mon Jan 01 1990 00:00:00 GMT-0700 (Mountain Standard Time) ]
F♭> '/a/' :regexp // regular expressions
[ /a/ ]
Words within an active quotation are evaluated
F♭> (1 2 3)
[ [ 1 2 3 ] ]
F♭> (1 2 +)
[ [ 3 ] ]
F♭> ('Hello' 'World' +)
[ [ 'HelloWorld' ] ]
Words within an active quotation are not evaluated (by default)
F♭> [1 2 3]
[ [ 1 2 3 ] ]
F♭> [1 2 +]
[ [ 1 2 + ] ]
F♭> ['Hello' 'World' +]
[ [ 'Hello' 'World' + ] ]
F♭> ['Hello' ' World' :+] // unless explitly marked as an immediate word
[ [ 'Hello World' ] ]
F♭> 1 unit
[ [ 1 ] ]
F♭> [ 2 ] 3 << // push
[ [ 2 3 ] ]
F♭> 5 [ 8 ] >> // unshift
[ [ 5 8 ] ]
F♭> [ 13 ] [ 21 ] + // concat
[ [ 13 21 ] ]
F♭> [ 5 8 13 ] ln // `ln` is returns the length.
[ 3 ]
F♭> 'Hello' ln // Same for strings, which often work just like an array of characters
[ 5 ]
F♭> [ 'first' 'second' ] 1 @ // Arrays are zero-based, negitive for access from the back
[ 'first' ]
F♭> 'This is a string' 0 @ // Again same for strings
[ 'T' ]
F♭> [ 'first' 'second' ] -2 @ // can acess from the end with negitive values
[ 'first' ]
F♭> 'This is a string' -1 @
[ 'g' ]
// Some defined access words
F♭> [ 'first' 'second' ] first
[ 'first' ]
F♭> 'This is a string' last
[ 'g' ]
F♭> [ 1 2 3 4 ] rest
[ [ 2 3 4 ] ]
F♭> [ 5 8 13 ] 8 indexof
[ 1 ]
F♭> 'hello' 'l' indexof
[ 2 ]
F♭> [ 2 ] 3 * // repeat items in an quotes with *
[ [ 2 2 2 ] ]
F♭> [ 1 2 3 ] [ 2 * ] * // Intersparse quotes using *
[ [ 1 2 * 2 2 * 3 2 * ] ]
F♭> 2 [ 3 + ] eval // Quotes can be evaluated
[ 5 ]
F♭> 1 2 3 [ 3 + ] dip // `dip` is useful for evaluating against the second item on the stack
[ 1 5 3 ]
F♭> [ 3 2 * ] in // `in` runs the quote in a child scope (more on scoping below), pushing the child stack to the parent
[ [ 6 ] ]
F♭> [ 1 2 * suspend 3 4 * ] in // children can be suspended
[ [ 2 3 4 * ] ]
F♭> in // and continued
[ [ 2 12 ] ]
F♭> [ 1 2 3 ] [ 2 * ] map // `map` is defined as `[ * in ]`
[ [ 2 4 6 ] ]
F♭> [ 1 2 3 ] [ 2 < ] filter
[ [ 1 ] ]
Maps are sets of key value pairs
F♭> { x: 1 y: 2 }
[ { x: 1, y: 2 } ]
F♭> { x: 1 y: 2 } x: @ // use `@` to get a value by key or string
[ 1 ]
F♭> { x: 1 y: { z: 3 } } 'y.z' @ // access deeply
[ 3 ]
F♭> { x: 1 } { y: 2 } + // joined
[ { x: 1, y: 2 } ]
F♭> { x: 1 y: 2 } ln // number of key/values
[ 2 ]
F♭> 'world' println // prints with a new line
world
[ ]
F♭> cwd
[ 'file:///Users/jmh/workspace/projects/f-flat_node/src/ff-lib/' ]
F♭> '../../readme.md' read // text files from cwd can be loaded as strings
[
'# F♭\n\nF♭ (pronounced F-flat) is a toy language.\n\nF♭ is a dynamically typed array-oriented concatenative language like Forth, Joy, and [others](http://www.concate ... \n\nMIT\n'
]
F♭> 'lambdas.ff' read eval // ff files can be evaluated
[ ]
F♭> 3 dup // `dup`licate the bottom (last) item
[ 3 3 ]
F♭> 2 5 swap // `swap` the bottom with the second element
[ 5 2 ]
F♭> 4 0 drop // `drop` the bottom item
[ 4 ]
F♭> 1 2 clr // wipe out the entire stack
[ ]
F♭> 1 2 3 4 over // duplicate the second item to the top
[ 1 2 3 4 3 ]
F♭> 1 2 3 4 2 pick // duplicate the third item to the top
[ 1 2 3 4 2 3 ]
F♭> 6 7 *
[ 42 ]
F♭> 1360 23 -
[ 1337 ]
F♭> 12 4 /
[ 3 ]
F♭> 13 2 % // modulo
[ 1 ]
F♭> 99 ~ // unary negation
[ -99 ]
F♭> -10 abs // absolute value
[ 10 ]
F♭> 2 3 ^ // power function
[ 8 ]
F♭> 1000 ! // factorial (see math.ff)
[ 4.0238726007709377384e+2567 ]
F♭> 1.5 ! // factorial approximation (using `gamma` see math.ff)
[ 1.3293403881791368454 ]
F♭> 10 ln // natural log
[ 2.302585092994046 ]
F♭> 5 exp // exponential
[ 148.41315910257660342 ]
F♭> 0.7 sin
[ 0.64421768723769105367 ]
F♭> (1 2) :complex (6 -4) :complex *
[ 14+8i ]
F♭> true false + // OR
[ true ]
F♭> true false * // AND
[ false ]
F♭> true ~ // NOT
[ false ]
// Null is considered unknown for boolean logic (Three-valued logic)
F♭> false null * // OR
[ false ]
F♭> true null * // AND
[ null ]
F♭> false null * // AND
[ false ]
F♭> null ~ // NOT
[ null ]
F♭> 0xFF 0xF0 bit-and
[ 240 ]
F♭> 0x0F 0xF0 bit-or
[ 255 ]
F♭> 0xF0 bit-not
[ -241 ]
F♭> 'a' 'b' <=> // Three-way comparison returns -1, 0, 1
[ 1 ]
F♭> 2 1 <=>
[ -1 ]
F♭> 2 1 <=>
[ -1 ]
F♭> 'x' 'x' <=>
[ 0 ]
F♭> 1 1 = // Equality defined as `=`
[ true ]
F♭> 2 1 != // and inequality defined `!=`
[ true ]
F♭> #x #x = // symbols are never equal
[ false ]
F♭> #x dup = // unless they are the same
[ true ]
F♭> [ 1 2 ] [ 1 2 ] = // arrays are checked by element
[ true ]
F♭> [1 [2]] [1 [3]] = // and deeply
[ false ]
F♭> 4 5 !=
[ true ]
F♭> 'a' 'b' < // Compare with `<`, `>`, `>=`, `<=`
[ true ]
F♭> 'a' 'b' >=
[ false ]
F♭> 255 type
[ 'number' ]
F♭> 255 string? // same as `type 'string' =`
[ false ]
F♭> 255 string
[ '255' ]
F♭> '300' number
[ 300 ]
F♭> 'hello' boolean
[ true ]
F♭> null boolean // `null` is neither
[ null ]
F♭> 5 3 =~ // pattern match
[ false ]
F♭> 5 5 =~ // for literals same as `=`
[ true ]
F♭> 'Hello' '/^Hell/':regexp =~ // strings can match regex
[ true ]
F♭> 'This can be anything' _ =~ // underscore (`_`) matches anything
[ true ]
F♭> [ 1 2 3 ] [ 1 _ 3 ] =~ // arrays matched per element
[ true ]
// test if true if false
// ---- -------------- ---------------
F♭> true 'this is true' 'this is false' choose
[ 'this is true' ]
F♭> null 'A' 'B' choose // null is neither true nor false
[ null ]
F♭> 2 [ 'Two is true' ] [ 'Two is not false' ] branch // `branch` is defined as `choose eval`
[ 'Two is true' ]
F♭> 5 [ 'Five is true' ] when // `when` is defined as `[] branch`
[ 'Five is true' ]
F♭> 0 [ 'Zero is false' ] when
[ ]
F♭> false [ 'false is false' ] unless
[ 'false is false' ]
Switch calls the quotation in the first quotation whose evaluation yields a truthy value.
F♭> 0
[
[ dup 0 = [drop 'no apples']]
[ dup 1 = [drop 'one apple']]
[ dup 1 > [string ' apples' +]]
] switch
[ 'no apples' ]
F♭> 1 2 [ 2 = ] case // case evaluates a quote without consuming a value
[ 1 2 true ]
F♭> 1
[
[ [ 0 = ] case [drop 'no apples']]
[ [ 1 = ] case [drop 'one apple']]
[ [ 1 > ] case [string ' apples' +]]
] switch
[ 'one apple' ]
F♭> 1 2 3 p-case // p-case pattern matches without consuming a value
[ 1 2 false ]
F♭> 3
[
[ 0 p-case [drop 'no apples']]
[ 1 p-case [drop 'one apple']]
[ _ p-case [string ' apples' +]]
] switch
[ '3 apples' ]
F♭> 3 // switch can combine different versions
[
[ 0 p-case [ ]]
[ 1 p-case [ ]]
[ [ 15 > ] case [ binet ]]
[ true [[1 - fib] [2 - fib] bi + ]]
] switch
[ 2 ]
// name body define
// ------- --------- -
F♭> square: [ dup * ] ;
[ ]
// apply functions by invoking the word
F♭> 5 square
[ 25 ]
F♭> x: 1 ; // creates a function that always returns 1 (shortcut for `x: [ 1 ] ;`)
[ ]
// We can print what a word does.
F♭> 'square' show
[ dup * ]
[ ]
F♭> y: [ x 2 + ] ; // Referenced words must be defined at defintion time
Thrown:
FFlatError: ';' Word is not defined: "x"
F♭> x: defer y: defer // mutually recursive words must be defered
[ ]
F♭> x: [ 1 > [ dup --] [ y ] branch ] ;
[ ]
F♭> y: [ 0 = [ dup ++ ] [ x ] branch ] ;
[ ]
F♭> 1 2 3 [ a: b: c: ] => // `=>` consumes one value per key, returning a map
[ { a: 1, b: 2, c: 3 } ]
F♭> [ 1 a b * ] { a: 2 b: 3 } rewrite // `rewrite` will use a map to rewrite a quote
[ [ 1 2 3 * ] ]
F♭> quad: [
[ a: b: c: ] => [
.b -1 * .b .b * 4 .a .c * * - sqrt -+ // words prefixed with a period (.) are not bound at definition time
[ 2 .a * / ] bi@
]
] lambda ; // quotes can be converted to lambdas, which use a map to rewrite the quote before evaluation
[ ]
F♭> 1 -3 0 quad
[ 0 3 ]
F♭> hello: [ 'Hello ' swap + ] ; // words are defined in a local context
[ ]
F♭> 'World' hello
[ 'Hello World' ]
F♭> hello: [ 'Hi ' swap + ] ; // words cannot be redefined in the current context
Thrown:
FFlatError: ';' cannot overwrite definition: 'hello'
F♭> [ hello: [ 'Hi ' swap + ] ; 'World' hello ] in // `in` runs the quote in a child scope, pushing the child stack to the parent
[ 'Hello World' [ 'Hi World' ] ]
F♭> 'World' hello // without inpacting definitions in parent
[ 'Hello World' [ 'Hi World' ] 'Hello World' ]
F♭> vocab // `vocab` will return a dictionary of words defined in the current context (key-symbol pairs)
[ { hello: #hello } ]
F♭> [ hi: [ 'Hi ' swap + ] ; vocab ] in // in a child
[ [ { hi: #hi } ] ]
F♭> 'example.ff' import // `import` will read a file, evaluate the string, and return the vocabulary (see `loader.ff`)
[ { hi: #hi } ]
F♭> 'example.ff' import use // `use` will bring the definitions into the current scope (not current context)
[ ]
F♭> 'World' hi
[ 'Hi World' ]
F♭> example: 'example.ff' import ; // The `vocab` dictionary can also be defined as a word
[ ]
F♭> 5 example.hi // for access by name
[ 'Hi World' ]
F♭> [ 2 3 * ] spawn // `spawn` is like `in` but returns a future
[ [Future:pending [2,3,*]] ]
F♭> // press enter to show the stack
[ [Future:resolved [6]] ]
F♭> eval
[ 6 ]
F♭> [ 2 3 * ] spawn await // await will wait for a future to be resolved
[ [ 6 ] ]