Interp - an Interpreted Programming Language Pt.11 - List and Dict

5 minute read

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.

Next Up

Click for Pt. 12 or click here for all parts.

Updated: