~cdv/loxen

d2af5d5803101889ccd77f035af8e7c2252cce4c — Chris Vittal 2 months ago bba9ce3
[relox] Variables
7 files changed, 488 insertions(+), 98 deletions(-)

M rs/src/chunk.rs
M rs/src/compiler.rs
M rs/src/object.rs
M rs/src/scanner.rs
M rs/src/value.rs
M rs/src/vm.rs
M util/test.py
M rs/src/chunk.rs => rs/src/chunk.rs +24 -27
@@ 7,7 7,7 @@ pub struct Chunk {
    lines: LineArray,
}

const MAX_CONSTANTS: usize = 0xffffff;
pub const MAX_CONSTANTS: usize = 0xffffff;

impl Chunk {
    pub fn write(&mut self, b: u8, line: u32) {


@@ 22,27 22,6 @@ impl Chunk {
        self.code.push(b);
    }

    pub fn write_op(&mut self, op: Op, line: u32) {
        self.write(op as u8, line);
    }

    pub fn write_constant(&mut self, v: Value, line: u32) -> Result<(), &'static str> {
        let ind = self.add_const(v);
        if ind > MAX_CONSTANTS {
            return Err("too many constants in chunk");
        }
        if ind < u8::MAX as usize {
            self.write_op(Op::Constant, line);
            self.write(ind as u8, line);
        } else {
            self.write_op(Op::ConstantLong, line);
            self.write(ind as u8, line);
            self.write((ind >> 8) as u8, line);
            self.write((ind >> 16) as u8, line);
        }
        Ok(())
    }

    pub fn add_const(&mut self, v: Value) -> usize {
        self.constants.push(v);
        self.constants.len() - 1


@@ 130,12 109,23 @@ impl ::std::fmt::Display for Op {
}

op_codes!(
    Return, OP_RETURN, "RETURN";
    Constant, OP_CONSTANT, "CONSTANT";
    ConstantLong, OP_CONSTANT_LONG, "CONSTANT_LONG";
    Nil, OP_NIL, "NIL";
    True, OP_TRUE, "TRUE";
    False, OP_FALSE, "FALSE";

    Pop, OP_POP, "POP";

    GetLocal, OP_GET_LOCAL, "GET_LOCAL";
    SetLocal, OP_SET_LOCAL, "SET_LOCAL";
    GetGlobal, OP_GET_GBL, "GET_GLOBAL";
    GetGlobalLong, OP_GET_GBL_LONG, "GET_GLOBAL_LONG";
    DefGlobal, OP_DEF_GBL, "DEFINE_GLOBAL";
    DefGlobalLong, OP_DEF_GBL_LONG, "DEFINE_GLOBAL_LONG";
    SetGlobal, OP_SET_GBL, "SET_GLOBAL";
    SetGlobalLong, OP_SET_GBL_LONG, "SET_GLOBAL_LONG";

    Equal, OP_EQUAL, "EQUAL";
    Greater, OP_GREATER, "GREATER";
    Less, OP_LESS, "LESS";


@@ 145,6 135,9 @@ op_codes!(
    Div, OP_DIVIDE, "DIVIDE";
    Not, OP_NOT, "NOT";
    Negate, OP_NEGATE, "NEGATE";

    Print, OP_PRINT, "PRINT";
    Return, OP_RETURN, "RETURN";
);

impl Op {


@@ 160,17 153,21 @@ impl Op {
            *prev_line = line;
        }
        match self {
            Return | Nil | True | False | Equal | Greater | Less | Add | Sub | Mul | Div
            | Negate | Not => {
            Return | Print | Pop | Nil | True | False | Equal | Greater | Less | Add | Sub
            | Mul | Div | Negate | Not => {
                eprintln!("{:16}", self);
                off + 1
            }
            Constant => {
            GetLocal | SetLocal => {
                eprintln!("{:16} {:4}", self, c.code[off + 1]);
                off + 2
            }
            Constant | GetGlobal | DefGlobal | SetGlobal => {
                let cid = c.code[off + 1] as usize;
                eprintln!("{:16} {:4} '{}'", self, cid, c.constants[cid]);
                off + 2
            }
            ConstantLong => {
            ConstantLong | GetGlobalLong | DefGlobalLong | SetGlobalLong => {
                let cid = (c.code[off + 1] as usize)
                    | (c.code[off + 2] as usize) << 8
                    | (c.code[off + 3] as usize) << 16;

M rs/src/compiler.rs => rs/src/compiler.rs +319 -48
@@ 1,22 1,39 @@
use std::{
    collections::hash_map::{Entry, HashMap},
    cell::Cell,
};

use crate::{
    chunk::*,
    object::Heap,
    scanner::{Scanner, Token, TokenType},
    value::Value,
    value::{HashValue, Value},
    Error,
};

pub fn compile(source: &str, heap: &mut Heap) -> crate::Result<Box<Chunk>> {
    let mut c = Compiler::new(source, heap);
    c.expression();
    c.consume(TokenType::Eof, "Expect end of expression.");
    while !c.match_(TokenType::Eof) {
        c.declaration();
    }
    c.end()
}

const U8_COUNT: usize = u8::MAX as usize + 1;

#[derive(Debug)]
struct Local<'s> {
    name: Token<'s>,
    depth: usize,
}

struct Compiler<'s, 'vm> {
    p: Parser<'s>,
    heap: &'vm mut Heap,
    chunk: Box<Chunk>,
    constants: HashMap<HashValue, usize>,
    depth: usize,
    locals: Vec<Local<'s>>,
}

impl<'s, 'vm> Compiler<'s, 'vm> {


@@ 25,12 42,15 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
            p: Parser::new(source),
            heap,
            chunk: Default::default(),
            constants: Default::default(),
            depth: 0,
            locals: Vec::with_capacity(128),
        }
    }

    fn end(mut self) -> crate::Result<Box<Chunk>> {
        self.emit_return();
        if self.p.had_err {
        if self.p.had_err.get() {
            Err(Error::Compile)
        } else {
            #[cfg(debug_assertions)]


@@ 41,14 61,99 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
        }
    }

    fn expression(&mut self) {
    fn declaration(&mut self) {
        if self.match_(TokenType::Var) {
            self.var_decl();
        } else {
            self.stmt();
        }

        if self.p.panicking.get() {
            self.synchronize();
        }
    }

    fn var_decl(&mut self) {
        let global = self.parse_var("Expect variable name.");
        if self.match_(TokenType::Equal) {
            self.expr();
        } else {
            self.emit_op(Op::Nil);
        }
        self.consume(TokenType::Semicolon, "Expect ';' after expression.");

        self.define_var(global);
    }

    fn stmt(&mut self) {
        if self.match_(TokenType::Print) {
            self.print_stmt();
        } else if self.match_(TokenType::LeftBrace) {
            self.begin_scope();
            self.block();
            self.end_scope();
        } else {
            self.expr_stmt();
        }
    }

    fn print_stmt(&mut self) {
        self.expr();
        self.consume(TokenType::Semicolon, "Expect ';' after value.");
        self.emit_op(Op::Print);
    }

    fn expr_stmt(&mut self) {
        self.expr();
        self.consume(TokenType::Semicolon, "Expect ';' after expression.");
        self.emit_op(Op::Pop);
    }

    fn block(&mut self) {
        while self.p.curr.ty != TokenType::RightBrace && self.p.curr.ty != TokenType::Eof {
            self.declaration();
        }

        self.consume(TokenType::RightBrace, "Expect '}' after block.");
    }

    fn expr(&mut self) {
        self.parse_prec(Prec::Assign);
    }

    fn unary(&mut self) {
    fn parse_prec(&mut self, prec: Prec) {
        self.adv();
        let can_assign = prec <= Prec::Assign;
        if let Some(prefix) = ParseRule::get(self.p.prev.ty).pre {
            prefix(self, can_assign)
        } else {
            self.error("Expect expression.");
            return;
        }

        while prec <= ParseRule::get(self.p.curr.ty).prec {
            self.adv();
            if let Some(infix) = ParseRule::get(self.p.prev.ty).inf {
                infix(self, can_assign);
            } else {
                unreachable!();
            }
        }

        if can_assign && self.match_(TokenType::Equal) {
            self.error("Invalid assignment target.");
        }
    }

    fn grouping(&mut self, _: bool) {
        self.expr();
        self.consume(TokenType::RightParen, "Expect ')' after expression.");
    }

    fn unary(&mut self, _: bool) {
        let ty = self.p.prev.ty;

        self.expression();
        self.parse_prec(Prec::Unary);

        match ty {
            TokenType::Bang => self.emit_op(Op::Not),


@@ 57,7 162,7 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
        }
    }

    fn binary(&mut self) {
    fn binary(&mut self, _: bool) {
        use TokenType::*;
        let ty = self.p.prev.ty;



@@ 79,7 184,7 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
        }
    }

    fn literal(&mut self) {
    fn literal(&mut self, _: bool) {
        match self.p.prev.ty {
            TokenType::False => self.emit_op(Op::False),
            TokenType::Nil => self.emit_op(Op::Nil),


@@ 88,12 193,7 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
        }
    }

    fn grouping(&mut self) {
        self.expression();
        self.consume(TokenType::RightParen, "Expect ')' after expression.");
    }

    fn number(&mut self) {
    fn number(&mut self, _: bool) {
        let n = self
            .s()
            .parse::<f64>()


@@ 101,29 201,167 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
        self.emit_constant(n.into());
    }

    fn string(&mut self) {
    fn string(&mut self, _: bool) {
        let s = &self.p.prev.s[1..self.p.prev.s.len() - 1];
        let oid = self.heap.clone_str(s);
        self.emit_constant(Value::Str(oid));
    }

    fn parse_prec(&mut self, prec: Prec) {
        self.adv();
        if let Some(prefix) = ParseRule::get(self.p.prev.ty).pre {
            prefix(self)
    fn variable(&mut self, can_assign: bool) {
        self.named_variable(self.p.prev, can_assign);
    }

    fn named_variable(&mut self, t: Token<'_>, can_assign: bool) {
        let getop;
        let setop;
        let mut arg = self.resolve_local(t);
        if arg != usize::MAX {
            debug_assert!(arg < U8_COUNT);
            setop = Op::SetLocal;
            getop = Op::GetLocal;
        } else {
            arg = self.ident_constant(t);
            setop = Op::SetGlobal;
            getop = Op::GetGlobal;
        }


        let (op, op_long) = if can_assign && self.match_(TokenType::Equal) {
            self.expr();
            (setop, Op::SetGlobalLong)
        } else {
            self.error("Expect expression");
            (getop, Op::GetGlobalLong)
        };

        if arg < u8::MAX as usize {
            self.emit_a(op, arg as u8);
        } else {
            self.emit_op(op_long);
            self.emit(arg as u8);
            self.emit((arg >> 8) as u8);
            self.emit((arg >> 16) as u8);
        }
    }

    fn parse_var(&mut self, msg: &str) -> usize {
        self.consume(TokenType::Ident, msg);

        self.declare_var();
        if self.depth > 0 {
            return 0;
        }

        self.ident_constant(self.p.prev)
    }

    fn define_var(&mut self, global: usize) {
        if self.depth > 0 {
            self.mark_init();
            return;
        }

        while prec <= ParseRule::get(self.p.curr.ty).prec {
            self.adv();
            if let Some(infix) = ParseRule::get(self.p.prev.ty).inf {
                infix(self);
            } else {
                unreachable!();
        debug_assert!(global < MAX_CONSTANTS);

        if global < u8::MAX as usize {
            self.emit_a(Op::DefGlobal, global as u8);
        } else {
            self.emit_op(Op::DefGlobalLong);
            self.emit(global as u8);
            self.emit((global >> 8) as u8);
            self.emit((global >> 16) as u8);
        }
    }

    fn declare_var(&mut self) {
        if self.depth == 0 {
            return;
        }

        let name = self.p.prev;

        for local in self.locals.iter().rev() {
            if local.depth != usize::MAX && local.depth < self.depth {
                break;
            }

            if name.s == local.name.s {
                self.error("Variable with this name already declared in this scope.");
            }
        }

        self.add_local(self.p.prev);
    }

    fn mark_init(&mut self) {
        if let Some(ref mut local) = self.locals.last_mut() {
            local.depth = self.depth;
        } else {
            unreachable!("no locals, this is a bug");
        }
    }

    fn add_local(&mut self, t: Token<'s>) {
        if self.locals.len() > U8_COUNT {
            self.error("Too many local variables in function");
            return;
        }

        self.locals.push(Local {
            name: t,
            depth: usize::MAX
        });
    }

    fn resolve_local(&self, name: Token<'_>) -> usize {
        for i in (0..self.locals.len()).rev() {
            let local = &self.locals[i];
            if local.name.s == name.s {
                if local.depth == usize::MAX {
                    self.error("Cannot read local variable in its own initializer.");
                }
                return i;
            }
        }

        usize::MAX
    }

    fn ident_constant(&mut self, t: Token<'_>) -> usize {
        let oid = self.heap.clone_str(t.s);
        self.make_const(Value::Str(oid))
    }

    fn make_const(&mut self, v: Value) -> usize {
        let cind = match self.constants.entry(v.into()) {
            Entry::Occupied(e) => return *e.get(),
            Entry::Vacant(e) => {
                let cind = self.chunk.add_const(v);

                *e.insert(cind)
            }
        };

        if cind >= MAX_CONSTANTS {
            self.error("Too many constants in one chunk.");
        }
        cind
    }

    fn begin_scope(&mut self) {
        self.depth += 1;
    }

    fn end_scope(&mut self) {
        self.depth -= 1;

        while let Some(&Local { depth, .. }) = self.locals.last() {
            if depth <= self.depth {
                break;
            }

            self.emit_op(Op::Pop);
            self.locals.pop();
        }
    }

    fn emit_return(&mut self) {


@@ 131,23 369,31 @@ impl<'s, 'vm> Compiler<'s, 'vm> {
    }

    fn emit_constant(&mut self, v: Value) {
        if let Err(m) = self.chunk.write_constant(v, self.line()) {
            self.error(m);
        let ind = self.make_const(v);

        if ind < u8::MAX as usize {
            self.emit_a(Op::Constant, ind as u8);
        } else {
            self.emit_op(Op::ConstantLong);
            self.emit(ind as u8);
            self.emit((ind >> 8) as u8);
            self.emit((ind >> 16) as u8);
        }
    }

    #[inline(always)]
    fn emit_op(&mut self, op: Op) {
        self.emit(op as u8);
    }

    fn emit_op2(&mut self, op1: Op, op2: Op) {
        self.emit(op1 as u8);
        self.emit(op2 as u8);
        self.emit_op(op1);
        self.emit_op(op2);
    }

    fn _emit2(&mut self, b1: u8, b2: u8) {
        self.emit(b1);
        self.emit(b2);
    fn emit_a(&mut self, op: Op, a: u8) {
        self.emit_op(op);
        self.emit(a);
    }

    fn emit(&mut self, b: u8) {


@@ 173,8 419,8 @@ struct Parser<'s> {
    scan: Scanner<'s>,
    prev: Token<'s>,
    curr: Token<'s>,
    had_err: bool,
    panicking: bool,
    had_err: Cell<bool>,
    panicking: Cell<bool>,
}

impl<'s> Parser<'s> {


@@ 189,8 435,8 @@ impl<'s> Parser<'s> {
            scan: Scanner::new(source),
            prev: PLACEHOLDER,
            curr: PLACEHOLDER,
            had_err: false,
            panicking: false,
            had_err: Cell::new(false),
            panicking: Cell::new(false),
        };
        p.adv();
        p


@@ 205,6 451,15 @@ impl<'s> Parser<'s> {
        self.error_at_current(msg);
    }

    fn match_(&mut self, ty: TokenType) -> bool {
        if self.curr.ty == ty {
            self.adv();
            true
        } else {
            false
        }
    }

    fn adv(&mut self) {
        self.prev = self.curr;



@@ 219,21 474,37 @@ impl<'s> Parser<'s> {
        }
    }

    fn error(&mut self, msg: &str) {
    fn synchronize(&mut self) {
        use TokenType::*;
        self.panicking.set(false);
        while self.curr.ty != Eof {
            if self.prev.ty == Semicolon {
                return;
            }

            match self.curr.ty {
                Class | Fun | Var | For | If | While | Print | Return => return,
                _ => self.adv(),
            }
        }
    }

    fn error(&self, msg: &str) {
        self.error_at(&self.prev, msg);
        self.had_err = true;
    }

    fn error_at_current(&mut self, msg: &str) {
    fn error_at_current(&self, msg: &str) {
        self.error_at(&self.curr, msg);
        self.had_err = true;
    }

    fn error_at(&self, tok: &Token<'_>, msg: &str) {
        if self.panicking {
        if self.panicking.get() {
            return;
        }

        self.had_err.set(true);
        self.panicking.set(true);

        eprint!("[line {}] Error", tok.line);
        if tok.ty == TokenType::Eof {
            eprint!(" at end");


@@ 288,15 559,15 @@ impl Prec {
}

struct ParseRule<'s, 'vm> {
    pre: Option<fn(&mut Compiler<'s, 'vm>)>,
    inf: Option<fn(&mut Compiler<'s, 'vm>)>,
    pre: Option<fn(&mut Compiler<'s, 'vm>, bool)>,
    inf: Option<fn(&mut Compiler<'s, 'vm>, bool)>,
    prec: Prec,
}

impl<'s, 'vm> ParseRule<'s, 'vm> {
    fn new(
        pre: Option<fn(&mut Compiler<'s, 'vm>)>,
        inf: Option<fn(&mut Compiler<'s, 'vm>)>,
        pre: Option<fn(&mut Compiler<'s, 'vm>, bool)>,
        inf: Option<fn(&mut Compiler<'s, 'vm>, bool)>,
        prec: Prec,
    ) -> Self {
        Self { pre, inf, prec }


@@ 327,7 598,7 @@ impl<'s, 'vm> ParseRule<'s, 'vm> {
            Less => Self::new(None, Some(Compiler::binary), Prec::Cmp),
            LessEq => Self::new(None, Some(Compiler::binary), Prec::Cmp),

            Ident => Self::new(None, None, Prec::None),
            Ident => Self::new(Some(Compiler::variable), None, Prec::None),
            Str => Self::new(Some(Compiler::string), None, Prec::None),
            Num => Self::new(Some(Compiler::number), None, Prec::None),


M rs/src/object.rs => rs/src/object.rs +18 -1
@@ 1,5 1,8 @@
use std::{collections::hash_map::HashMap, fmt, rc::Rc};

use crate::value::Value;

#[derive(Debug)]
pub enum Obj {
    Str(Rc<str>),
    #[allow(unused)]


@@ 7,13 10,14 @@ pub enum Obj {
}

#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Oid(usize);

#[derive(Default)]
pub struct Heap {
    objs: Vec<Obj>,
    strings: HashMap<Rc<str>, Oid>,
    globals: HashMap<Oid, Value>,
}

impl std::ops::Index<Oid> for Heap {


@@ 45,6 49,19 @@ impl From<String> for Obj {
}

impl Heap {
    pub fn get_global(&self, oid: Oid) -> Option<Value> {
        self.globals.get(&oid).copied()
    }

    pub fn set_global(&mut self, oid: Oid, v: Value) -> Option<Value> {
        debug_assert!(matches!(&self[oid], Obj::Str(_)));
        self.globals.insert(oid, v)
    }

    pub fn del_global(&mut self, oid: Oid) -> Option<Value> {
        self.globals.remove(&oid)
    }

    pub fn clone_str(&mut self, s: &str) -> Oid {
        match self.strings.get(s) {
            Some(&oid) => oid,

M rs/src/scanner.rs => rs/src/scanner.rs +7 -2
@@ 122,8 122,13 @@ impl<'s> Scanner<'s> {
            }
            self.adv();
        }
        self.adv();
        self.make_token(Str)

        if self.at_end() {
            self.err_token("Unterminated string.")
        } else {
            self.adv();
            self.make_token(Str)
        }
    }

    fn skip_whitespace(&mut self) {

M rs/src/value.rs => rs/src/value.rs +37 -1
@@ 52,7 52,43 @@ impl fmt::Display for Value {
            Self::Bool(b) => <bool as fmt::Display>::fmt(b, f),
            Self::Nil => f.pad("nil"),
            Self::Str(oid) => write!(f, "str {:#06x}", oid),
            Self::Num(n) => <f64 as fmt::Display>::fmt(n, f),
            Self::Num(n) => {
                if n == &0f64 && n.signum() == -1. {
                    f.pad("-0")
                } else {
                    <f64 as fmt::Display>::fmt(n, f)
                }
            }
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HashValue {
    Bool(bool),
    Nil,
    Num(u64),
    Str(Oid),
}

impl From<Value> for HashValue {
    fn from(v: Value) -> Self {
        match v {
            Value::Bool(b) => Self::Bool(b),
            Value::Nil => Self::Nil,
            Value::Num(n) => Self::Num(n.to_bits()),
            Value::Str(o) => Self::Str(o),
        }
    }
}

impl From<HashValue> for Value {
    fn from(v: HashValue) -> Self {
        match v {
            HashValue::Bool(b) => Self::Bool(b),
            HashValue::Nil => Self::Nil,
            HashValue::Num(n) => Self::Num(f64::from_bits(n)),
            HashValue::Str(o) => Self::Str(o),
        }
    }
}

M rs/src/vm.rs => rs/src/vm.rs +68 -11
@@ 41,7 41,7 @@ impl VM {
                a += &b;
                a
            }
            _ => unreachable!(),
            objs => unreachable!("must be both strings, got {:?}", objs),
        };
        self.pop();
        self.pop();


@@ 53,7 53,7 @@ impl VM {
        macro_rules! binary_op {
            ($op:tt) => {
                if !self.peek(0).is_num() || !self.peek(1).is_num() {
                    self.runtime_error(format_args!("Operands must be numbers"))?;
                    self.runtime_error(format_args!("Operands must be numbers."))?;
                }

                let b = self.pop();


@@ 87,6 87,56 @@ impl VM {
                Nil => self.push(Value::Nil),
                True => self.push(true),
                False => self.push(false),
                Pop => {
                    self.pop();
                }
                GetLocal => {
                    let slot = self.read_byte() as usize;
                    self.push(self.stack[slot]); // FIXME temporary
                }
                SetLocal => {
                    let slot = self.read_byte() as usize;
                    self.stack[slot] = self.peek(0);
                }
                GetGlobal | GetGlobalLong => {
                    let oid = if let Value::Str(oid) = self.read_constant(op) {
                        oid
                    } else {
                        unreachable!()
                    };

                    if let Some(v) = self.heap.get_global(oid) {
                        self.push(v);
                    } else {
                        self.runtime_error(format_args!(
                            "Undefined variable '{}'.",
                            self.heap[oid]
                        ))?;
                    }
                }
                DefGlobal | DefGlobalLong => {
                    if let Value::Str(oid) = self.read_constant(op) {
                        let _ = self.heap.set_global(oid, self.peek(0));
                        self.pop();
                    } else {
                        unreachable!()
                    }
                }
                SetGlobal | SetGlobalLong => {
                    let oid = if let Value::Str(oid) = self.read_constant(op) {
                        oid
                    } else {
                        unreachable!()
                    };

                    if let None = self.heap.set_global(oid, self.peek(0)) {
                        self.heap.del_global(oid);
                        self.runtime_error(format_args!(
                            "Undefined variable '{}'.",
                            self.heap[oid]
                        ))?;
                    }
                }
                Equal => {
                    let b = self.pop();
                    let a = self.pop();


@@ 99,13 149,17 @@ impl VM {
                    binary_op!(<);
                }
                Add => match (self.peek(1), self.peek(0)) {
                    (&Value::Str(sa), &Value::Str(sb)) => self.concatenate(sa, sb),
                    (&Value::Num(a), &Value::Num(b)) => {
                    (Value::Str(sa), Value::Str(sb)) => self.concatenate(sa, sb),
                    (Value::Num(a), Value::Num(b)) => {
                        self.pop();
                        self.pop();
                        self.push(a + b);
                    }
                    _ => todo!(),
                    _ => {
                        self.runtime_error(format_args!(
                            "Operands must be two numbers or two strings."
                        ))?;
                    }
                },
                Sub => {
                    binary_op!(-);


@@ 122,7 176,7 @@ impl VM {
                }
                Negate => {
                    if !self.peek(0).is_num() {
                        self.runtime_error(format_args!("Operand must be a number"))?;
                        self.runtime_error(format_args!("Operand must be a number."))?;
                    }

                    if let Value::Num(v) = self.pop() {


@@ 131,9 185,12 @@ impl VM {
                        unreachable!();
                    }
                }
                Return => {
                Print => {
                    let v = self.pop();
                    println!("{}", self.vfmt(&v));
                }
                Return => {
                    // Exit
                    return Ok(());
                }
            }


@@ 142,11 199,11 @@ impl VM {

    fn read_constant(&mut self, op: Op) -> Value {
        match op {
            Constant => {
            Constant | GetGlobal | DefGlobal | SetGlobal => {
                let cid = self.read_byte() as usize;
                self.chunk.constants[cid]
            }
            ConstantLong => {
            ConstantLong | GetGlobalLong | DefGlobalLong | SetGlobalLong => {
                let nip = self.ip + 3;
                let mut cid_buf = &self.chunk[self.ip..nip];
                self.ip = nip;


@@ 175,8 232,8 @@ impl VM {
        self.stack.pop().expect("Attempted to pop from empty stack")
    }

    fn peek(&self, dist: usize) -> &Value {
        &self.stack[self.stack.len() - dist - 1]
    fn peek(&self, dist: usize) -> Value {
        self.stack[self.stack.len() - dist - 1]
    }

    fn vfmt<'vm>(&'vm self, v: &'vm Value) -> VRef {

M util/test.py => util/test.py +15 -8
@@ 54,6 54,14 @@ def go_interpreter(name, tests):
    INTERPRETERS[name] = Interpreter(name, 'golang', ['go/glox'], tests)


def rust_interpreter(name, tests, disable=False):
    if disable:
        return

    INTERPRETERS[name] = Interpreter(name, 'rust', ['rs/target/release/relox'],
                                     tests)


go_interpreter(
    'glox',
    tests={


@@ 96,10 104,10 @@ c_interpreter(
        'test/expressions/evaluate.lox': 'pass',
    })

c_interpreter(
    'chap21_global',
    implemented=False,  # subsumed by later tests
    rename='cdvlox',
# chapeter 21 tests
rust_interpreter(
    'relox',
    disable=True,
    tests={
        'test': 'pass',



@@ 166,10 174,9 @@ c_interpreter(
        'test/variable/local_from_method.lox': 'skip',
    })

c_interpreter(
    'chap22_local',
    implemented=False,  # subsumed by later tests
    rename='cdvlox',
# chapter 22 tests
rust_interpreter(
    'relox',
    tests={
        'test': 'pass',