ksplang
| Designed by | 33 high school students |
|---|---|
| Appeared in | 2023 |
| Memory system | Stack-based |
| Computational class | Turing complete |
| Major implementations | ksplang, |
| File extension(s) | .ksplang |
Ksplang is a stack-based programming language with 33 instructions, independently designed by 33 people. It is a surprisingly good puzzle to find out how to write some basic programs in this language, and yet it is powerful enough to do anything with reasonable performance.
History
The famous Czech children’s story Jak si pejsek s kočičkou dělali k svátku dort (English translation) by Josef Čapek teaches that you can bake the best cake by adding all the good things you can think of into it. Yes, that is definitely the correct interpretation. We have decided to prove this experimentally, with a programming language.
We have done this within KSP, a Czech programming competition for high-school students. Within the first series of tasks of the 36th year, we have asked each competitor for a single original instruction for a stack-based language. Within the second and third series we have assigned tasks to solve in ksplang.
Learning the language
These tasks are good first steps to learn how to use the language. All of them assume a non-empty stack.
- task 1: Add 0 to the stack (leaving all the other elements intact)
- task 2: Turn x into -x (except for -2^63)
- task 3: Duplicate a number
- task 4: Turn positive
kinto a sequencek k-1 ... 0 - task 5: Sort the stack (given its length)
- task 6: Calculate the stack length
There are detailed write-ups here (1-3) and here (4-6) on how to solve the tasks available in Czech, but it is really recommended to at least figure out the first 3 tasks.
The stack
We have one stack of 64-bit signed values (-2^63 to 2^63-1). The initial stack is the input, the resulting stack is the output. Overflows are generally an error, so is indexing out of bounds. The list of instructions is indexed from zero. Instructions have ids (0 to 32). Programs are sequences of case insensitive instructions, separated by whitespace. The program ends when the program naturally reaches past the end or the start when executing backwards.
The max stack size is configurable, but in general, we assume it is large enough to fit anything we need.
Overview of instructions
See ksplang/instructions for a full list with all details. Alternatively, you can check out the reference Rust interpreter.
In the description, we usually talk about the stack growing right or upwards.
Stack manipulation
| ID | Zápis | Popis |
|---|---|---|
| 0 | praise
|
Adds "Mám rád KSP" N times (Czech for “I like KSP”). |
| 1 | pop
|
Removes the top value. |
| 2 | pop2
|
Removes the second value from the top. |
| 3 | max
|
Removes the smaller of the top two values. |
| 4 | L-swap
|
Swaps the first and last value on the stack. |
| 5 | lroll
|
Rolls the top N values on the stack K positions to the right. |
| 6 | -ff
|
If the top of the stack is 2, then 4, do nothing; otherwise empty the stack and fill it with -2^63. |
| 7 | swap
|
Pop an index n, then swap new top value with n-th value from the bottom (zero-indexed). |
| 8 | kPi
|
Gets decimal digits of π (see full description for details). |
Arithmetic
| ID | Zápis | Popis |
|---|---|---|
| 9 | ++
|
Increments the top number by 1. |
| 10 | u
|
Universal arithmetic; first pops operation id, then does addition/absolute difference/multiplication/division or remainder/factorial/sign. |
| 11 | REM
|
Truncated division modulo (like a % b in C).
|
| 12 | %
|
Euclidean modulo (like a % abs(b) in Python).
|
| 13 | tetr
|
Tetration. |
| 14 | ^^
|
Tetration (reverse parameter order compared to tetr). |
| 15 | m
|
Median of top k numbers, k is top value on the stack (not removed). |
| 16 | CS
|
Digit sum of top number (not removed). |
| 17 | lensum
|
Sum “decimal lengths” of top two numbers on the stack. |
| 18 | bitshift
|
Left bitshift. |
| 19 | And
|
Bitwise AND of top two numbers. |
| 20 | sum
|
Sum the entire stack. |
| 21 | gcd
|
GCD of two numbers. |
| 22 | d
|
GCD of n numbers. |
| 23 | qeq
|
Finds integer solutions of quadratic equations. |
| 24 | funkcia
|
Factorizes 2 numbers and multiplies the factorization except for common primes, modulo 1000000007. |
| 25 | bulkxor
|
Pops N and then applies XOR to N pairs of values interpreted as x > 0 and creates N zeros/ones. |
Control flow
IP is the current instruction pointer.
| ID | Zápis | Popis |
|---|---|---|
| 26 | BRZ
|
Sets IP to second top value if the top is zero (conditional absolute jump). |
| 27 | call
|
Adds IP+1 to the stack and sets top of the stack as IP (absolute jump with return IP). |
| 28 | GOTO
|
Sets IP to top value (absolute jump). |
| 29 | j
|
Adds top value + 1 to IP (relative jump). |
| 30 | rev
|
Jumps forward, rotates the stack and executes code backwards until itself (see full description for details) |
Other
| ID | Zápis | Popis |
|---|---|---|
| 31 | SPANEK
|
During task evaluation, gives double the points and goes to sleep… (results in a timeout error) |
| 32 | deez
|
Reads n values, translates them to instructions by their ids, evaluates the subprogram and appends the resulting stack as instructions to the end of the program. |
Computational class
Assuming a sufficiently big max stack size, the language is Turing-complete (a brainfuck -> ksplang translation exists and has been implemented).
Empty or non-empty stack
There are two different classes of programs depending on whether the initial stack is empty or not.
There is only one instruction which can be used with an empty stack: sum, which results in the creation of a zero on the stack. Additionally, there is only one instruction which can be safely used with an arbitrary stack without destroying some of its values: CS.
Because of this, it is not possible to write a program which handles both empty and non-empty initial stack, as programs for the two different cases must start with a different instruction.
Open questions
- What is the smallest maximum stack size required to calculate anything regardless of the max stack size bounds? The deez instruction allows to store data in the program rather on the stack. But you still need some memory for deez invocations. What is the smallest max stack size which does not prevent from calculating like we have infinite max stack size?
External resources
Implementations
- ksp/ksplang is the reference Rust interpreter
- web interpreter is an interactive interpreter with a stepping debugger (unfortunately in Czech, but it's only a few words, so a translator will deal with it easily)
- ksplang programs has a Kotlin translation of the Rust interpreter
Programs implemented in ksplang
Before looking at this, you should really try to solve those first tasks
- ksplang programs by User:Sejsel
- Advent of Code 2024 - a few days of Advent of Code solved in ksplang
- wasm2ksplang - a WASM runtime in ksplang (supports WASM MVP except for floats for now)