Crappy programming language made for a school assignment. Heavily influenced by JavaScript/TypeScript. We had to use functional functions, which sometimes suck in Python. So the code might not always be readable.
- Five builtin types: number, string, bool, array, and void
- Three builtin functions: print, println, and rand
- Custom functions with parameters using the
func
keyword - If statements
- While loops
- Scoped variables, with the option to update a var in a 'parent' scope
- Fixed size arrays
- Comments
- Basic CLI interface (using
python -m smickelscript.cli
)
Some things which could be improved. The language is "Jan-Complete" at the moment, which means that it should be enough to pass the course.
- [interpreter] Better debugging (trace state changes?)
- Better error message when a function is missing a return statement (but a return typehint is given), see
test_main_number_no_return
- [compiler] Some data could be saved in
.RODATA
instead of.DATA
- [compiler] Optimize
mov
statements. There are somemov
statements which could be removed/optimized. - [compiler] Could've used AsmTokens instead of raw assembly strings.
- [compiler] Add byte arrays (useful for ASCII strings).
- [compiler] The
print
function could automatically be translated to eitherprint_str
orprint_integer
. - [compiler] With large programs you sometimes get errors where the literal pool is out of range for LDR. More info.
# Install the package
pip install -e .
# Run a script
python -m smickelscript.cli exec -i example/hello_world.sc
# Run a script and pass one argument
python -m smickelscript.cli exec -i example/hello_name.sc Wouter
# Or pass multiple args
python -m smickelscript.cli exec -i example/multi_args.sc Wouter "How are you?"
# Or call a different entry point
python -m smickelscript.cli exec -i example/functions.sc -e sommig 5
# Install the package
pip install -e .
# Compile a script to asm. This prints the generated code to stdout.
python -m smickelscript.cli native -i example_native/hello_world.sc
# Compile and run a script on an Arduino (connect via usb)
python -m smickelscript.cli native -i example_native/hello_world.sc --execute
# Install the package
pip install -e .
pip install Flask flask-cors waitress
# Start web api (development)
cd smickelscript_web
flask run
# Start web api (production)
cd smickelscript_web
waitress-serve --port=3025 app:app
# Example NGINX config
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name saas.cerbus.nl;
# include snippets/ssl-cerbus.conf;
# root /opt/HU-ATP/smickelscript_web/public/;
# location /api/ {
# proxy_pass http://127.0.0.1:3025/;
# }
# }
If a negative sign 'touches' a number it will always be interpreted as a negative number.
E.g var a = 1 -1
is NOT the same as var a = 1 - 1
.
The first example is invalid code, and the second is valid code.
All variables in the stack are readable and writeable by all functions. For example the following snippet is valid code.
func main() {
var a = 0;
incr_a();
// This will print '1'.
println(a);
}
func incr_a() {
a = a + 1;
}
All typehints are optional, but when they are given they will be enforced. If the typehints are omitted they will be guessed. Guessing means that it will try to parse it as a number, and if that doesn't work then it must be a string.
The interpreter will print all print
and println
output to the stdout. It will also print the return value of the entry point function (which may be None).
The compiler transforms your smickelscript source code into ARM Cortex-M0 assembly code. The PlatformIO toolkit is used to compile, flash, and monitor the generated code.
The compiler has a few limitations compared to the interperter:
- A function can have at most one argument. This is not a technical limitation, but it keeps the compiler's code a lot simpler.
- You can only have 4 local variables in each scope (aka function). This too keeps the compiler's code significantly less complex.
- Each element of an array is 4 bytes large. This means that you can't call
print
andprintln
on arrays, you can however useprint_int_as_char
to print an integer (element of the array) as an ASCII character. - The println function is divided into multiple functions. These functions are
print_integer
,print_str
,println_integer
,println_str
, andprint_int_as_char
. - A
bool
type variable is translated into an int0
or1
by the compiler, this also means that you have to use eitherprint_integer
orprintln_integer
to print a bool variable. - The compiler doesn't do a lot of 'logic checking', this means that usually the compiler won't stop you from writing stupid code.
- You can't access variables in a higher stack layer.
if
andwhile
statements do NOT create a new stack layer.
There are 7 files with tests.
- test_lexer tests the basic functionality of the lexer.
- test_parser tests the basic functionality of the parser.
- test_interpreter tests the basic functionality of the interpreter.
- test_must_haves tests the code samples provided by the course.
- test_extra tests a few edge cases which aren't (yet) supported by the language. These are optional.
- test_compiler has tests for the compiler.
- test_run_native tests the compiled code on actual hardware.
# Run a specific test suite.
python -m pytest tests/test_run_native.py -vv
The not so interesting part.
- If statements (implemented in interpreter.py line 274)
- While loops (implemented in interpreter.py line 290)
- Function calls (implemented in interpreter.py line 99)
- This includes creating your own functions
- And calling functions within functions
- The return value can either be assigned to a variable, or directly printed.
- See example/functions.sc
- Multiple variable scopes (implemented in interpreter.py line 157)
- Classes with inheritance (at the top in interpreter.py, parser.py and lexer.py)
- Object printing for all classes using JSON and a custom encoder.
- Type annotated
- Uses higher order functions
- (map functions are just a crappy way to write list comprehensions, change my mind)
- 2x map + 2x zip in execute_func
- reduce in tokenize_file and tokenize_str
- map a bunch of times inside test_lexer.py
- map inside cli.py
- used
next
in find_func but I had to remove it to allow for duplicate checking
We could write a brainfuck interpreter in this language. Because brainfuck is turing complete it also means that this language is turing complete. To write a brainfuck interpreter you only needs arrays, conditions and basic math (addition and subtraction), all these features are supported in this language.
See statement_exec_map
and operators_map
at line ~391 inside interpreter.py for a list of all the implemented functions, and how they are implemented.