Giter Site home page Giter Site logo

jump's Introduction

Jump : an obscure, one-dimensional stack-based language

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.

Running a Jump program

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.

Running a local .jump file

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

Running a small inline script

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+^'

Concepts

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.

The execution cursor

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

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.

Flags

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 gotos.

On program start, the flag mapping is empty.

Program entry

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.

Program termination

Programs terminate if the execution cursor leaves the end of the code string, or if a x (TERMINATE) instruction is executed.

Instructions

  • 0, 1, 2, ..., 9 : Push the given integer
  • x (TERMINATE) : End execution
  • + (PLUS) : Pop B, then pop A, then push A + B
  • - (SUBTRACT) : Pop B, then pop A, then push A - B
  • * (MULTIPLY) : Pop B, then pop A, then push A * B
  • d (DUPLICATE) : Pop A, then push A twice
  • ^ (EMIT) : Pop A and write A.toString() (JS) to stdout
  • A (EMIT_AS_ASCII) : Pop A and write String.fromCharCode(A) (JS) to stdout
  • n (FLUSH) : EMIT until there are no values on the stack
  • a (FLUSH_AS_ASCII) : compile the values on the stack into a string of their ASCII representations (in pop order) and write to stdout
  • v (CONSUME) : read a line from stdin and push its integer representation (eg "10" -> 10)
  • R (CONSUME_AS_ASCII) : read a line from stdin and push it as ASCII codes, in reverse order (eg "Hi" -> push 105 (i) then push 72 (H))
  • o (SWAP) : pop A, pop B, then push A and push B (swap the top two values on the stack)
  • > (FORWARD_JUMP) : pop N and jump the execution cursor N steps forward (right)
  • } (CONDITIONAL_FORWARD_JUMP) : pop Q, then pop N, then jump execution cursor N steps forward if Q == 0
  • ) (SET_FLAG_AHEAD) : pop A, pop B, and set flag A to the current execution cursor plus B (eg 12) sets flag 1 to the location two ahead of the ))
  • | (SET_FLAG) : pop A, and set flag A to the current execution cursor (?| is therefore functionally the same as ?0))
  • < (JUMP_TO_FLAG) : pop A, and set the current execution cursor to that marked by flag A
  • [ (STOMP_TO_FLAG) : pop A, and set the current execution cursor to that marked by flag A, then remove the record of flag A

Examples

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.

Add two integer literals together

_12+^x

This program:

  1. Begins _ (Stack: [])
  2. Pushes 1 to the stack (Stack: [1])
  3. Pushes 2 to the stack ([1 2])
  4. Pops and adds the top two values on the stack and pushes the result (+) ([3])
  5. Pops and emits the top value to stdout ([])
  6. 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+^

Read two integers from stdin and add them

vv+^

This program:

  1. Reads a number A from stdin and pushes it ([A])
  2. Reads a number B from stdin and pushes it ([B])
  3. Pops and adds the top two values on the stack and pushes the result (+) ([A+B])
  4. 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.

Increment from 1 to 10000

0
0|
1+
d^
d 455** d* -
2}0<

This one's a bit bigger. Let's step through it.

  1. Push 0 ([0])
  2. Create flag 0 at location 2
  3. Add 1 to the value on top of the stack ([1] on first run through)
  4. 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)
  5. 455** means "put 100 on the stack": it equates to 4*5*5
  6. d* means "square the top value of the stack": duplicate and then multiply
  7. All of d 455** d* - therefore means "put the result of subtracting 10000 from the current top of the stack on top of the stack ([1 -9999] on first run through)
  8. 2}0<: if the result of that subtraction was 0 (ie if the top of the stack before it was 10000, and not less), jump 2 spaces forward (and finish the program). Else, jump back to flag 0 and repeat, adding one more and looping until we hit 10000.

Basic ASCII handling

725**4+ A
825** 92+7* 825**5+ 725**4+ a
R n

This shows off three abilities around ASCII text handling in Jump:

  1. 725**4+ A pushes 74 then uses A (EMIT_AS_ASCII) to push the ASCII representation of that value to stdout, emitting the character "J"
  2. 825** 92+7* 825**5+ 725**4+ a pushes four characters' values to the stack then uses a (FLUSH_AS_ASCII) to flush them one by one to stdout as ASCII, emitting "J U M P"
  3. R a reads a line from stdin with R and then immediately flushes it again as ASCII. Because R pushes to the stack in reverse order, this results in what you entered coming back out in order.

Reusable code ("functions")

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:

  1. 072*) places flag 0 at a location 72* or 14 steps ahead of the ). This puts it directly on the end of this first line's code.
  2. 1| sets flag 1 at the current location. This defines the "entry point" of our cubing function.
  3. 0[ "stomps" to flag 0, 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:

  1. On the next line, 25 pushes 2 then 5. Here, 5 is the number we want cubing (the function argument) and the 2 is our intended return location - we're saying we want execution to come back to flag 2 once we've cubed our five.
  2. 23) throws that flag 2 forward three steps from the ) (to the end of the line, after the next expression).
  3. 1< "calls" the function. We jump to flag 1 (set at the start, during function definition).
  4. From there, we first hit that 0[ again. This does nothing, since flag 0 no longer exists.
  5. We run the code that actually cubes the 5 on top of the stack (dd**), leaving us with 125 then 2 on the stack.
  6. 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.
  7. We're now left with the cubed value of 5 on the top of the stack.
  8. Repeat for the next two lines, and then flush to stdout with n.

jump's People

Contributors

k0nserv avatar unwitting avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

k0nserv

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.