A stack-based programming language that compiles to JavaScript.
"Hello, World!" peek;
In jth, values are pushed onto a stack. Operators pop their arguments off the stack, do their work, and push results back. It is a simple model that turns out to be surprisingly powerful.
npm install -g jth-clijth run hello.jthjth run -c '2 3 + peek;'
# Output: 5jth compile program.jth output.mjsjthREPL dot-commands: .help, .peek, .count, .clear, .stack, .exit
Every value you write gets pushed onto the stack:
42; // stack: [42]
"hello"; // stack: [42, "hello"]
true; // stack: [42, "hello", true]
The peek operator logs the top of the stack without removing it. The peek-all operator logs the entire stack.
42 peek; // logs: 42, stack: [42]
1 2 3 peek-all; // logs: 1 2 3, stack: [1, 2, 3]
Supported value types:
| Type | Examples |
|---|---|
| Numbers | 1, 3.14, 0xFF, -5 |
| Strings | "hello", 'world', `tmpl` |
| Booleans | true, false |
| Null/Undef | null, undefined |
| Arrays | [1 2 3] (space-separated) |
| Objects | { "key" "value" "k2" 42 } |
Semicolons separate statements.
Operators pop their arguments from the stack and push back the result:
2 3 +; // stack: [5] — pops 2 and 3, pushes 5
10 3 -; // stack: [7] — pops 10 and 3, pushes 7
4 5 *; // stack: [20]
15 4 /; // stack: [3.75]
17 5 %; // stack: [2] — modulo
2 8 **; // stack: [256] — exponentiation
You can chain operations. The stack carries values forward:
2 3 + 4 *; // (2 + 3) * 4 = 20, stack: [20]
These operators rearrange what is on the stack:
5 dupe; // stack: [5, 5] — duplicate the top
3 7 swap; // stack: [7, 3] — swap top two
1 2 3 drop; // stack: [1, 2] — discard the top
1 2 3 count; // stack: [1, 2, 3, 3] — push the stack depth
clear; // stack: [] — empty the stack
reverse reverses the entire stack. copy duplicates the entire stack. collect gathers all items into an array.
A block is an anonymous function wrapped in #[ ]. It operates on the stack when executed:
#[ dupe * ] :square; // define "square" as an operator
5 square peek; // 25
:name pops the top of the stack and registers it as a named operator. Blocks become callable operators; plain values (numbers, strings) are pushed onto the stack when the operator is used.
3.14159 :PI;
PI peek; // 3.14159 — PI pushes the value onto the stack
::name pops a value into a JavaScript const. This is primarily used for interop with inline JS — it is not accessible as a jth operator name.
if — pops a condition, a true-block, and a false-block:
// Syntax: #[ false-block ] #[ true-block ] condition if
#[ "odd" ] #[ "even" ] 10 2 % 0 = if peek;
// 10 % 2 = 0, 0 = 0 is true, so "even" is printed
The order on the stack before if executes is: false-block (bottom), true-block, condition (top). In code you write the false-block first, then the true-block, then the condition.
if/elseif/else chains — flat conditional chaining without nesting:
dupe 15 % 0 = #[ drop "FizzBuzz" ] swap if
dupe 3 % 0 = #[ drop "Fizz" ] swap elseif
dupe 5 % 0 = #[ drop "Buzz" ] swap elseif
#[ ] else
if in 2-arg mode starts a chain. elseif checks additional conditions. else is the fallback. Only the first matching branch executes.
times — runs a block N times:
#[ "hi" peek ] 3 times;
// logs "hi" three times
when — keeps a value only if the condition is truthy:
42 true when; // stack: [42]
42 false when; // stack: []
try wraps a block and catches any error:
#[ "oops" throw ] try; // stack: [Error("oops")]
error? peek; // true
throw pops a message and throws an error. error? checks whether the top of the stack is an Error.
Arrays use square brackets with space-separated values:
[1 2 3] peek; // [1, 2, 3]
[1 2 3] 4 push peek; // [1, 2, 3, 4]
[1 2 3] ...; // spreads onto stack: 1, 2, 3
Array operators include push, pop, shift, unshift, flatten, suppose (add to collection), ... (spread), and collect (gather stack into array).
"hello" upper peek; // "HELLO"
"hello" "world" strcat peek; // "helloworld"
"hello world" len peek; // 11
" hello " trim peek; // "hello"
Objects use { } with alternating key-value pairs:
{ "name" "jth" "version" 2 } peek;
// { name: "jth", version: 2 }
{ "a" 1 "b" 2 } keys peek; // ["a", "b"]
{ "a" 1 "b" 2 } values peek; // [1, 2]
Both // and # start comments. // also acts as a statement terminator:
42 peek // this logs 42
7 peek // and this logs 7
Import operators from another jth file:
::import "./math.jth" { square cube };
5 square peek; // 25
Export operators from a module:
#[ dupe * ] :square;
#[ dupe dupe * * ] :cube;
::export square cube;
Prefix a number to an operator to create a partial application. The semantics are N op x — the prefix number comes first:
10 3+; // 3 + 10 = 13 (commutative, same either way)
7 2*; // 2 * 7 = 14 (commutative)
100 10log; // log base 10 of 100 = 2
8 2log; // log base 2 of 8 = 3
For non-commutative operators, the prefix number is the left operand: 3- means 3 - x, 2/ means 2 / x.
Double parentheses embed raw JavaScript. The expression is treated as a value:
((Math.random())) peek; // random number
((x => x * 2)) :double;
5 double peek; // 10
_ awaits a promise. __ runs Promise.all on an array of promises:
((fetch("https://api.example.com/data"))) _ peek;
| Operator | Description | Example |
|---|---|---|
+ / plus |
Addition | 2 3 + => 5 |
- / minus |
Subtraction | 10 3 - => 7 |
* / mul |
Multiplication | 4 5 * => 20 |
/ / div |
Division | 15 4 / => 3.75 |
% / mod |
Modulo | 17 5 % => 2 |
%% |
Remainder | 17 5 %% => 2 |
** / pow |
Exponentiation | 2 8 ** => 256 |
++ |
Increment | 5 ++ => 6 |
-- |
Decrement | 5 -- => 4 |
abs |
Absolute value | -5 abs => 5 |
sqrt / √ |
Square root | 16 sqrt => 4 |
floor |
Floor | 3.7 floor => 3 |
ceil |
Ceiling | 3.2 ceil => 4 |
round |
Round | 3.5 round => 4 |
trunc |
Truncate | 3.9 trunc => 3 |
log |
Natural logarithm | 1 log => 0 |
⋅ |
Multiply (unicode) | 4 5 ⋅ => 20 |
÷ |
Divide (unicode) | 15 3 ÷ => 5 |
| Operator | Description | Example |
|---|---|---|
Σ |
Sum all stack values (variadic) | 1 2 3 Σ => 6 |
Π |
Product of all stack values (variadic) | 2 3 4 Π => 24 |
min |
Minimum of all stack values (variadic) | 3 7 1 min => 1 |
max |
Maximum of all stack values (variadic) | 3 7 1 max => 7 |
| Operator | Description | Example |
|---|---|---|
noop / ∅ |
Do nothing | 5 noop => stack unchanged |
clear |
Clear the stack | 1 2 3 clear => [] |
... |
Spread (array to stack) | [1 2 3] ... => 1 2 3 on stack |
drop |
Remove top | 1 2 drop => [1] |
dupe / dup |
Duplicate top | 5 dupe => [5, 5] |
copy |
Duplicate entire stack | 1 2 copy => [1, 2, 1, 2] |
swap |
Swap top two | 1 2 swap => [2, 1] |
over |
Copy second to top | 1 2 over => [1, 2, 1] |
rot |
Rotate third to top | 1 2 3 rot => [2, 3, 1] |
reverse |
Reverse stack | 1 2 3 reverse => [3, 2, 1] |
count / depth |
Push stack depth | 1 2 count => [1, 2, 2] |
collect |
All items to array | 1 2 3 collect => [[1, 2, 3]] |
peek |
Peek/log top | 42 peek => logs 42 |
peek-all |
View/log all | 1 2 peek-all => logs 1 2 |
| Operator | Description | Example |
|---|---|---|
= / eq? |
Strict equal | 3 3 = => true |
== |
Loose equal | 3 "3" == => true |
!= / ne? |
Not equal | 3 4 != => true |
< / lt? |
Less than | 2 5 < => true |
<= / le? |
Less than or equal | 3 3 <= => true |
> / gt? |
Greater than | 5 2 > => true |
>= / ge? |
Greater than or equal | 3 3 >= => true |
<=> |
Spaceship (three-way) | 3 5 <=> => -1 |
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | true false && => false |
|| |
Logical OR | true false || => true |
xor |
Exclusive OR | true false xor => true |
nand |
NOT AND | true true nand => false |
nor |
NOT OR | false false nor => true |
~~ / not |
Logical NOT | true ~~ => false |
| Operator | Description | Example |
|---|---|---|
if |
Conditional branch (3-arg) | #[ "no" ] #[ "yes" ] true if => "yes" |
if |
Start conditional chain (2-arg) | #[ "yes" ] true if |
elseif |
Chain conditional | #[ "alt" ] cond elseif |
else |
Default branch | #[ "default" ] else |
when |
Keep if truthy | 42 true when => 42 |
drop-when |
Drop if truthy | 42 true drop-when => [] |
keep-if |
Keep value if truthy | 42 true keep-if => 42 |
drop-if |
Drop value if truthy | 42 true drop-if => [] |
times |
Repeat block N times | #[ "hi" peek ] 3 times |
while |
Loop while condition truthy | #[ body ] #[ cond ] while |
until |
Loop until condition truthy | #[ body ] #[ cond ] until |
break |
Exit current loop | break |
| Operator | Description | Example |
|---|---|---|
try |
Catch errors from block | #[ "fail" throw ] try => Error |
throw |
Throw an error | "oops" throw |
error? |
Check if top is Error | err error? => true |
| Operator | Description | Example |
|---|---|---|
len |
Length | "hello" len => 5 |
upper |
Uppercase | "hi" upper => "HI" |
lower |
Lowercase | "HI" lower => "hi" |
trim |
Trim whitespace | " hi " trim => "hi" |
strcat |
Concatenate | "ab" "cd" strcat => "abcd" |
strseq |
Reverse concat | "ab" "cd" strseq => "cdab" |
| Operator | Description | Example |
|---|---|---|
typeof |
Push type string | 42 typeof => "number" |
number? |
Is number? | 42 number? => true |
string? |
Is string? | "hi" string? => true |
array? |
Is array? | [1] array? => true |
nil? |
Is null/undefined? | null nil? => true |
function? |
Is function? | #[ ] function? => true |
empty? |
Is empty? | "" empty? => true |
contains? |
Contains element? | [1 2 3] 2 contains? => true |
starts? |
Starts with prefix? | "hello" "hel" starts? => true |
ends? |
Ends with suffix? | "hello" "llo" ends? => true |
| Operator | Description | Example |
|---|---|---|
push |
Append to array | [1 2] 3 push => [1,2,3] |
pop |
Remove last (pushes array and item) | [1 2 3] pop => [1,2] and 3 |
shift |
Remove first (pushes array and item) | [1 2 3] shift => [2,3] and 1 |
unshift |
Prepend to array | [2 3] 1 unshift => [1,2,3] |
suppose |
Add to collection | [1 2] 3 suppose => [1,2,3] |
flatten |
Flatten all stack values (variadic) | [1] [2 3] flatten => 1 2 3 on stack |
map |
Apply block to each element | [1 2 3] #[ 2 * ] map => [2,4,6] |
filter |
Keep elements where block is truthy | [1 2 3 4] #[ 2 % 0 = ] filter => [2,4] |
reduce |
Accumulate with block and init | [1 2 3] 0 #[ + ] reduce => 6 |
fold |
Alias for reduce (catamorphism) | [1 2 3] 0 #[ + ] fold => 6 |
bend |
Unfold/anamorphism from seed | 1 #[ 5 <= ] #[ dupe 1 + ] bend => [1,2,3,4,5] |
| Operator | Description | Example |
|---|---|---|
keys |
Get keys | { "a" 1 } keys => ["a"] |
values |
Get values | { "a" 1 } values => [1] |
entries |
Get entries | { "a" 1 } entries => [["a",1]] |
merge |
Merge objects | obj1 obj2 merge |
record |
Build from pairs | [["a" 1]] record => {a: 1} |
| Operator | Description | Example |
|---|---|---|
into-json |
Stringify to JSON | { "a" 1 } into-json => '{"a":1}' |
from-json |
Parse JSON string | '{"a":1}' from-json => {a: 1} |
to-json |
Parse JSON (legacy alias for from-json) | '{"a":1}' to-json => {a: 1} |
into-lines |
Join by newline | ["a" "b"] into-lines => "a\nb" |
from-lines |
Split by newline | "a\nb" from-lines => ["a","b"] |
to-lines |
Split by newline (legacy alias for from-lines) | "a\nb" to-lines => ["a","b"] |
| Operator | Description | Example |
|---|---|---|
_ |
Await a promise | promise _ |
__ |
Promise.all | [p1 p2] __ |
| Operator | Description | Example |
|---|---|---|
apply / exec |
Execute block | #[ 2 3 + ] apply => 5 |
$ |
Execute block (legacy) | #[ 2 3 + ] $ => 5 |
$$ |
Execute and spread | #[ 2 3 + ] $$ |
<<- |
Rewind all | Moves pointer to start |
->> |
Skip all | Moves pointer to end |
| Operator | Description | Example |
|---|---|---|
each |
Apply block to each stack item | 1 2 3 #[ 2 * ] each |
fanout |
Run value through multiple blocks | 5 #[ 2 * ] #[ 1 + ] fanout => 10, 6 |
zip |
Pair elements from two arrays | [1 2] ["a" "b"] zip => [[1,"a"],[2,"b"]] |
compose |
Combine blocks into pipeline | #[ 2 * ] #[ 1 + ] compose |
| Operator | Description | Example |
|---|---|---|
iter |
Create iterator | [1 2 3] iter |
next |
Get next value | iterator next |
.. |
Exhaust to array | iterator .. |
| Operator | Description | Example |
|---|---|---|
fibonacci |
Fibonacci step | 0 1 fibonacci => 1 0 1 (pushes b, a, a+b) |
All statistics operators are variadic — they consume the entire stack.
| Operator | Description | Example |
|---|---|---|
mean / x̄ |
Arithmetic mean | 1 2 3 mean => 2 |
median |
Median value | 1 2 3 median => 2 |
mode |
Most frequent | 1 1 2 mode => 1 |
modes |
All modes | 1 1 2 2 modes => [1, 2] |
| Operator | Description | Example |
|---|---|---|
*** |
Tetration | 2 3 *** |
**** |
Pentation | 2 3 **** |
Prefix any number to an arithmetic operator. Semantics: N op x (N is left operand):
| Pattern | Description | Example |
|---|---|---|
N+ |
N + x | 10 3+ => 13 |
N- |
N - x | 20 3- => -17 (3 - 20) |
N* |
N * x | 7 2* => 14 |
N/ |
N / x | 50 100/ => 2 (100 / 50) |
N% |
N % x | 17 5% => 5 |
N** |
N ** x | 3 2** => 8 (2 ** 3) |
Nlog |
Log base N of x | 100 10log => 2 |
jth run <file> # Compile and execute a .jth file
jth run -c '<code>' # Run inline jth code
jth compile <file> [output] # Compile .jth to .mjs
jth compile -c '<code>' # Compile inline code to stdout
jth --version, -v # Show version
jth --help, -h # Show help
jth is organized as a monorepo with 7 packages:
| Package | Description |
|---|---|
| jth-runtime | Stack VM, processN, op() helper, operator registry |
| jth-compiler | Lexer, parser, code generator, transform pipeline |
| jth-stdlib | Standard library (~110 operators) |
| jth-cli | Command-line interface for running and compiling |
| jth-repl | Interactive REPL |
| jth-ai | Ollama AI integration |
| jth-types | Internal type definitions |
# Install dependencies
npm install
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run end-to-end tests
npm run test:e2e