Table of Contents
1. What is it?
2. Scanning
3. Parsing
4. Variables and Closures
5. Array Operations
6. Built-in Functions
7. Error Reporting
8. Conclusions
What is it?
Using Rust, I attempted to create an interpreted language called Ari. Given that it's my first attempt, I resorted to a C-like grammar due to its simplicity and ubiquity.
Scanning
Scanning is the process of transforming the input program into a sequential array of tokens. Tokens are the basic blocks of the program akin to physical atoms. The block below illustrates the conversion:
+ ➡️ TokenType::Plus
; ➡️ TokenType::Semicolon
= ➡️ TokenType::Equal
== ➡️ TokenType::EqualEqual
123 ➡️ TokenType::Number
"123" ➡️ TokenType::String
someVariable ➡️ TokenType::Identifier
- The scanner's implementation can be found at scanner.rs
- All the tokens are defined at token.rs
Parsing
Parsing is the process of creating an abstract syntax tree (AST) from the array of tokens. Ari's AST comprises of three basic blocks:
- Literals are values such as numbers, strings, arrays, null etc.
- Expressions are individual operations upon literals such as arithmetic, comparisons, array access, variable assignment, function call etc.
- Statements are groups of expressions such as if-else, loops, function definitions, etc.
For simplicity, Ari uses a manually-implemented recursive descent parser. Although easier than other parser types, it is vulnerable to stack overflow if the input program's recursion is too deep. All statements and expressions are allocated on the heap memory using Rust's standard Box.
- The AST's implementation can be found at ast.rs
- The parser's implementation can be found at parser.rs
Variables and Closures
In order to keep track of initialized variables, Ari uses Rust's standard Hashmap to map the variables. Moreover, Ari also implements block scoping which enables variable shadowing described in the docs.
Ari's functions are first class citizens, meaning that they can be stored in variables and passed around as arguments to other functions. Similar to Javascript, Ari implements the concept of closures where functions keep a copy of their outer scope. The docs provide some examples.
- The environment's implementation can be found at environment.rs
Array Operations
Ari creates and manipulates arrays using Rust's standard Vec. To parallelize array operations, Rayon is used. Array functions like map, filter, reduce are also provided.
Built-in Functions
No language would be appealing or useful if everything must be implemented from scratch. Ari provides a small set of built-in functions that I think are helpful for numbers, strings, arrays, random numbers, file operations and web stuff.
- The standard functions are implemented at function.rs
Error Reporting
Inspired by Rust, I incorporated terminal colors and helpful error messages which highlight what and where the errors are.
◦ Undefined variable
◦ Invalid logarithm
◦ Missing semicolon
Conclusions
This project was a good exposure to the intricacies of language design and also an exciting opportunity to put Rust into practice.