A basic stack based programming language (For learning purposes).
Those goals will surely change during the project.
- Simulated
- Compiled
- Statically typed (with compilation time type checking)
- Turing complete (See examples/rule110.ssmpl [Rule 110])
- Self-Hosted
First clone the repository, Then you'll can compile the compiler using one of the following commands.
make release
while compile the compiler as release and add a link to the current directory named ssmpl
make
or make debug
will provide a debug version of the compiler (Also add a link to the current directory)
Some types can implicitly be casted to other types at compiled time.
bool
can be casted fromi64
andf64
i64
can be casted frombool
andptr
f64
can be casted fromi64
andbool
ptr
can be casted fromi64
cast(TYPE)
cast the top element of the stack to the given type.
With explicit casting you can cast any type to any other type.
Casting to bool
alter the bits of the value while any other cast only affect the behavior of future operations.
Some operations can take a size argument marked as SIZE
in the documentation.
Those size arguments can be either a positive integer literal or a macro consisting of a single positive integer literal.
Comments will use //
for line comments and /*
and */
for block comments as a delimiter.
// This is a comment
Code Here /* This is an inline comment */ Code here
/* This is a
multiline comment */
To push a digit to the stack you just have to write the digit as is.
You can pass integers as well as floats.
values prefixes with 0b
, 0o
, 0x
are supported and will be converted to integers from binary, octal and hex respectively.:
42 69. 420.12 -9.9 .690 -10 0xBEEF 0b101 0o707
``dump` pop the top element of the stack and print it followed by a new line.
Any operation between a integer and a float will implicitly convert the integer to a float.
+
let b = pop();
let a = pop();
push(a + b);
-
let b = pop();
let a = pop();
push(a - b);
*
let b = pop();
let a = pop();
push(a * b);
Division is a bit special as it push both the result and the reminder of the division to the stack.
/
let b = pop();
let a = pop();
push(a / b);
Modulo is only supported for integers.
%
let b = pop();
let a = pop();
push(a % b);
++
let a = pop();
push(a + 1);
--
let a = pop();
push(a - 1);
drop(SIZE)
(pops SIZE
times from the stack)
If no SIZE
is provide (drop
), it is equivalent to drop1
for _ in 0..SIZE{
pop();
}
swap
let a = pop();
let b = pop();
push(a);
push(b);
over(SIZE)
Where SIZE
is a positive integer (push a copy of the SIZE+1
nth element of the stack)
If no SIZE
is provide (over
), it is equivalent to over1
let a = stack[SIZE+1];
push(a);
setOver(SIZE)
Where SIZE
is a positive integer (set the SIZE+1
nth element of the stack to the top of the stack)
dup(SIZE)
Where SIZE
is a positive integer (push a copy of the SIZE
firsts elements of the stack (in the same order))
for _ in 0..SIZE{
push(stack[SIZE]);
}
In all control flow, the condition
part must add exactly one boolean value to the stack without modifying the rest of the stack.
An If block must return the stack as it was before the if block.
if # condition # do
# Execute here if condition is true #
end
A while block must return as it was before the while block.
while # condition # do
# Execute here while condition is true #
end
An if with an else block must modify the stack in the same way
if # condition # do
# Execute here if condition is true #
else
# Execute here if condition is false #
end
All comparison operators push a boolean value to the stack.
WARNING: Comparing floats uses the CPU floats arithmetic so it can be off cause to precision error.
==
let b = pop();
let a = pop();
push(a == b);
!=
let b = pop();
let a = pop();
push(a != b);
>
let b = pop();
let a = pop();
push(a > b);
>=
let b = pop();
let a = pop();
push(a >= b);
<
let b = pop();
let a = pop();
push(a < b);
<=
let b = pop();
let a = pop();
push(a <= b);
argc
: Pushes the number of arguments passed to the program
argv
: Pushes the pointer to the start of the arguments passed to the program
<|X
Where X
is the size of the value to load from memory. (Possible values: 8, 16, 32, 64)
let ptr = pop();
push(memory[ptr]);
|>X
Where X
is the size of the value to store to the memory. (Possible values: 8, 16, 32, 64)
let value = pop();
let ptr = pop();
memory[ptr] = value;
Macro are replaced by their value at compile time.
macro NAME {
OPERATIONS
}
include "file_path"
file path is relative to where the compiler is executed. (I will try to make it relative to the file in the future)
Parse the file and append everything it contains to main program
&&
If the first two element of the stack evaluates to true
then true
is pushed to the stack, otherwise false
is pushed.
let b = pop();
let a = pop();
push(a && b);
||
If one of the first two element of the stack evaluates to true
then true
is pushed to the stack, otherwise false
is pushed.
let b = pop();
let a = pop();
push(a || b);
&
Pushes the bitwise AND of the top two elements of the stack to the stack.
|
Pushes the bitwise OR of the top two elements of the stack to the stack.
<<
Pushes the second element of the stack shifted left by the top element of the stack.
let b = pop();
let a = pop();
push(a << b);
>>
Pushes the second element of the stack shifted right by the top element of the stack.
let b = pop();
let a = pop();
push(a >> b);
Ssmpl has 1024 bytes of free memory which you can do whatever you want with.
mem
pushes the pointer to the start of the free memory.
decla NAME SIZE
declares a memory region with named NAME of size SIZE.
mem(NAME)
pushes the pointer to the start of the named memory region.