WARNING! This language is a work in progress!
It's like Forth but written in Python. But I don't actually know since I never programmed in Forth, I only heard that it's some sort of stack-based programming language. Porth is also stack-based programming language. Which makes it just like Forth am I rite?
Porth is planned to be (these are not the selling points, but rather milestones of the development)
- Compiled
- Native
- Stack-based (just like Forth)
- Turing-complete
- Self-hosted (Python is used only as an initial bootstrap, once the language is mature enough we gonna rewrite it in itself)
- Statically typed (the type checking is probably gonna be similar to the WASM validation)
Hello, World:
"Hello, World\n" 1 1 syscall3
Simple program that prints numbers from 0 to 99 in an ascending order:
100 0 while 2dup > do
dup print 1 +
end
Simulation simply interprets the program.
$ cat program.porth
34 35 + print
$ ./porth.py sim program.porth
69
Compilation generates assembly code, compiles it with nasm, and then links it with GNU ld. So make sure you have both available in your $PATH
.
$ cat program.porth
34 35 + print
$ ./porth.py com program.porth
[INFO] Generating ./program.asm
[CMD] nasm -felf64 ./program.asm
[CMD] ld -o ./program ./program.o
$ ./program
69
Test cases are located in ./tests/ folder. The *.txt
files are the expected outputs of the corresponding programs.
Run ./test.py script to execute the programs and assert their outputs:
$ ./test.py
To updated expected output files run the record
subcommand:
$ ./test.py record
The ./examples/ contains programs that are ment for showcasing the language rather then testing it, but we still can them for testing just like the stuff in ./tests/:
$ ./test.py -f ./examples/
$ ./test.py -f ./examples/ record
This is what the language supports so far. Since the language is a work in progress the exact set of operations is the subject to change.
<integer>
- push the integer onto the stack. Right now the integer is anything that is parsable by int function.
push(<integer>)
<string>
- push size and address of the string literal onto the stack. A string literal is a sequence of character enclosed with"
.
size = len(<string>)
push(n)
ptr = static_memory_alloc(n)
copy(ptr, <string>)
push(ptr)
dup
- duplicate an element on top of the stack.
a = pop()
push(a)
push(a)
2dup
- duplicate pair.
b = pop()
a = pop()
push(a)
push(b)
push(a)
push(b)
swap
- swap 2 elements on the top of the stack.
a = pop()
b = pop()
push(a)
push(b)
drop
- drops the top element of the stack.
pop()
print
- print the element on top of the stack in a free form to stdout and remove it from the stack.
a = pop()
print(a)
over
a = pop()
b = pop()
push(b)
push(a)
push(b)
=
- checks if two elements on top of the stack are equal. Removes the elements from the stack and pushes1
if they are equal and0
if they are not.
a = pop()
b = pop()
push(int(a == b))
!=
- checks if two elements on top of the stack are not equal.
a = pop()
b = pop()
push(int(a != b))
>
- checks if the element below the top greater than the top.
b = pop()
a = pop()
push(int(a > b))
<
- checks if the element below the top less than the top.
b = pop()
a = pop()
push(int(a < b))
>=
b = pop()
a = pop()
push(int(a >= b))
<=
b = pop()
a = pop()
push(int(a >= b))
+
- sums up two elements on the top of the stack.
a = pop()
b = pop()
push(a + b)
-
- subtracts the top of the stack from the element below.
a = pop()
b = pop()
push(b - a)
mod
a = pop()
b = pop()
push(b % a)
shr
a = pop()
b = pop()
push(b >> a)
shl
a = pop()
b = pop()
push(b << a)
bor
a = pop()
b = pop()
push(b | a)
band
a = pop()
b = pop()
push(b & a)
if <then-branch> else <else-branch> end
- pops the element on top of the stack and if the element is not0
executes the<then-branch>
, otherwise<else-branch>
.while <condition> do <body> end
- keeps executing both<condition>
and<body>
until<condition>
produces0
at the top of the stack. Checking the result of the<condition>
removes it from the stack.
mem
- pushes the address of the beginning of the memory where you can read and write onto the stack.
push(mem_addr)
.
- store a given byte at the given address.
byte = pop()
addr = pop()
store(addr, byte)
,
- load a byte from the given address.
addr = pop()
byte = load(addr)
push(byte)
syscall<n>
- perform a syscall with n arguments where n is in range[0..6]
. (syscall1
,syscall2
, etc)
syscall_number = pop()
<move syscall_number to the corresponding register>
for i in range(n):
arg = pop()
<move arg to i-th register according to the call convention>
<perform the syscall>