StackScript is a toy programming language, designed as a personal project to explore stack-based paradigm. This repository contains documentation about the language and a simple StackScript interpreter written in Python. While the language itself is not intended for real world use due to its limitations and inefficiency, it is a great learning tool and provides fun challenges when writing a program using it.
Please note that this language and the provided interpreter are not intended for production use and should be treated purely as an educational and recreational tool. I cannot be held accountable for any mistakes it could lead to.
This document is divided in multiple sections:
- How to install and run StackScript programs
- Core concepts
- Instruction set
- I/O instructions
- Arithmetic instructions
- Jump Instructions
- Stack manipulation instructions
- Code examples
- arithmetic.stsc
- loop.stsc
- fibonacci.stsc
- How to contribute to the project
- How to add an instruction
- Things you could help with
Note that python is required to run StackScript programs.
Clone this repository locally using:
git clone https://github.com/brayevalerien/StackScript
Add the path of the directory to your PYTHONPATH
environnement variable. You can cd
into the directory you downloaded and run:
export PYTHONPATH=$PYTHONPATH:${pwd}
Add this command to your ~/.bashrc
file to make this change permanent.
To run a StackScript program, simply run the stackscript
executable file. For instance, if you want the run the factorial.stsc example:
./stackscript ./examples/factorial.stsc
Note that this executable calls python to run the StacScript interpreter written in python.
If you need/want to build the executable by yourself, do:
make clean
make
StackScript relies heavily on the concept of a stack. A stack is a data structure that is similar to a list, as it stores elements at memory addresses. However, a stack differs from a list in that it only allows the user to interact with the top element. This means that to operate on an element at the bottom of the stack, the user must first remove (pop) all elements above it. Similarly, elements can only be added to the top of the stack through a process called pushing.
In StackScript, the stack can only store two types of elements:
- Floating-point numbers
- Tags, which are objects that point to instructions in the source code.
As a result, StackScript does not have native support for characters, strings, or more complex data structures like arrays. Moreover, there is no way to define variables or procedures/functions.
A StackScript program consists of instructions, which can be separated from each other by spaces, tabs, and/or line breaks. These instructions are executed one by one by the interpreter, performing the corresponding operations. To get a better idea of how this works all together, see the examples section.
print
: prints the top of the stack to the standard output.uInput
: reads a value from the standard input. Values that are not convertible to float are invalid.show
: prints the whole stack to the standard output formatted as[elem1, elem2, ... elemn]
whereelemn
is the top of the stack andelem1
the bottom of the stack.
In this section, we will note a the second-to-top element and b the top element of the stack.
add
: pops a and b and pushes a+bsub
: pops a and b and pushes b-amul
: pops a and b and pushes a*bdiv
: pops a and b and pushes b/aeuc
: pops a and b and pushes b//a (the result of the euclidean division)div
: pops a and b and pushes b%a (the remainder of the euclidean division)
Tags are a used by jump instructions. A jump instruction (conditional or not) requires a registered tag to be on top of the stack when it is executed, and will jump to the position in the source code where the tag was registered. Note that jump instructions pop the tag that are on top of the stack when executed.
>tagName
: registers a tag namedtagName
.jump
: unconditionally jumps to the tag that is currently on top of the stackjumpZero
: jumps to the tag that is currently on top of the stack iff the second-to-top element is 0.jumpNotZero
: jumps to the tag that is currently on top of the stack iff the second-to-top element is not 0.jumpPos
: jumps to the tag that is currently on top of the stack iff the second-to-top element is greater than or equal to 0.jumpNeg
: jumps to the tag that is currently on top of the stack iff the second-to-top element is strictly less than 0.
- there is no push instruction. Simply write the number or the tag you want to push on the stack (e.g.
1 2 tag show
will output[1.0, 2.0, 'tag']
) dup
: duplicates the top element and pushes it on the top of the stack (e.g.1 dup show
will output[1.0, 1.0]
).drop
: pops the top element and discards it.swap
: swaps the top two elements of the stack.reach
: similar todup
, but it duplicates the second-to-top element.cycle
: cycles the top three elements, bringing the one at the bottom on top.clear
: removes all elements from the stack
All code examples are available at ./examples/ for you to run them. Some of them are explained in this section.
This simple program demonstrates the use of the stack to compute simple math expression, how to display the result and use the drop
instruction.
27 42 add print drop
27 42 sub print drop
27 42 mul print drop
27 42 div print drop
27 42 euc print drop
27 42 mod print drop
Each line starts by pushing the numbers 27 and 42 on top of the stack. Then it uses one of the four arithmetic instruction to add, substract, multiply and divide the numbers that were added to the stack, then prints the top of the stack (the result of the math operation) and finally dops the top element (cleaning the stack). The expected output is:
69.0 // 42+27
15.0 // 42-27
1134.0 // 42*27
1.5555555555555556 // 42/27
1 // 42//27
15 // 42%27
This example shows a simple way to make a for loop using tags and conditional jump instructions.
10 print
>loop -1 add
print
loop jumpNotZero
The first line pushes 10 on top of the stack and prints it. A tag named "loop" is defined in the second line and we start the loop by decrementing the value on top of the stack (decrementing the value on top of the stack is done by -1 add
). The value is then printed. The last line will jump the the loop
tag iff the value on top of the stack is different from 0. The expected output of this program is:
10.0
9.0
8.0
7.0
6.0
5.0
4.0
3.0
2.0
1.0
0.0
Note that this program is equivalent and very similar to the following C code
double i;
for (i = 10.0; i != 0.0; i--) {
printf("%f\n", i);
}
Both codes have a simple way to express:
- a way to initialize the loop counter
- a condition for the loop to be executed again
- some code to execute at the end of a loop
this program is a bit more complex but it computes the first terms of the Fibonacci sequence. It uses many important concepts of StackScript, including stack manipulation.
1 print 1 print
20
>nextTerm
-1 add
cycle cycle
swap reach add
print
cycle
nextTerm jumpNotZero
The first line initializes the program by putting the first two terms of the Fibonacci sequence on top of the stack (1 and 1). It defines the number of terms to compute at line 2(in addition to the first two terms). Then, it uses a loop that computes and prints the next term. The -1 add
instruction decrements the term counter and cycle cycle
puts the counter at the bottom of the stack and bring the previous two terms of the sequence on top. swap reach add
adds the two terms on top but makes sure that in the end, the bottom-most of the two is the n-2 term (for instance, if [a, b]
is the top of the stack, after this line the top of the stack will be [b, a+b]
). We then print the result and bring the counter to the top of the stack using cycle
. This is repeated until the counter is equal to 0.
You can open issues and pull requests at any time, I check Github daily. If you took some time to contribute to the project, thank you very much.
If you have an idea for an interesting (or necessary) instruction to be added to the interpreter, it is pretty simple since the interpreter is written to make adding instructions easy.
You only need to modify stsc_interpreter.py, please follow these steps:
- At the top of the
Interpreter
class, define a new constant<INST_NAME>_INSTRUCTION
with the name of the instruction. - Define a function above
Interpreter.run
, calledstsc_<inst_name>
, that does what your instruction is supposed to do. This function can take arguments and manipulate the stack,self.stack
as well as the registered tags stored inself.tags
- In the
Interpreter.run
function, add an entry to theinstruction_mapping
: the key should be the constant you defined at step 1. and the value should be the function you defined at step 2. - In the same
run
function, add anelif
case corresponding to your instruction.
Keep in mind that StackScript is made to be extremely basic and limited, so I might not accept pull request of instructions that add more complexity to the language or that do not fit the idea of it.
Here are some ideas of what you could work on if you want to contribute to the project but don't know where to start. They should be fairly simple to do but would make great contributions:
- think about instructions that could improve the language and add them. Make sure they are necessary or very usefull.
- either prove that StackScript is Turing complete or that it is not. Even if it is not Turing complete, it would be great to know.
- improve the documentation by adding better examples, correcting grammar mistakes/typos and make the explanations clearer.