#+tanco-format: 0.2
> ok
The ‘ok’ word is a niladic function (takes no arguments) that evaluates to NIL and produces no output.
> set[`x; 42] 42 > get[`x] 42
Basic variable binding using set and retrieval using get. Variables hold mutable state that persists across expressions.
> y: 99 99 > y 99 > :y 99
The colon notation provides shorthand: x: value is syntactic sugar for set[`x; value], and :x gets the value without further evaluation. Throughout the rest of these tests, we’ll use this shorthand notation freely.
> (x: 12; y: 34; z: 56) 56
A sequence of expressions separated by semicolons evaluates each in order and returns the value of the last expression. This allows building multi-step programs.
> x: 12 y: 34 z: 56 56
Whitespace-separated expressions are syntactic sugar for semicolon-separated sequences. The parentheses and semicolons can be omitted at the top level.
> ite[1; 42; 99] 42 > ite[0; 42; 99] 99 > x: 5 5 > ite[x > 3; `big; `small] `big
The ite[cond; then; else] function provides conditional branching. The condition is evaluated, and if truthy (non-zero, non-nil), the then branch is evaluated and returned; otherwise the else branch is evaluated and returned. Only one branch is evaluated (lazy evaluation).
> (n: 0; while[n < 5; n: n + 1]; n) 5
The while[cond; body] function provides repetition. The condition is evaluated, and while truthy, the body is repeatedly evaluated. The loop returns NIL. This, combined with mutable state (assignment), sequence, and branching, provides the fundamental building blocks for any computable program (Turing-complete).
> [nil] [nil] > nil > 1 `x `x
> echo "hello" hello
> echo[2 + 2] 4
outputing “2 2” would be reasonable here if you don’t look ahead for operators. So this test forces you to define operators, and look ahead multiple tokens.
- next -> echo (verb)
- have verb, so look ahead for adverb/conjunction/
- peek -> 2 (noun)
- okay. we will apply verb to noun
- fetch full noun phrase, by calling “next”
- next -> 2 (noun)
- have noun, so look ahead for .method or infix op
> echo[1 + 2 * 3 + 5] 14
> 2 + 3 * 5 + 7 32 > 2, + 3 * 5, + 7 24 > ! 10, * 2, + 1 1 3 5 7 9 11 13 15 17 19 > ! 10 * 2 + 1 1 3 5 7 9 11 13 15 17 19 > 1 + 2 * ! 10 0 3 6 9 12 15 18 21 24 27 > 1, + 2 * ! 10 1 3 5 7 9 11 13 15 17 19
> echo .: this is a comment :. "hi" hi
> echo show "quoted" "quoted"
> echo["hello"] hello
> +[2;3] 5
> +[2] +[2]
“verb[noun]”, applies the noun as the verb’s first argument.
> 2 + +[2]
“verb[noun]” also applies the noun as the verb’s first argument.
> echo[+[2;3]] 5
> echo xmls [1 2; "three"; 'four]
<imp:lst open="[" close="]">
<imp:ints v="1 2"/>
<imp:str v="three"/>
<imp:sym k="lit" v="four"/>
</imp:lst>
> echo xmls '['foo :bar baz:]
<imp:lst open="[" close="]">
<imp:sym k="lit" v="foo"/>
<imp:sym k="get" v="bar"/>
<imp:sym k="set" v="baz"/>
</imp:lst>
> echo xmls [/refn foo/bar %file/path]
<imp:lst open="[" close="]">
<imp:sym k="refn" v="refn"/>
<imp:sym k="path" v="foo/bar"/>
<imp:sym k="file" v="file/path"/>
</imp:lst>
# > xmls [#abc]
# [#abc]
> echo xmls [http://example.com]
<imp:lst open="[" close="]">
<imp:sym k="url" v="http://example.com"/>
</imp:lst>
> echo xmls [`foo bar!]
<imp:lst open="[" close="]">
<imp:sym k="bqt" v="foo"/>
<imp:sym k="typ" v="bar"/>
</imp:lst>
> echo xmls '[@note .msg .kw:]
<imp:lst open="[" close="]">
<imp:sym k="ann" v="note"/>
<imp:sym k="msg" v="msg"/>
<imp:sym k="kw" v="kw"/>
</imp:lst>
> echo xmls '[!msg2 !kw2:]
<imp:lst open="[" close="]">
<imp:sym k="msg2" v="msg2"/>
<imp:sym k="kw2" v="kw2"/>
</imp:lst>
> echo xmls [?error]
<imp:lst open="[" close="]">
<imp:sym k="err" v="error"/>
</imp:lst>
> echo xmls '[`foo`bar`baz]
<imp:lst open="[" close="]">
<imp:sym k="bqt" v="foo"/>
<imp:sym k="bqt" v="bar"/>
<imp:sym k="bqt" v="baz"/>
</imp:lst>
> `[1 2 3]
[1 2 3]
> '[a b c]
[a b c]
> '[a 'b '[c]]
[a 'b '[c]]
> get[`undefined_var] ?undefined_var
When getting an undefined variable, get returns a fault symbol (prefixed with ?).
> set[`a `b `c; 1 2 3] 1 2 3 > get[`a `b `c] [1, 2, 3]
Parallel assignment distributes vector values to multiple variables.
> set[`p `q `r; 99] 99 > get[`p `q `r] [99, 99, 99]
When setting multiple variables to a scalar, all get the same value.
> set[`alpha; 10] 10 > get[`alpha `beta `gamma] [10 ?beta ?gamma]
Getting a mix of defined and undefined variables returns values and faults.
> x: 42
42
> x
42
> y: 2 + 3
5
> y
5
> a: 10
10
> b: a + 5
15
> b
15
> x: y: 42
42
> x
42
> y
42
> 1 + a: 2 3 4
3 4 5
> a
2 3 4
> 5 * b: 3
15
> b
3
> load "2 + 3"
2 + 3
> f: %hello.txt
%hello.txt
> e? f
0
> wr f "test content"
> e? f
1
> rd f
"test content"
> rm f
> x: 42
42
> `[1 2 ,x]
[1 2, 42]
> a: 10
10
> b: 20
20
> `[sum is ,a plus ,b]
[sum is 10 plus 20]
> y: 99
99
> `[outer [inner ,y]]
[outer [inner 99]]
> x: 'two
'two
> `[one ,x three]
[one two three]
> 1 2 3
1 2 3
> echo 1 2 3 4 5
1 2 3 4 5
> 42
42
> `a `b `c
`a `b `c
> echo `foo `bar `baz
`foo `bar `baz
> 1 + 0 1 2
1 2 3
> 1 2 + 3 4
4 6
> echo[1 2 3]
1 2 3
> 1 2 3
1 2 3
> (0) 1
1
> (1 2) 3
3
> a: 2
2
> b: 3
3
> a b
3
> + a b
5
Strands are formed only from adjacent literal tokens (INT, NUM, backtick symbols), not from evaluated expressions or variables. So `+ a b` passes two separate arguments (variables aren’t literals), while `+ 2 3` would form a strand `2 3` as one argument (resulting in partial application).
> + 1 2 3, 4 5 6
5 7 9
When collecting function arguments, strands form within each comma-separated argument. The comma acts as an argument separator.
> show 1 2 3
"1 2 3"
When a function collects arguments, adjacent literals form a strand that is passed as a single argument. (Note: using `show` instead of `echo` since `echo` writes to stdout which isn’t captured by the test harness)
> echo show type? 3
int!
> echo show type? "hello"
str!
> echo show type? `foo
sym!
> echo show type? [1 2 3]
lst!
> echo show type? 1 2 3
ints!
> echo show type? nil
nil!
> 10 + 5
15
> 10 - 5
5
> 10 * 5
50
> 10 % 3
3
> 2 ^ 8
256
> min[3; 7]
3
> max[3; 7]
7
> 1 2 3 min 2 4 1
1 2 1
> 5 tk 10
10 10 10 10 10
> 3 tk 1 2
1 2 1
> 4 tk 7 8 9
7 8 9 7
> 7 tk "abc"
"abcabca"
> 5 tk '[x y]
[x y x y x]
> rev 1 2 3 4
4 3 2 1
> rev '[a b c]
[c b a]
> rev `x `y `z
`z `y `x
> len 1 2 3
3
> len "hello"
5
> len '[a b c]
3
> len 42
1
> echo chr 65
A
> echo chr 65 66 67
ABC
> ord "A"
65
> ord "AB"
65 66
> echo hex 255
ff
> echo hex -31
-1f
> echo show hex 15 16
["f" "10"]
> echo oct 64
100
> echo oct -9
-11
> echo show oct 8 9
["10" "11"]
> ! 5
0 1 2 3 4
> ! 0
> ! 3
0 1 2
> part 42
"N"
> part 'echo
"V"
> part '+
"V"
> +/ 1 2 3 4
10
> */ 1 2 3 4
24
> +/ 5
5
> min/ 5 2 8 1 9
1
> max/ 5 2 8 1 9
9
> +\ 1 2 3 4
1 3 6 10
> *\ 2 3 4
2 6 24
> f: {x + 1}
{x + 1}
> f 5
6
> f 10 20 30
11 21 31
> add: {x + y}
{x + y}
> add[3; 5]
8
> double: {x * 2}
{x * 2}
> inc: {x + 1}
{x + 1}
> double inc 5
12
> add: {x + y}
{x + y}
> add3: add[3]
{x + y}[3]
> add3[5]
8
> {x + 2}
{x + 2}
> {x * 2} 10
20
Functions are first-class values. A standalone function definition `{x + 2}` returns the function itself. Functions can be immediately applied by providing arguments.
> f: {x + 2}
{x + 2}
> g: {x * 3}
{x * 3}
> f 5
7
> g 4
12
Assigning curly brace functions to variables works correctly. The function is stored as a value and can be called later.
> calc: {x + y * 2}
{x + y * 2}
> calc[3; 5]
16
Function bodies can contain infix operators. The body is preserved unevaluated until the function is called with arguments. In this case, `3 + 5 * 2` evaluates left-to-right as `(3 + 5) * 2 = 16`.
> f: {x * 2}
{x * 2}
> :f
{x * 2}
> g: :f
{x * 2}
> g 5
10
> add: {x + y}
{x + y}
> add3: add[3]
{x + y}[3]
> :add3
{x + y}[3]
> imparse load "2 + 3"
+[2 ; 3]
> imparse '[2 + 3]
+[2 ; 3]
Both `load “code”` (creates TOP node) and `’[code]` (regular list) get transformed into TOP nodes with M-expressions.
> imparse '[2 !]
![2]
> imparse '[5 !]
![5]
> imparse '[10 !]
![10]
The parser transforms postfix unary (a F → F[a]) into M-expression form, returning a TOP node.
> imparse '[2 + 3]
+[2 ; 3]
> imparse '[10 - 3]
-[10 ; 3]
> imparse '[4 * 5]
*[4 ; 5]
The parser transforms infix operators (a op b → op[a; b]) into M-expression form, returning a TOP node.
> imparse '[2 + 3 * 5]
*[+[2 ; 3] ; 5]
> imparse '[10 - 2 + 5]
+[-[10 ; 2] ; 5]
> imparse '[2 * 3 + 4 * 5]
*[+[*[2 ; 3] ; 4] ; 5]
The parser transforms infix chains left-to-right, creating nested M-expressions in a TOP node.
> imparse '[! 10]
![10]
> imparse '[! 10 * 2]
*[![10] ; 2]
> imparse '[! 10 * 2 + 1]
+[*[![10] ; 2] ; 1]
The parser now handles prefix verbs! Two-pass transformation: first prefix (! 10 → ![10]), then infix/postfix.
> imparse '[ord "hello" - 32 chr]
chr[-[ord["hello"] ; 32]]
> imparse '["hello" ord - 32 chr]
chr[-[ord["hello"] ; 32]]
Monadic verbs should stay postfix even when a noun appears to their left; both forms parse identically.
> imparse '[2, + 3]
+[2 ; 3]
> imparse '[2, + 3 * 5]
+[2 ; *[3 ; 5]]
The parser now handles comma threading! When a comma is followed by a verb, it threads the previous value as the first argument.
> imparse load ":x"
get[`x]
> imparse load ":foo"
get[`foo]
The parser transforms GET symbols (:word) into M-expression form: get[`word].
> imparse load "x: 42"
set[`x ; 42]
> imparse load "foo: 99"
set[`foo ; 99]
The parser transforms SET symbols (word: value) into M-expression form: set[`word; value].
> imparse load "x: 2 + 3"
set[`x ; +[2 ; 3]]
> imparse load "y: 5 * 10 + 1"
set[`y ; +[*[5 ; 10] ; 1]]
The parser transforms the RHS of SET through the full transformation pipeline, converting infix to M-expressions.
> imparse load "a: b: 99"
set[`a ; set[`b ; 99]]
> imparse load "x: y: z: 42"
set[`x ; set[`y ; set[`z ; 42]]]
Chained assignments are right-associative: a: b: 99 becomes set[`a; set[`b; 99]].
> imparse load "2 ! 10"
![2 ; 10]
> imparse load "5 ! 20 30"
![5 ; 20 30]
The parser collects trailing nouns after postfix verbs. The evaluator will check arity and throw valence errors if needed.
> 2 ! 10
Error: [project] !: valence error: expected 1 args, got 2
> ![2; 10]
Error: [project] !: valence error: expected 1 args, got 2
When a function receives more arguments than its arity, the evaluator throws a valence error.
> x: 42
42
> :x
42
> y: x + 10
52
> a: b: c: 99
99
> a + b + c
297
GET (:x) and SET (x: value) transformations integrate seamlessly with the rest of the language.
> d: :[`a 1; `b 2 + 2]
:[`a 1; `b 4]
> d[`a]
1
> d[`b]
4
Dictionaries are key-value maps. Created with `:[key val; …]` syntax where keys must be backtick symbols.
> d: :[`a 1; `b 4]
:[`a 1; `b 4]
> d`a
1
> d`b
4
Dictionary values can be accessed using backtick infix syntax: `dict`key`.
> d: :[`a 1; `b 2 + 2]
:[`a 1; `b 4]
> d[`a `b]
[1, 4]
Multiple keys can be accessed at once using space-separated strands. Returns a list of values.
> e: :[]
:[]
> e
:[]
Empty dictionaries are created with `:[]` syntax.
> d: :[`x [1 2 3]; `y "hello"]
:[`x [1 2 3]; `y "hello"]
> d`x
[1 2 3]
> d`y
"hello"
Dictionary values can be any implish type, including lists and strings.
> d: :[`a 1]
:[`a 1]
> echo show type? d
dct!
The type? function returns dct! for dictionaries.
> d: :[`c `d; `e `f `g]
:[`c, `d; `e, `f `g]
> d`c
`d
> d`e
`f `g
When a dictionary value is a backtick symbol, it’s printed with a comma to distinguish it from the key. If the value is a strand of symbols, only the first gets the comma.
ex: - x is negate, x - y is subtraction x + y is addition, + x is transpose or complex conjugate
eq = ne ~: gt > lt < ge >: le <: xr ~: an *. or +. nt -. lid / rid ? (li/ri?)
- unification for rewrite rules
- hehner has two levels (one for expr, one for proofs)
- really just same op with two precedence levels
- quoting might fill the gap?
> rln
> hello
"hello"
This test verifies that rln reads a line from the input stream. The input provider can be customized for different environments (Node.js, browser, testing, etc.)
> name: rln
> Alice
"Alice"
> echo name
Alice
> a: rln
> first
"first"
> b: rln
> second
"second"
> a
"first"
> b
"second"
> imparse '[1 + 2 * ! 10]
*[+[1 ; 2] ; ![10]]
Prefix verbs should bind to their right argument even when an infix verb sits to their left.
> (1 + 2 * ! 10)
0 3 6 9 12 15 18 21 24 27
> imparse '[1 + 2 * ! 10]
*[+[1 ; 2] ; ![10]]
An infix verb with a monadic verb on the right should apply that verb to its arguments before forming the infix M-expression.