Interp - an Interpreted Programming Language Pt.11 - List and Dict
How’s it going? Tody in Pt. 11 we make lists and dicts.
Bit Of Refactoring
src/vm/mod.rs has grown in size. Let’s move expr_to_value and value_repr to their own files src/vm/expr_to_value.rs and src/vm/value_repr.rs respectively.
Add this to the top.
src/vm/mod.rs
mod value;
mod arithmetic;
mod expr_to_value;
mod value_repr;
pub use expr_to_value::expr_to_value;
pub use value_repr::value_repr;
Parsing
LineTypes
Add the assignment LineType.
src/parser/line.rs:
pub enum LineType {
...
CombineAssign(Expr, Expr),
}
ExprTypes
Add these ExprTypes.
src/parser/expr.rs:
pub enum ExprType {
...
List(Vec<Expr>), // [1, 2, 3]
Dict(Vec<(Expr, Expr)>), // {x = 1, y = 2, z = 3}
Index(Box<Expr>, Box<Expr>), // list[2]
Field(Box<Expr>, Box<Expr>), // dict.field
Combine(Box<Expr>, Box<Expr>), // [1,2,3] & [4,5,6]
}
Grammar
Add these tokens and grammar rules.
src/parser/grammar.lalrpop:
match {
...
"[",
"]",
".",
"&",
"&=",
}
...
pub Line: Line = {
...
<l:@L> <a:expr> "&=" <b:expr> => Line { type_: LineType::CombineAssign(a, b), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
}
...
expr0: Expr = {
...
list,
dict,
index,
field,
}
...
list: Expr = {
<l:@L> "[" <a:expr_list> "]" => Expr { type_: ExprType::List(a), loc: Loc { filename: filename.to_owned(), line_nr, index: l } }
}
dict: Expr = {
<l:@L> "{" <a:key_value_list> "}" => Expr { type_: ExprType::Dict(a), loc: Loc { filename: filename.to_owned(), line_nr, index: l } }
}
index: Expr = {
<l:@L> <a:expr0> "[" <b:expr> "]" => Expr { type_: ExprType::Index(Box::new(a), Box::new(b)), loc: Loc { filename: filename.to_owned(), line_nr, index: l } }
}
field: Expr = {
<l:@L> <a:expr0> "." <b:variable> => Expr { type_: ExprType::Field(Box::new(a), Box::new(b)), loc: Loc { filename: filename.to_owned(), line_nr, index: l } }
}
...
expr3: Expr = {
...
<l:@L> <a:expr3> "&" <b:expr2> => Expr { type_: ExprType::Combine(Box::new(a), Box::new(b)), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
}
...
key_value_list: Vec<(Expr, Expr)> = {
=> vec![],
<a:variable> "=" <b:expr> "," <mut c:key_value_list> => { c.insert(0, (a, b)); c },
<a:variable> "=" <b:expr> => vec![(a, b)]
}
VM
Value
Add the following Values.
NOTE: Lists can’t contain Void values.
Add the List and Dict values.
src/vm/value.rs:
pub enum Value {
...
List(Vec<Value>),
Dict(Vec<(Value, Value)>),
}
Operator Evaluation
Add the Combine arithmetic operator.
src/vm/arithmetic.rs:
...
pub fn combine(x: Value, y: Value, x_loc: &Loc) -> Value {
match (x, y) {
(Value::List(mut x), Value::List(mut y)) => {
x.append(&mut y);
Value::List(x)
}
(Value::Dict(mut x), Value::Dict(mut y)) => {
x.append(&mut y);
Value::Dict(x)
}
_ => {
err::throw_loc(err::Err::TokenExpected { expected: "lists or dicts".to_owned() }, x_loc);
Value::Void
}
}
}
expr_to_value
Add the Expr evaluations.
src/vm/expr_to_value.rs:
use crate::vm::{VM, value, arithmetic, var_address, value_repr};
use crate::parser::expr::{Expr, ExprType};
use crate::err;
pub fn expr_to_value(vm: &mut VM, expr: &Expr) -> value::Value {
match &expr.type_ {
...
ExprType::List(exprs) => {
let mut list = vec![];
for expr in exprs {
let value = expr_to_value(vm, expr);
if value == value::Value::Void {
err::throw_loc(
err::Err::TokenUnexpected { not_expected: "void".to_owned() }, &expr.loc);
}
list.push(value);
}
value::Value::List(list)
}
ExprType::Dict(key_val_pairs) => {
let mut dict = vec![];
for (key, value) in key_val_pairs {
if let ExprType::Variable(var) = &key.type_ {
let key = value::Value::String(var.to_owned());
dict.push((key, expr_to_value(vm, value)));
}
}
value::Value::Dict(dict)
}
ExprType::Index(parent_expr, child_expr) => {
let parent_value = expr_to_value(vm, parent_expr);
let child_value = expr_to_value(vm, child_expr);
match parent_value {
value::Value::List(list) => {
if let value::Value::Int(mut index) = child_value {
// negatives start at the end
if index < 0 {
index = list.len() as i64 + index;
}
let index = index as usize;
if index < list.len() {
return list[index as usize].clone();
}
}
}
value::Value::Dict(dict) => {
for (key, value) in dict {
if key == child_value {
return value.clone();
}
}
}
value::Value::String(str) => {
let chars: Vec<char> = str.chars().collect();
if let value::Value::Int(mut index) = child_value {
if index < 0 {
index = chars.len() as i64 + index;
}
let index = index as usize;
if index < chars.len() {
return value::Value::String(chars[index as usize].to_string());
}
}
}
_ => { }
}
value::Value::Void
}
ExprType::Field(parent_expr, field_expr) => {
let parent_value = expr_to_value(vm, parent_expr);
if let ExprType::Variable(field_str) = &field_expr.type_ {
let field_value = value::Value::String(field_str.to_owned());
if let value::Value::Dict(key_value_list) = parent_value {
for (key, value) in key_value_list {
if key == field_value {
return value.clone();
}
}
}
}
value::Value::Void
}
ExprType::Combine(x_expr, y_expr) => {
let x = expr_to_value(vm, x_expr);
let y = expr_to_value(vm, y_expr);
arithmetic::combine(x, y, &x_expr.loc)
}
...
exec_line
Add the assignment LineData case.
src/vm/mod.rs:
pub fn exec_line(vm: &mut VM, parsed_line: &Line) {
...
match &parsed_line.type_ {
...
LineType::CombineAssign(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::combine(left_value, right_value, &left_expr.loc);
assign(vm, left_expr, result);
}
}
}
Helper Functions
Add helper functions.
fn assign(vm: &mut VM, left_expr: &Expr, right_value: value::Value) {
if let Ok(value) = get_value_to_assign(vm, left_expr, right_value == value::Value::Void) {
*value = right_value;
};
}
fn get_value_to_assign<'a>(vm: &'a mut VM, expr: &'a Expr, is_void: bool) -> Result<&'a mut value::Value, ()> {
match &expr.type_ {
ExprType::Variable(var) => {
if let Some(address) = var_address(vm, var) {
return Ok(&mut vm.stack[address]);
}
}
ExprType::Index(parent_expr, _) |
ExprType::Field(parent_expr, _) => {
let child_value = indexed_value(vm, expr);
let list_or_dict = get_value_to_assign(vm, parent_expr, false)?;
match list_or_dict {
value::Value::List(list) => {
if !is_void {
if let value::Value::Int(mut index) = child_value {
if index < 0 {
index = list.len() as i64 + index;
}
let index = index as usize;
if index < list.len() {
return Ok(&mut list[index]);
}
}
}
}
value::Value::Dict(dict) => {
for i in 0..dict.len() {
if dict[i].0 == child_value {
return Ok(&mut dict[i].1);
}
}
dict.push((child_value, value::Value::Void));
let i = dict.len() - 1;
return Ok(&mut dict[i].1);
}
_ => {}
}
}
_ => { }
}
err::throw_loc(err::Err::CannotAssign, &expr.loc);
Err(())
}
fn indexed_value(vm: &mut VM, expr: &Expr) -> value::Value {
match &expr.type_ {
ExprType::Index(_, child_expr) =>
expr_to_value(vm, child_expr),
ExprType::Field(_, field_expr) => {
if let ExprType::Variable(field) = &field_expr.type_ {
value::Value::String(field.to_owned())
} else { unreachable!() }
}
_ => unreachable!()
}
}
value_repr
Add the repr.
src/vm/value_repr.rs:
use crate::vm::value;
pub fn value_repr(val: value::Value) -> String {
match val {
...
value::Value::List(values) => format!("[{}]",
values
.into_iter()
.map(|a| value_repr(a))
.collect::<Vec<String>>()
.join(", ")
),
value::Value::Dict(key_value_pairs) => format!("{{{}}}",
key_value_pairs
.into_iter()
.map(|(a, b)| value_repr(a) + " = " + &value_repr(b))
.collect::<Vec<String>>()
.join(", ")
),
}
}
Source Code
Source code for this part can be found here.