Skip to content

Yet Another Interpreter Writen In Rust

License

Notifications You must be signed in to change notification settings

Pavel-Durov/yaiwr

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

8791feb · Jun 5, 2023

History

60 Commits
Apr 11, 2023
Apr 27, 2023
Apr 27, 2023
Feb 10, 2023
Feb 10, 2023
Feb 10, 2023
Feb 15, 2023
Apr 4, 2023
Feb 10, 2023
Jun 5, 2023
Feb 10, 2023
Apr 24, 2023
Apr 4, 2023
Feb 13, 2023

Repository files navigation

Bors enabled

yaiwr

Yet Another Interpreter Written In Rust

CI

Buildbot

Bors repository

Usage

Repl

$ cargo run 
👉 2+1 
3

Cli args

$ cargo run 'println(2+2+3);'
6
6
6

File

$ cargo run ./programs/print.yaiwr
4

Logs

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

Tests

# run unit tests
$ cargo test
# run language unit tests
$ cargo test --test lang
# run test in a container
$ run_docker_ci_job # optional (--prune)

Langugage Spec(ish)

Types

Numbers

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

Booleans represents a value, which could be true or false.

Example:

let _t = true;
let _f = false;

Comparison Operators

Symbol Meaning Example
> Greater than 1 > 2
< Less than 2 < 1

Logical Operators

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;

Comments

YAIWR comments can be used to explain the YAIWR code. YAIWR comments can be used to prevent execution when testing alternative code.

Convention:

  1. Single line comments should start with //
  2. 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.

Statements

println - Prints to the standard output, with a new line

Example:

println(1+2);
println(1);

Variables

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;

Conditionals

if...else statements

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 */
}

Functions

Function Declaration

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; 
}

Function calls

<name> (<arguments>)

<arguments> - (optional) single or list of parameters passed to the function

Example:

add(1,2)

Recursion

Example:

fun add (x){ 
    if (x < 10) {
        return add(x + 1);
    }
    return x;
}

Closures

Example:

fun f() {
  let x = 0;
  fun g() {
    x = x + 1;
    return x;
  }
  return g;
}

let a = f();
println(a());

Function Scope

  • 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

TODOs

[ ] Compile variable names to integers

[ ] Implement non-recursive set_var and get_var scope functionality

[ ] Benchmarking

Terminology

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

YAIWR architecture overview

Loading
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

Resources

Building a Virtual Machine [2/29]: Stack vs. Register VM

Which Parsing Approach?

Yacc

Quickstart Yet Another Interpreter Written In Rust

Grammars

ANSI C grammar, Lex specification

About

Yet Another Interpreter Writen In Rust

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Rust 89.9%
  • Yacc 8.9%
  • Other 1.2%