Jock
A Nock combinator language

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.

1. The public alpha is the original release, from zorp-corp.
2. Development of a release candidate continues in a successor repository (currently private), expanding the language's features and capabilities.

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.

Jock→ Nock
var a: Bool = true;
a = false;
a
compiles to
[8 [1 0] 7 [10 [2 1 1] 0 1] 0 2]
Write real Jock programs first, only meeting the Nock ISA noun underneath when it starts to matter.

02 » Examples

A few programs from the examples folder — each one a complete, executable program.

point.jock — operator traits on a sealed class
operator traits, classes, Float64 — p + q dispatches through class conformance
struct 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
sort.jock — generic insertion sort
bounded generics, T: Lt, operators as bounds — the same < becomes a trait call
func<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])
stoplight.jock — a state-machine kernel
a zero-ceremony NockApp kernel — the compiler synthesizes load/peek/poke from the Kernel protocol
enum 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",
    }
  };
};
counter.jock — the zero-ceremony counter kernel
impl Kernel, state threading — no driver, no import: this kernel builds and runs unchanged under jojo, the generic NockApp host
struct 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 )
counter-v2.jock — the upgrade target for counter
load as migration — same host, a new state shape; load's as! carries counter's exported state into total
struct 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

TrackRepositoryStatus
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

KeywordSyntax
letlet x: @ = 5;
varvar b: @ = 0; b = b + 1;
funcfunc inc(n: @) -> @ { n + 1 }
lambdalet f = lambda(x: @) -> @ { x + 1 };
if / elseif a == 3 { 42 } else { 17 }
matchmatch a { %1 => 0, _ => 84, }
loop / recurloop { if a == b { b } else { b = b + 1; recur } }
evaleval(subj, form)
crash_ => crash;
importimport urbit;
as / as? / as!(n as? Byte) ?? 'x'
assertassert n > 0;
printprint("count = {n}");
and / or / xor / notif a and not b { 1 } else { 0 }
structstruct PointState { x: Float64, y: Float64 }
class / implclass Point(PointState) impl Arithmetic { … }
impl … forimpl Arithmetic for Float64 { … }
traittrait Arithmetic { add(self: Self, other: Self) -> Self; }
alias … isalias Number is Float64;
enumenum Shape { point, circle(Float64) }
inx 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.

PunctuationMeaning
;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
%footerm 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
\0astring 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 firstNotes
a.b   a.<   a.>   a.+5field, 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
<   >   <=   >=   innon-associative: a < b < c is a parse error, not a boolean surprise. ∈ aliases in
==   !=   ===   !==non-associative
not   and   xor   orword 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.

GlyphMeaningSyntax
;statement terminatorlet x = 5; x
,separator — tuples, lists, sets, arguments, type arguments, struct fieldsf(a, b)   (1, 2, 3)
:type annotationlet x: @ = 5;
.field access; also the lark and axis forms belowp.x
.<   .>lark: head and tail, composing outermost-firstp.<   p.>   p.<>
.+naxis, named directly — always a literalp.+6
->return type; also keys map-literal pairsfunc f(n: @) -> @ { n }   {1 -> 7}
=>separates a case pattern from its bodymatch v { 1 => 0; _ => 84; }
?optionality, and only optionalitylet o: @? = ~;
??coalesce — unwrap or fall backo ?? 9
~the null / empty literallet o: @? = ~;
_wildcard pattern_ => 84,
.namea case pattern; a bare name is a binding.circle(r) => r,
%term literal; also modulus%foo   a % b
@the any-aura atom typelet x: @ = 5;
|joins types into an untagged unionalias Maybe is @ | (@, @);
( )grouping, tuples, calls; glued to a type name, construction(a, b)   f(x)   Point( x: 1 )
< >glued to a type name, type applicationList<@>   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 assignmentvar b = 0; b = b + 1;
==   !=equality through the Eq traitif a == 3 { 1 } else { 0 }
===   !==structural identity — never overloadablea === b
<   >   <=   >=ordered comparison — non-associative, parenthesizea < b
+   -   *   /   %arithmetic; binary - must be spaced on both sidesa + b   a - 1
\NNinside 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.