nock.js
v1.1.0
Published
Nock interpreter
Readme
nock.js
Nock is a combinator interpreter on nouns. A noun is an atom or a cell. An atom is an unsigned integer of any size; a cell is an ordered pair of nouns.
Nock is the foundational layer of Urbit.
nock.js is a toy interpreter, built for the fun of it.
usage
- install from npm:
npm install nock.js
var nock = require('nock.js')
nock.nock(1, [0, 1])
// => 1- or, clone and run
git clone https://github.com/joemfb/nock.js.git
cd nock.js
node example.js- run unit tests
npm install
npm test- load it in the browser (
nock.jsis a UMD module):
<script src="./nock.js"></script>
<script>
nock.nock(1, [0, 1])
// => 1
</script>methods
See urbit.org/docs/nock/definition/ for an explanation of the pseudocode reduction rules that make up the Nock spec.
nock
nock(a) *a
[a b c] [a [b c]]
*[a [b c] d] [*[a b c] *[a d]]
*a *anock() recursively applies formulas (tail of its argument) to the subject (head of its argument).
nock.nock(1, [0, 1])
// 1Nock nouns are always atoms (unsigned integers) or cells (a pair of nouns). The JS analogue to a cell is Array(2), so nock() converts Array arguments into nouns, associating right:
nock.nock([1, 2, 3], [0, 1])
// [1,[2,3]]The subject can be omitted if the formula doesn't reference it:
nock.nock([1, 1])
// 1Formulas without a subject are evaluated against [1, 0], which is Hoon null (~).
For convenience in evaluating generated formulas, arguments can be passed as strings (see generating formulas below):
nock.nock("[1 2 3]", "[0 1]")
// [1,[2,3]]operators
Nock defines four operators:
?[a b] 0
?a 1
+[a b] +[a b]
+a 1 + a
=[a a] 0
=[a b] 1
=a =a
/[1 a] a
/[2 a b] a
/[3 a b] b
/[(a + a) b] /[2 /[a b]]
/[(a + a + 1) b] /[3 /[a b]]
/a /a- wut (?): test for an atom (1) or cell (0)
- lus (+): increment an atom
- tis (=): test equality
- fas (/): resolve a tree address
See urbit.org/docs/hoon/syntax for an explanation of the the method names
Operators are exported in the nock.operators namespace:
nock.operators.fas(2, [5, 9])
// 5Unlike nock(), operators require both arguments to be present and valid nouns.
formulas
Nock defines 6 primitive formulas:
*[a 0 b] /[b a]
*[a 1 b] b
*[a 2 b c] *[*[a b] *[a c]]
*[a 3 b] ?*[a b]
*[a 4 b] +*[a b]
*[a 5 b] =*[a b]- slot (0): resolve a tree address
- constant (1): return the formula regardless of subject
- evaluate (2): evaluate the product of second formula against the product of the first
- cell (3): test if the product is a cell
- incr (4): increment the product
- eq (5): test for equality between nouns in the product
And five additional formulas, reducible to the 6 above:
*[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b]
*[a 7 b c] *[a 2 b 1 c]
*[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c]
*[a 9 b c] *[a 7 c 2 [0 1] 0 b]
*[a 10 [b c] d] *[a 8 c 7 [0 3] d]
*[a 10 b c] *[a c]- ife (6): if/then/else
- compose (7): evaluate formulas composed left-to-right
- extend (8): evaluate the second formula against [product of first, subject]
- invoke (9): construct a core and evaluate one of it's arms against it
- hint (10): skip first formula, evaluate second
Formulas are exported in the nock.formulas namespace:
nock.nock(2, [8, [[4, [1, 1]], [0, 1]]])
// [2, 2]
nock.formulas.eq(2, [8, [[4, [1, 1]], [0, 1]]])
// 0As in the case of nock.operators, formulas require both arguments to be present and valid nouns.
Formulas 6-10 are implemented in two ways: directly by the interpreter, and as evaluated macro expansions:
nock.formulas.extend(42, [[4, [0, 1]], [0, 1]])
// [43, 42]
nock.formulas.macroExtend(42, [[4, [0, 1]], [0, 1]])
// [43, 42]By default, the direct implementations are used. To use the macro expansions instead:
nock.useMacros()
nock.nock(42, [8, [[4, [0, 1]], [0, 1]]])
// [43, 42]generating formulas
Hoon, the native language of Urbit, is also the language of dojo, the Urbit shell. Hoon expressions are compiled into Nock formulas, which are interpreted.
~novlen-hanweb:dojo> 1
1Hoon -> Nock compilation is a first class primitive in Hoon (:code or !=):
~novlen-hanweb:dojo> !=(1)
[1 1]as is the Nock formula itself (:nock or .*):
~novlen-hanweb:dojo> .*(. [1 1])
1
~novlen-hanweb:dojo> .*(. !=(1))
1As previously noted, nock() can evaluate formulas passed as strings:
nock.nock('[1 1]')
// => 1For convenience, convert your Nock formula to a string directly in dojo (technically, print your noun to a tape):
~novlen-hanweb:dojo> <!=(1)>
"[1 1]"generating decrement
Hoon provides a decrement function in it's standard library:
~novlen-hanweb:dojo> (dec 10)
9The implemention for dec is in a core - compiling it to Nock results in a formula that references that core, not a standalone decrement implementation:
~novlen-hanweb:dojo> !=((dec 10))
[8 [9 24.834.031 0 31] 9 2 [0 4] [7 [0 3] 1 10] 0 11]There's probably a way to de-reference that core address (24.834.031) and compile the implementation - but I don't know it... Instead, we can evaluate the standard-library decrement implementation directly:
++ dec :: decrement
~/ %dec
|= a/@
~| %decrement-underflow
?< =(0 a)
=+ b=0
|- ^- @
?: =(a +(b)) b
$(b +(b))from urbit.org/docs/hoon/library/1a/#-dec
Converting the linked implementation from tall-form to flat-form (and removing the hint instructions) results in this expression:
|=(a/@ ?<(=(0 a) =+(b=0 |-(^-(@ ?:(=(a +(b)) b $(b +(b))))))))which we can evaluate:
~novlen-hanweb:dojo> (|=(a/@ ?<(=(0 a) =+(b=0 |-(^-(@ ?:(=(a +(b)) b $(b +(b)))))))) 10)
9compile to Nock (with !=):
~novlen-hanweb:dojo> !=((|=(a/@ ?<(=(0 a) =+(b=0 |-(^-(@ ?:(=(a +(b)) b $(b +(b)))))))) 10))
[ 8
[8 [1 0] [1 6 [5 [1 0] 0 6] [0 0] 8 [1 0] 8 [1 6 [5 [0 30] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1] 0 1]
9
2
[0 4]
[7 [0 3] 1 10]
0
11
]evaluate the compiled Nock (with .*):
~novlen-hanweb:dojo> .*(~ !=((|=(a/@ ?<(=(0 a) =+(b=0 |-(^-(@ ?:(=(a +(b)) b $(b +(b)))))))) 10)))
9print to a tape (with <...>):
~novlen-hanweb:dojo> <!=((|=(a/@ ?<(=(0 a) =+(b=0 |-(^-(@ ?:(=(a +(b)) b $(b +(b)))))))) 10))>
"[8 [8 [1 0] [1 6 [5 [1 0] 0 6] [0 0] 8 [1 0] 8 [1 6 [5 [0 30] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1] 0 1] 9 2 [0 4] [7 [0 3] 1 10] 0 11]"and, finally, evaluate in nock.js:
nock.nock("[8 [8 [1 0] [1 6 [5 [1 0] 0 6] [0 0] 8 [1 0] 8 [1 6 [5 [0 30] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1] 0 1] 9 2 [0 4] [7 [0 3] 1 10] 0 11]")
// 9To get up and and running with an urbit, see github.com/urbit/urbit and urbit.org/docs/using/install/.
