Interp - an Interpreted Programming Language Pt.7 - Variables
It’s Pt. 7 Interp is now closer to becoming an actual programming language. This time we implement variables.
Parsing
LineTypes
Add LineTypes for the assignment operators.
src/parser/line.rs:
pub enum LineType {
...
Assign(Expr, Expr),
AddAssign(Expr, Expr),
SubAssign(Expr, Expr),
MulAssign(Expr, Expr),
DivAssign(Expr, Expr),
ModAssign(Expr, Expr),
PowAssign(Expr, Expr),
ConcatAssign(Expr, Expr),
Var(Expr, Expr),
}
Grammar
Next we add tokens and grammar rules for the operators.
src/parser/grammar.lalrpop:
match {
...
"=",
"+=",
"-=",
"*=",
"/=",
"%=",
"^=",
"<>=",
"var",
}
...
pub Line: Line = {
...
<l:@L> <a:expr> "=" <b:expr> => Line { type_: LineType::Assign(a, b), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
<l:@L> <a:expr> "+=" <b:expr> => Line { type_: LineType::AddAssign(a, b), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
... // Implement the rest of the assign operators here
<l:@L> "var" <a:variable> "=" <b:expr> => Line { type_: LineType::Var(a, b), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
}
VM
The Value struct must be clonable and equatable. Le’ts also add null while we’re at it.
Values
src/vm/value.rs:
#[derive(Debug, Clone)]
pub enum Value {
Void,
Null,
...
}
VM State
We finally add some state to our programs.
src/vm/mod.rs:
...
use crate::parser::line::{Line, LineType};
use crate::parser::expr::{Expr, ExprType};
use crate::err;
use std::collections::HashMap;
pub struct VM {
// the stack stores our values
pub stack: Vec<value::Value>,
// symbols point variable names into stack addresses
pub symbols: HashMap<String, usize>,
}
pub fn init_vm() -> VM {
VM {
stack: vec![],
symbols: HashMap::new(),
}
}
exec_line
Your job is to add the missing exec_line cases.
pub fn exec_line(vm: &mut VM, parsed_line: &Line) {
match &parsed_line.type_ {
...
LineType::Assign(left_expr, right_expr) => {
let value = expr_to_value(vm, right_expr);
assign(vm, left_expr, value);
}
LineType::AddAssign(left_expr, right_expr) => {
let left_value = expr_to_value(vm, left_expr);
let right_value = expr_to_value(vm, right_expr);
let result = arithmetic::add(left_value, right_value, &left_expr.loc);
assign(vm, left_expr, result);
}
... // Define SubAssign, MulAssign, DivAssign, ModAssign, PowAssign, and ConcatAssign here
LineType::Var(left_expr, right_expr) => {
if let ExprType::Variable(var) = &left_expr.type_ {
if is_var_illegal(var) {
err::throw_loc(err::Err::CannotAssignIllegalName, &left_expr.loc);
} else {
let value = expr_to_value(vm, right_expr);
vm.symbols.insert(var.to_owned(), vm.stack.len());
vm.stack.push(value);
}
} else { panic!() }
}
}
}
expr_to_value
fn expr_to_value(vm: &mut VM, expr: &Expr) -> value::Value {
match &expr.type_ {
...
ExprType::Variable(var) => {
match var.as_str() {
// Add Void and Null here
"void" => value::Value::Void,
"null" => value::Value::Null,
"Print" => value::Value::PrintBuiltin,
_ => {
// Return Void if variable not found
if let Some(address) = var_address(vm, var) {
vm.stack[address].clone()
} else {
value::Value::Void
}
}
}
}
Helper Functions
Some useful functions.
fn var_address(vm: &VM, var: &str) -> Option<usize> {
if let Some(address) = vm.symbols.get(var) {
Some(*address)
} else {
None
}
}
// Assign Value to Expr
fn assign(vm: &mut VM, left_expr: &Expr, right_value: value::Value) {
match &left_expr.type_ {
ExprType::Variable(var) => {
if let Some(address) = var_address(vm, var) {
vm.stack[address] = right_value;
} else {
err::throw_loc(err::Err::CannotAssign, &left_expr.loc);
}
}
_ => {
err::throw_loc(err::Err::CannotAssign, &left_expr.loc);
}
}
}
fn is_var_illegal(var: &str) -> bool {
if let "void" | "null" | "Print" = var {
true
} else { false }
}
value_repr
don’t forget to implement a case in the value_repr function.
fn value_repr(val: value::Value) -> String {
match val {
...
value::Value::Null => "<null>".to_owned(),
...
}
}
CannotAssign Error
You must also define the CannotAssign error.
src/err.rs:
pub enum Err {
...
CannotAssign,
CannotAssignIllegalName,
}
pub fn throw(err: Err) {
match err {
...
Err::CannotAssign => eprintln!("can't assign"),
Err::CannotAssignIllegalName => eprintln!("can't assign to a reserved variable"),
}
}
Testing
Let’s test!
var my_number = 1
my_number += 2
Print(my_number ^ 3) # Output: 27
Source Code
Source code for this part can be found here.