Jock
Jock compiles a high-level syntax to Nock, the twelve-opcode combinator calculus underneath Nockchain and Urbit, among other platforms. Jock models its syntax on Swift and Rust, aiming to be more approachable than Hoon while still permitting direct expression of Nock concepts: classes, generics, pattern matching and operator traits, all the way down to raw nouns.
01 » Compiles to Nock
Every Jock program erases to a noun and a formula. Structs become right-nested tuples, enums become tagged cells, and the whole language bottoms out in twelve instructions.
var a: Bool = true; a = false; a
[8 [1 0] 7 [10 [2 1 1] 0 1] 0 2]
02 » Examples
A few programs from the examples folder — each one a complete, executable program.
p + q dispatches through class conformancestruct PointState { x: Float64, y: Float64 };
class Point(PointState) impl Add, Sub {
add(self: Self, p: Self) -> Self {
Point( x: self.x + p.x, y: self.y + p.y )
};
sub(self: Self, p: Self) -> Self {
Point( x: self.x - p.x, y: self.y - p.y )
};
};
let p = Point( x: 101.0, y: 105.0 );
let q = Point( x: 42.0, y: 7.0 );
(p + q).x
T: Lt, operators as bounds — the same < becomes a trait callfunc<T: Lt> insert(x: T, xs: List<T>) -> List<T> {
match xs {
(h, t) => if x < h { (x, xs) } else { (h, insert(x, t)) },
_ => [x],
}
};
func<T: Lt> isort(xs: List<T>) -> List<T> {
match xs {
(h, t) => insert(h, isort(t)),
_ => ~,
}
};
isort([5, 3, 8, 1, 9, 2])
Kernel protocolenum Color { red, green, yellow };
struct LightState { c: Color };
func advance(c: Color) -> Color {
match c {
.red => Color.green,
.green => Color.yellow,
.yellow => Color.red,
}
};
impl Show for Color {
show(self: Color) -> String {
match self {
.red => "red",
.green => "green",
.yellow => "yellow",
}
};
};
impl Kernel, state threading — no driver, no import: this kernel builds and runs unchanged under jojo, the generic NockApp hoststruct KS { n: @ };
class Counter(KS) impl Kernel {
load(self: Counter, old: *) -> Counter { old as! Counter };
peek(self: Counter, p: *) -> * { self.n };
poke(self: Counter, e: *) -> (List<Effect>, Counter) { (~, Counter( n: self.n + 1 )) };
};
Counter( n: 0 )
load's as! carries counter's exported state into totalstruct KS2 { n: @, total: @ };
class CounterV2(KS2) impl Kernel {
load(self: CounterV2, old: *) -> CounterV2 { CounterV2( n: old as! @, total: 100 ) };
peek(self: CounterV2, p: *) -> * { (self.n, self.total) };
poke(self: CounterV2, e: *) -> (List<Effect>, CounterV2) { (~, CounterV2( n: self.n + 1, total: self.total + 1 )) };
};
CounterV2( n: 0, total: 100 )
03 » Repositories
| Track | Repository | Status |
|---|---|---|
| Alpha | zorp-corp/jock-lang | public (original release) |
| Recommenced | sigilante/jock | private (ongoing development) |
Jock is a one-man rewrite in active development. The public alpha remains the reference for the original language release by Zorp in June 2025; new feature work is happening in the recommenced repository as of August 2026.
04 » Keywords
| Keyword | Syntax |
|---|---|
| let | let x: @ = 5; |
| var | var b: @ = 0; b = b + 1; |
| func | func inc(n: @) -> @ { n + 1 } |
| lambda | let f = lambda(x: @) -> @ { x + 1 }; |
| if / else | if a == 3 { 42 } else { 17 } |
| match | match a { %1 => 0, _ => 84, } |
| loop / recur | loop { if a == b { b } else { b = b + 1; recur } } |
| eval | eval(subj, form) |
| crash | _ => crash; |
| import | import urbit; |
| as / as? / as! | (n as? Byte) ?? 'x' |
| assert | assert n > 0; |
| print("count = {n}"); | |
| and / or / xor / not | if a and not b { 1 } else { 0 } |
| struct | struct PointState { x: Float64, y: Float64 } |
| class / impl | class Point(PointState) impl Arithmetic { … } |
| impl … for | impl Arithmetic for Float64 { … } |
| trait | trait Arithmetic { add(self: Self, other: Self) -> Self; } |
| alias … is | alias Number is Float64; |
| enum | enum Shape { point, circle(Float64) } |
| in | x in s |
defer is reserved (%defer) but its runtime semantics aren't pinned
yet, so it's left off the table above rather than shown with an invented example.
05 » Punctuation & operators
Every built-in operator has an ASCII-first spelling. The punctuator inventory is
$jpunc in /sur/jock; the precedence table was frozen 2026-08-06
and changes to it are version boundaries, not amendments.
| Punctuation | Meaning |
|---|---|
| ; | statement terminator. Required between items; optional on the last one before }, so a dropped separator is a loud parse error rather than a silent mis-parse |
| , | the separator everywhere — tuples, lists, sets, arguments, type arguments. A trailing comma is permitted before every closing delimiter |
| : | type annotation — let x: @ = 5; |
| -> | return type, and the key/value arrow in map literals — {1 -> 7} |
| => | case arm in match |
| ~ | the null / empty literal. Its own type (%sing %n 0): a numeric zero is not null |
| _ | wildcard pattern |
| %foo | term literal. Hyphens are part of the name — %list-notes is one token |
| @ | the any-aura atom type |
| | | type-position union only (A | B). Retired from the operator inventory entirely |
| & | reserved through 1.0. Every occurrence refuses with prose (%lex-connective) — use and, or hoon.dis for bitwise |
| \0a | string escape: one byte as two hex digits (\09 tab, \\ backslash). Per-byte, so UTF-8 spells itself — "caf\c3\a9" is café. Anything else after a backslash refuses |
| {{ }} | literal braces inside an interpolated string |
| // | comment to end of line |
The adjacency law. Postfix forms bind only when glued. f(x) is a call
and f (x) is not; xs[0] is an index and xs [0] is not;
Point( x: 1 ) is construction while a free-standing { … } is a block.
An un-glued ( after a complete expression is always a parse error with a hint —
never a different meaning.
| Operators, tightest first | Notes |
|---|---|
| a.b a.< a.> a.+5 | field, then lark head/tail, then a literal axis. p.<> is the tail of the head |
| as as? as! | static cast / soft cast to a unit / forced cast |
| * / % | × and ÷ are aliases |
| + - | binary - is spaced on both sides: a- 1 and a -1 refuse. Fully glued a-1 is one hyphenated name; -42 in operand position is an Int literal |
| ?? | coalesce, unwrap-or-default. Binds tighter than the comparisons, so m[k] ?? 0 == 100 groups the way it reads |
| < > <= >= in | non-associative: a < b < c is a parse error, not a boolean surprise. ∈ aliases in |
| == != === !== | non-associative |
| not and xor or | word connectives, loosest — not a == b reads not (a == b) |
** and unary - are reserved through 1.0: they lex and parse at frozen
precedence but refuse at desugar, so their placement cannot be renegotiated when they land.
Ten Unicode glyphs (⊛ ∩ ⊗ ∘ ⋅ ∪ ⊕ ⊖ ± ⊂)
are trait-only: the language lexes and parses them and assigns no meaning, so a library
supplies one through the ops registry.
05 » Punctuators
Everything the language spells with punctuation rather than a word. Each glyph has one meaning; where two readings were possible the grammar picks one and refuses the other loudly, so nothing here is context-sensitive.
| Glyph | Meaning | Syntax |
|---|---|---|
| ; | statement terminator | let x = 5; x |
| , | separator — tuples, lists, sets, arguments, type arguments, struct fields | f(a, b) (1, 2, 3) |
| : | type annotation | let x: @ = 5; |
| . | field access; also the lark and axis forms below | p.x |
| .< .> | lark: head and tail, composing outermost-first | p.< p.> p.<> |
| .+n | axis, named directly — always a literal | p.+6 |
| -> | return type; also keys map-literal pairs | func f(n: @) -> @ { n } {1 -> 7} |
| => | separates a case pattern from its body | match v { 1 => 0; _ => 84; } |
| ? | optionality, and only optionality | let o: @? = ~; |
| ?? | coalesce — unwrap or fall back | o ?? 9 |
| ~ | the null / empty literal | let o: @? = ~; |
| _ | wildcard pattern | _ => 84, |
| .name | a case pattern; a bare name is a binding | .circle(r) => r, |
| % | term literal; also modulus | %foo a % b |
| @ | the any-aura atom type | let x: @ = 5; |
| | | joins types into an untagged union | alias Maybe is @ | (@, @); |
| ( ) | grouping, tuples, calls; glued to a type name, construction | (a, b) f(x) Point( x: 1 ) |
| < > | glued to a type name, type application | List<@> Map<@->@> func<T> |
| [ ] | list and map literals, told apart by the arrow; glued, an index or a put | [1, 2, 3] [1 -> 100] [] [->] xs[0] |
| { } | a block, and a declaration body — nothing else | { n + 1 } |
| = | binding and assignment | var b = 0; b = b + 1; |
| == != | equality through the Eq trait | if a == 3 { 1 } else { 0 } |
| === !== | structural identity — never overloadable | a === b |
| < > <= >= | ordered comparison — non-associative, parenthesize | a < b |
| + - * / % | arithmetic; binary - must be spaced on both sides | a + b a - 1 |
| \NN | inside a string, one byte as two hex digits; \n \t \" \' \\ are sugar over it | "line\0aline" "line\nline" |
The adjacency law. A postfix (, [ or {
attaches to what precedes it iff no whitespace separates them. So
fib(n) is a call and fib (n) is not;
Point( x: 1 ) is construction while a free-standing
{ … } is a block. An un-glued one is always a parse error
with a hint, never a different meaning.
Unicode aliases. × ÷ ±
⊕ ⊗ ⊖ ∘ ·
∩ ∪ ∈ ⊂ ⊃
⊛ lex as ordinary punctuators —
3 × 4 and 2 ∈ s are the arithmetic
and membership you would expect. ASCII spellings are always available.
Reserved, and loud about it. & is reserved through 1.0 and
every occurrence refuses with prose — && is not Jock, the
connectives are the words and, or, not. A glued
|| refuses the same way; single | stays the type-union punctuator.
$(…) is reserved for recursion re-entry with edits but has no parser path
yet, so recursion today is by name for a func and bare recur for a
loop. Unary - and ** are reserved through 1.0.