A frames per second
meme programming language π written in Rust
The concept of this language is to execute statements on a per frame
level
The lexing/parsing concept is based on the book Crafting Interpreters
The weird FPS
part was just a silly idea I had when thinking about frames per second
in video games π
All lexing and parsing was initally developed completelly manual without using any third-party libraries. I might change this in the future.
Currently I'm not using any lifetimes or Arc/Rc/RefCell as I was too deep into development when I wanted to make this change. I might be adding this in the future in order to remove all those ugly .clone()
π
# represents a frame
let a=1; # print(a); ## <- exit program on last frame
^ ^
| |_frame 1 -> 'print(a);' will be executed on frame 2
frame 0 -> 'let a = 1;' will be executed on frame 1
And this is where FPS Lang
shines at being weird
π€£
// this is FRAME 0
print("printed at frame 1 - declared at frame 0");
let a = 0;
#3 // frame 1 content will be spread out accross the next 3 frames - but special attention to the for loop!
// this message will be printed out 3 times (at frames 2,3,4)
print("printed at frames 2|3|4 - declared at frame 1");
// each for loop iteration is a new frame, which means the inner loop statements will executed for 6 frames -> 3 (frames) * 2 (range 0..=1)
for 0..=1 {
print("printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop");
a = a + 1;
print(a);
}
# // frame 4
print("printed at frame 5 - declared at frame 4");
print(a); // should print 4
##
output
FPS 1 -> printed at frame 1 - declared at frame 0
FPS 2 -> printed at frames 2|3|4 - declared at frame 1
FPS 2 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 2 -> 1 // 'a' value - printed inside for loop
FPS 3 -> printed at frames 2|3|4 - declared at frame 1
FPS 3 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 3 -> 2 // 'a' value - printed inside for loop
FPS 4 -> printed at frames 2|3|4 - declared at frame 1
FPS 4 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 4 -> 3 // 'a' value - printed inside for loop
FPS 5 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 5 -> 4 // 'a' value - printed inside for loop
FPS 5 -> printed at frame 5 - declared at frame 4
FPS 5 -> 4 // 'a' value - printed at frame 5 (declared at frame 4)
FPS 6 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 6 -> 5 // 'a' value - printed inside for loop
FPS 7 -> printed at frames 2|3|4|5|6|7 - declared at frame 1 inside for loop
FPS 7 -> 6 // 'a' value - printed inside for loop
cargo run -- --help
Usage: fps-lang.exe [OPTIONS]
Options:
-r, --repl Flag to access the REPL
-h, --help Print help
-V, --version Print version
Note
that currently it only executes the content of sample.fps
file.
I'll be adding a custom file input soon π
cargo run --
Start a REPL
cargo run -- -r
# REPL - FPS Lang #
--------------------
Type '\q' to exit
fps>
fps> let a = 0; print(a); ##
FPS 1 -> 0
Execute the currently implemented tests
cargo test
running 23 tests
test ast::environment::tests::declare ... ok
test ast::environment::tests::assign ... ok
test lexer::tests::comment ... ok
test lexer::tests::unterminated_consumption ... ok
test lexer::tests::numeric_literal ... ok
test lexer::tests::single_char_tokens ... ok
test parser::tests::test_comparison_paren ... ok
test lexer::tests::types ... ok
test lexer::tests::two_char_tokens ... ok
test lexer::tests::identifer_literal ... ok
test ast::environment::tests::declare_different_env ... ok
test lexer::tests::keywords ... ok
test parser::tests::declaration ... ok
test interpreter::tests::multiple_frames ... ok
test parser::tests::declaration_assign ... ok
test parser::tests::declaration_print ... ok
test parser::tests::test_addition ... ok
test ast::expr::tests::pretty_print_ast ... ok
test lexer::tests::string_literal ... ok
test parser::tests::test_logical_and ... ok
test parser::tests::test_logical_or ... ok
test parser::tests::test_print_statement ... ok
test parser::tests::test_comparison ... ok
test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Currently supported types
Type | Declaration |
---|---|
Int | 1 |
Float | 1. or 1.0 |
String | "value" |
Boolean | true |
Range(int, int) | 0..1 |
RangeEqual(int, int) | 0..1= |
Null | null |
#
represents a frame
let a = 0;
print(a);
#
print(a);
##
output
FPS 1 -> 0
FPS 2 -> 0
##
represents the end of the program
Use let
to declare a variable.
let a = 0;
Use ..
surround by 2 digits to define a Range
Range
is bound inclusively below and exclusively above
Ranges can also be RangeEqual
by adding a =
sign to the range 0..=1
RangeEqual
is bound inclusively below and inclusively above.
let a = true && false;
print(a);
a = true || false;
print(a);
##
output
FPS 1 -> false
FPS 1 -> true
For loop iterations represent a frame. So, every statement inside a for loop will be executed x times across x frames
for 0..2 { print("hello"); } ##
output
FPS 1 -> hello
FPS 2 -> hello
let a = 1;
while a < 3 {
print(a);
a = a + 1;
}
print(a);
##
output
FPS 1 -> 1
FPS 1 -> 2
FPS 1 -> 3
if 0 == 1 {
print("equals");
} else {
print("not equals");
}
let a = 1;
{
let b = 1;
print(a + b);
}
##
output
FPS 1 -> 2
For error handling in Rust
I am using anyhow and thiserror.
Regarding FPS Lang
I accumulate lexer
, parsing
or interpreter
errors and show them to the user
ERROR: Errors parsing: ["Invalid variable declaration: 'Could not consume: '\"Expected ';' after declaration\"''", "Could not consume: '\"Expected ';' after statement\"'"]