Yet Another Interpreter Written In Rust
$ cargo run
π 2+1
3
$ cargo run 'println(2+2+3);'
6
6
6
$ cargo run ./programs/print.yaiwr
4
Log levels can be configured via the environment variable: RUST_LOG.
RUST_LOG=info cargo run -- '2+2'
RUST_LOG=debug cargo run -- '2+2'
RUST_LOG=error cargo run -- '2+?'
env_logger crate docs
# run unit tests
$ cargo test
# run language unit tests
$ cargo test --test lang
# run test in a container
$ run_docker_ci_job # optional (--prune)
Integers are supported as numeric values.
Integers are stored as whole numbers as Rust u64
constant for the 64-bit unsigned integers.
Example:
let _a = 123;
Booleans represents a value, which could be true
or false
.
Example:
let _t = true;
let _f = false;
Symbol | Meaning | Example |
---|---|---|
> | Greater than | 1 > 2 |
< | Less than | 2 < 1 |
Symbol | Meaning | Example |
---|---|---|
== | Equal | 1 == 2 |
!= | Not Equal | 2 != 1 |
|| | Or | true || false |
&& | And | true && false |
Example:
(1+2) > 3 # false
1000 > 42 # true
let _a = 1 > 2;
YAIWR comments can be used to explain the YAIWR code. YAIWR comments can be used to prevent execution when testing alternative code.
Convention:
- Single line comments should start with
//
- No multi-line comment supported
Example:
let _a = 4; // let _a = 5;
// let _a = 5;
println(_a);
In this example, the output will be 4
.
println
- Prints to the standard output, with a new line
Example:
println(1+2);
println(1);
Variable names:
Variable names can only include alphanumeric and underscore ("_") characters
let <name> = <expression>;
let
- keyword indicating the beginning of the variable declaration
<name>
- variable name
<expression>
- expression that will be evaluated and assigned to the variable
Example:
let someVariable = (1+2);
let someVariable3 = 1;
let x = 2;
let y = 1 * _x;
if (true) {
/* code to run if condition is true */
} else {
/* run some other code instead */
}
else
block is optional, one can use if statements without it.
Example:
if (true) {
/* code to run if condition is true */
}
fun <name> (<params>) { <statements> }
<fun>
- keyword indicating the beginning of a function declaration
<name>
- any alphanumeric sequence
<params>
- (optional) single or list of parameters passed to the function
<statements>
- statements that comprise the body of the function
Example:
fun add (_arg1, _arg2){ return _arg1 + _arg2; }
fun add1 (_arg1){
return _arg1 + 1;
}
<name>
(<arguments>
)
<arguments>
- (optional) single or list of parameters passed to the function
Example:
add(1,2)
Example:
fun add (x){
if (x < 10) {
return add(x + 1);
}
return x;
}
Example:
fun f() {
let x = 0;
fun g() {
x = x + 1;
return x;
}
return g;
}
let a = f();
println(a());
- Variables declared within a function, become "local" to the function.
- Variables declared in the outer scope of a function are accessible by the "local" function context
Example:
let g = 0;
// code here can't use "a" variable
fun f() {
// code here can use "g" variable
let a = 2;
// code here can use "a" variable
}
// code here can't use "a" variable
[x] Go through the calc example in the quick start guide
[x] Implement a testing framework
[x] Split between "compile an AST to Vec and then have an evaluator which takes Vec and executes the program"
[x] Implement stack-based VM
[x] Implement print statement
[x] Implement variables
[x] Implement functions
[x] Implement conditional statements
[x] Propogate all errors to top-level where the error is printed
[x] Add support for custom error handling, i.e InterpError
[x] Implement function scope
[x] Multi-line statements support as it was intended in #17
[x] Add comments support
[x] Integrate lang_tester https://lib.rs/crates/lang_tester
[x] Allow function calls without ;
, for example: add1(add1(1))
instead of add1(add1(1););
[x] Conditionals
[x] Closures
[x] Rename interpreter from calc to something more meaningful
[ ] Compile variable names to integers
[ ] Performance - non-recursive set_var and get_var scope functionality
[ ] Benchmarking
Parse Tree - includes all the "useless" syntactic information that humans like/need but doesn't affect compilation
AST - strip out that useless syntactic stuff
Evaluator - evaluates something (parse tree, AST, or opcodes) directly; a "compiler" converts one thing into another
Stack-based machines - Stack for operands and operators, the result is always on top of the stack
sequenceDiagram
Program->>+VM: Evaluate
note right of VM: Defined in yaiwr.rs
note right of Lexer: Defined in yaiwr.l
VM->>+Lexer: Lexical analysis
Lexer-->>-VM: Lexemes
VM->>+Parser: Parse Lexemes
note right of Lexer: Defined in yaiwr.y
Parser-->>-VM: AST
VM->>Bytecode: Parse AST to bytecode
loop foreach AST node
Bytecode->>+Bytecode: Convert to Instruction
end
Bytecode-->>-VM: Bytecode Instructions
loop foreach instuction
VM->>+VM: Evaluate instuction
end
Building a Virtual Machine [2/29]: Stack vs. Register VM
Quickstart Yet Another Interpreter Written In Rust