Welcome to Jump! Jump is a hard-to-read esoteric programming language. It has curiosity value and very little else, but it's a fun puzzle to write a program or two, and it'll help you learn about stack-based programming.
This repo acts as both the language spec and the source for a Node.JS-based interpreter.
Before getting into the language concepts, let's talk about how to run Jump code.
First, clone this repo.
nvm use
Uses nvm
to select the correct version of Node.JS for the interpreter, and
npm install --production
will install the dependencies you need (very few, and even those are nice-to-haves)
From here, you have a few options.
You can really use any file extension, but if you have some code in a file, you can run it with
node jump.js path/to/file.jump
If you just want to play with tiny one-liners, you can give them directly to the interpreter with the -e
or --exec
flag:
node jump.js -e '12+^'
Jump programs are strings of single control characters which represent instructions to the interpreter. Execution progresses through the program, instruction by instruction, left to right. Sometimes the flow will be sent elsewhere by an instruction that breaks the usual one-step-by-one-step movement.
Three pieces of persistent information are kept track of during the execution of a Jump program: the execution cursor, the stack and the flag mapping.
The location in the code string which is currently being executed is referred to as the execution cursor, and by default it increments by one after each instruction (so, in most cases, the execution cursor begins as 0
, then 1
, then 2
, etc until changed by a flow control instruction).
The stack is the main data-storage location of a Jump program. It's the only one of the persistent locations which is dynamic in size (and theoretically of infinite capacity, though not really). It is always accessible, and is acted on in some way by most of Jump's instructions.
Jump's stack is entirely made up of integers (positive or negative). Functions to encode and decode readable Unicode characters are / will be available, but they'll always be represented on-stack as integers.
As with any good stack, only two operations are defined: push and pop. Pushing a value puts it on the top of the stack, and popping retrieves (and removes) the top value from the stack.
As an example, imagine the stack looks like:
3 <-- Top
2
1 <-- Bottom
If we push the values 5, and then 4, we'll end up with:
4 <-- Top
5
3
2
1 <-- Bottom
If we now pop three values from the stack, we'll have:
2 <-- Top
1 <-- Bottom
At program start, the stack is empty.
The flag mapping is the final piece of persistent information kept in a Jump program.
Flags are pointers to code addresses referenced by integer labels.
So, you might ask your program to create a flag at the current execution cursor (let's say it's 10
), with label 3
.
This is called "flag 3".
The interpreter will remember that flag 3 refers to the location 10
in code.
In future, should your code hit an instruction to jump to flag 3, the next instruction executed will be the one directly after
the location of flag 3, ie 11
.
In this way, flags act as labelled goto
s.
On program start, the flag mapping is empty.
Program execution starts at the location of the first _
instruction (there should be at most one), or at the first instruction of the code string if no _
is found.
Programs terminate if the execution cursor leaves the end of the code string, or if a x
(TERMINATE
) instruction is executed.
0
,1
,2
, ...,9
: Push the given integerx
(TERMINATE
) : End execution+
(PLUS
) : PopB
, then popA
, then pushA + B
-
(SUBTRACT
) : PopB
, then popA
, then pushA - B
*
(MULTIPLY
) : PopB
, then popA
, then pushA * B
d
(DUPLICATE
) : PopA
, then pushA
twice^
(EMIT
) : PopA
and writeA.toString()
(JS) tostdout
A
(EMIT_AS_ASCII
) : PopA
and writeString.fromCharCode(A)
(JS) tostdout
n
(FLUSH
) :EMIT
until there are no values on the stacka
(FLUSH_AS_ASCII
) : compile the values on the stack into a string of their ASCII representations (in pop order) and write tostdout
v
(CONSUME
) : read a line fromstdin
and push its integer representation (eg"10" -> 10
)R
(CONSUME_AS_ASCII
) : read a line fromstdin
and push it as ASCII codes, in reverse order (eg"Hi"
-> push 105 (i) then push 72 (H))o
(SWAP
) : popA
, popB
, then pushA
and pushB
(swap the top two values on the stack)>
(FORWARD_JUMP
) : popN
and jump the execution cursorN
steps forward (right)}
(CONDITIONAL_FORWARD_JUMP
) : popQ
, then popN
, then jump execution cursorN
steps forward ifQ == 0
)
(SET_FLAG_AHEAD
) : popA
, popB
, and setflag A
to the current execution cursor plusB
(eg12)
setsflag 1
to the location two ahead of the)
)|
(SET_FLAG
) : popA
, and setflag A
to the current execution cursor (?|
is therefore functionally the same as?0)
)<
(JUMP_TO_FLAG
) : popA
, and set the current execution cursor to that marked byflag A
[
(STOMP_TO_FLAG
) : popA
, and set the current execution cursor to that marked byflag A
, then remove the record offlag A
Let's look at a few example programs to get you started. Use whichever mechanism you like for running them (see the first heading above), but I'll just include the code itself here.
_12+^x
This program:
- Begins
_
(Stack:[]
) - Pushes
1
to the stack (Stack:[1]
) - Pushes
2
to the stack ([1 2]
) - Pops and adds the top two values on the stack and pushes the result (
+
) ([3]
) - Pops and emits the top value to
stdout
([]
) - Terminates with
x
If you run it, you'll see 3
emitted to stdout
.
Both the _
and the x
can be omitted, since they're implicit at the start and end of the code anyway.
This program is functionally equivalent:
12+^
vv+^
This program:
- Reads a number
A
fromstdin
and pushes it ([A]
) - Reads a number
B
fromstdin
and pushes it ([B]
) - Pops and adds the top two values on the stack and pushes the result (
+
) ([A+B]
) - Pops and emits the top value to
stdout
([]
)
If you run it, you'll be prompted twice for input and see the sum of your two values emitted to stdout
.
0
0|
1+
d^
d 455** d* -
2}0<
This one's a bit bigger. Let's step through it.
- Push
0
([0]
) - Create flag 0 at location
2
- Add
1
to the value on top of the stack ([1]
on first run through) d^
is a small pattern meaning "print the top value to stdout without removing it": it duplicates and then emits ([1]
on first run through)455**
means "put100
on the stack": it equates to4*5*5
d*
means "square the top value of the stack": duplicate and then multiply- All of
d 455** d* -
therefore means "put the result of subtracting10000
from the current top of the stack on top of the stack ([1 -9999]
on first run through) 2}0<
: if the result of that subtraction was0
(ie if the top of the stack before it was10000
, and not less), jump2
spaces forward (and finish the program). Else, jump back to flag 0 and repeat, adding one more and looping until we hit10000
.
725**4+ A
825** 92+7* 825**5+ 725**4+ a
R n
This shows off three abilities around ASCII text handling in Jump:
725**4+ A
pushes74
then usesA
(EMIT_AS_ASCII
) to push the ASCII representation of that value tostdout
, emitting the character "J"825** 92+7* 825**5+ 725**4+ a
pushes four characters' values to the stack then usesa
(FLUSH_AS_ASCII
) to flush them one by one tostdout
as ASCII, emitting "J U M P"R a
reads a line fromstdin
withR
and then immediately flushes it again as ASCII. BecauseR
pushes to the stack in reverse order, this results in what you entered coming back out in order.
072*) 1| 0[ dd** o[
25 23) 1<
26 23) 1<
27 23) 1<
n
Let's look at a "pattern" for achieving reusable code in Jump.
The code above effectively defines a "function" for finding the cube of a number and then calls it with three different values before ending.
Function definition:
072*)
places flag0
at a location72*
or14
steps ahead of the)
. This puts it directly on the end of this first line's code.1|
sets flag1
at the current location. This defines the "entry point" of our cubing function.0[
"stomps" to flag0
, meaning that it jumps there and removes the flag. This takes execution to the end of the first line, skipping the actual functional code remaining. The skipped code constitutes the stuff that does the actual work of cubing numbers, and returning back to where we want. We'll come back to it in a second.
Function calling:
- On the next line,
25
pushes2
then5
. Here,5
is the number we want cubing (the function argument) and the2
is our intended return location - we're saying we want execution to come back to flag2
once we've cubed our five. 23)
throws that flag2
forward three steps from the)
(to the end of the line, after the next expression).1<
"calls" the function. We jump to flag1
(set at the start, during function definition).- From there, we first hit that
0[
again. This does nothing, since flag0
no longer exists. - We run the code that actually cubes the
5
on top of the stack (dd**
), leaving us with125
then2
on the stack. - Finally we
SWAP
(o
) the top of the stack to get our return address in the right place, and stomp[
to it, returning execution to the flag defined before calling. - We're now left with the cubed value of
5
on the top of the stack. - Repeat for the next two lines, and then flush to stdout with
n
.