[rlox] Bugfixes, lots of bugfixes
This commit is contained in:
@@ -262,7 +262,7 @@ impl<'src> Iterator for Scanner<'src> {
|
|||||||
} else if start_ch == '"' {
|
} else if start_ch == '"' {
|
||||||
match self.scan_string() {
|
match self.scan_string() {
|
||||||
Ok(end) => self.make_token(TokenType::String, start_line, start_pos, end),
|
Ok(end) => self.make_token(TokenType::String, start_line, start_pos, end),
|
||||||
Err(end) => self.make_token(TokenType::Error(ScanErrorKind::UndelimitedString), start_line, start_pos, end),
|
Err(end) => self.make_token(TokenType::Error(ScanErrorKind::UndelimitedString), start_line, start_pos, end - 1),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid character");
|
panic!("Invalid character");
|
||||||
@@ -292,6 +292,9 @@ pub enum ParseErrorKind {
|
|||||||
NoSemicolonAfterExpression,
|
NoSemicolonAfterExpression,
|
||||||
NoVariableName,
|
NoVariableName,
|
||||||
NoSemicolonAfterVarDecl,
|
NoSemicolonAfterVarDecl,
|
||||||
|
InvalidAssignmentTarget,
|
||||||
|
InvalidVariableName,
|
||||||
|
ScanError(ScanErrorKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ParseErrorKind {
|
impl fmt::Display for ParseErrorKind {
|
||||||
@@ -303,6 +306,10 @@ impl fmt::Display for ParseErrorKind {
|
|||||||
ParseErrorKind::NoSemicolonAfterExpression => write!(f, "Expect ';' after expression."),
|
ParseErrorKind::NoSemicolonAfterExpression => write!(f, "Expect ';' after expression."),
|
||||||
ParseErrorKind::NoVariableName => write!(f, "Expect variable name."),
|
ParseErrorKind::NoVariableName => write!(f, "Expect variable name."),
|
||||||
ParseErrorKind::NoSemicolonAfterVarDecl => write!(f, "Expect ';' after variable declaration."),
|
ParseErrorKind::NoSemicolonAfterVarDecl => write!(f, "Expect ';' after variable declaration."),
|
||||||
|
ParseErrorKind::InvalidAssignmentTarget => write!(f, "Invalid assignment target."),
|
||||||
|
ParseErrorKind::InvalidVariableName => write!(f, "Expect variable name."),
|
||||||
|
ParseErrorKind::ScanError(ScanErrorKind::UndelimitedString) =>
|
||||||
|
write!(f, "Unterminated string.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,6 +378,7 @@ impl<'src> Parser<'src> {
|
|||||||
Star | Slash => Some(Precedence::Factor),
|
Star | Slash => Some(Precedence::Factor),
|
||||||
EqualEqual | BangEqual => Some(Precedence::Equality),
|
EqualEqual | BangEqual => Some(Precedence::Equality),
|
||||||
Greater | GreaterEqual | Less | LessEqual => Some(Precedence::Comparison),
|
Greater | GreaterEqual | Less | LessEqual => Some(Precedence::Comparison),
|
||||||
|
Equal => Some(Precedence::Assignment),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,13 +478,21 @@ impl<'src> Parser<'src> {
|
|||||||
let offset = self.add_string(chunk, token.span);
|
let offset = self.add_string(chunk, token.span);
|
||||||
|
|
||||||
if self.scanner.peek().is_some_and(|t| t.ttype == TokenType::Equal) {
|
if self.scanner.peek().is_some_and(|t| t.ttype == TokenType::Equal) {
|
||||||
self.scanner.next();
|
if (min_prec <= Precedence::Assignment) {
|
||||||
self._expression(chunk, Precedence::Assignment)?;
|
self.scanner.next();
|
||||||
chunk.add_op(Op::SetGlobal {offset}, token.line);
|
self._expression(chunk, Precedence::Assignment)?;
|
||||||
|
chunk.add_op(Op::SetGlobal {offset}, token.line);
|
||||||
|
} else {
|
||||||
|
let eq_token = self.scanner.next().unwrap();
|
||||||
|
return Err(self.error_at(eq_token, ParseErrorKind::InvalidAssignmentTarget));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chunk.add_op(Op::GetGlobal {offset}, token.line);
|
chunk.add_op(Op::GetGlobal {offset}, token.line);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
TokenType::Error(err) => {
|
||||||
|
return Err(self.error_at(token, ParseErrorKind::ScanError(err)));
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(self.error_at(token, ParseErrorKind::IncompleteExpression));
|
return Err(self.error_at(token, ParseErrorKind::IncompleteExpression));
|
||||||
}
|
}
|
||||||
@@ -514,6 +530,7 @@ impl<'src> Parser<'src> {
|
|||||||
TokenType::BangEqual => chunk.add_op(Op::Equal, op.line).add_op(Op::Not, op.line),
|
TokenType::BangEqual => chunk.add_op(Op::Equal, op.line).add_op(Op::Not, op.line),
|
||||||
TokenType::GreaterEqual => chunk.add_op(Op::Less, op.line).add_op(Op::Not, op.line),
|
TokenType::GreaterEqual => chunk.add_op(Op::Less, op.line).add_op(Op::Not, op.line),
|
||||||
TokenType::LessEqual => chunk.add_op(Op::Greater, op.line).add_op(Op::Not, op.line),
|
TokenType::LessEqual => chunk.add_op(Op::Greater, op.line).add_op(Op::Not, op.line),
|
||||||
|
TokenType::Equal => {return Err(self.error_at(op, ParseErrorKind::InvalidAssignmentTarget))},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -562,13 +579,32 @@ impl<'src> Parser<'src> {
|
|||||||
fn synchronize(&mut self) {
|
fn synchronize(&mut self) {
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
|
|
||||||
while let Some(_token) = self.scanner.next_if(
|
while let Some(peek) = self.scanner.peek() {
|
||||||
|tok| ![Semicolon, Class, Fun, Var, For, If, While, Print, Return].contains(&tok.ttype)
|
if peek.ttype == TokenType::Semicolon {
|
||||||
) {}
|
self.scanner.next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if [Class, Fun, Var, For, If, While, Print, Return].contains(&peek.ttype) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scanner.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variable(&mut self) -> Result<'src, Token<'src>> {
|
||||||
|
let ident = self.must_consume(TokenType::Identifier, ParseErrorKind::NoVariableName)?;
|
||||||
|
|
||||||
|
if (ident.span == "nil") {
|
||||||
|
Err(self.error_at(ident, ParseErrorKind::InvalidVariableName))
|
||||||
|
} else {
|
||||||
|
Ok(ident)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn var_declaration(&mut self, var_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
fn var_declaration(&mut self, var_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
let ident = self.must_consume(TokenType::Identifier, ParseErrorKind::NoVariableName)?;
|
let ident = self.variable()?;
|
||||||
let offset = self.add_string(chunk, ident.span);
|
let offset = self.add_string(chunk, ident.span);
|
||||||
|
|
||||||
match self.scanner.peek() {
|
match self.scanner.peek() {
|
||||||
@@ -593,7 +629,10 @@ impl<'src> Parser<'src> {
|
|||||||
match peeked.ttype {
|
match peeked.ttype {
|
||||||
TokenType::Var => {
|
TokenType::Var => {
|
||||||
self.scanner.next();
|
self.scanner.next();
|
||||||
self.var_declaration(peeked, chunk);
|
if let Err(err) = self.var_declaration(peeked, chunk) {
|
||||||
|
self.errors.push(err);
|
||||||
|
self.synchronize();
|
||||||
|
};
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.statement(chunk).unwrap_or_else(
|
self.statement(chunk).unwrap_or_else(
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ mod gc;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use bc::Chunk;
|
use bc::Chunk;
|
||||||
use vm::VM;
|
use vm::VM;
|
||||||
|
|
||||||
|
|
||||||
fn compile_and_run(source: &str, do_trace: bool) {
|
fn compile_and_run(source: &str, do_trace: bool) -> ExitCode {
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
let errors = lc::compile(source, &mut chunk);
|
let errors = lc::compile(source, &mut chunk);
|
||||||
|
|
||||||
@@ -19,12 +20,17 @@ fn compile_and_run(source: &str, do_trace: bool) {
|
|||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.set_trace(do_trace);
|
vm.set_trace(do_trace);
|
||||||
if let Err(err) = vm.stdrun(&chunk) {
|
if let Err(err) = vm.stdrun(&chunk) {
|
||||||
eprintln!("{:?}", err);
|
eprintln!("{}", err);
|
||||||
|
ExitCode::from(70)
|
||||||
|
} else {
|
||||||
|
ExitCode::from(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
eprintln!("{}", error)
|
eprintln!("{}", error);
|
||||||
}
|
}
|
||||||
|
ExitCode::from(65)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,21 +51,23 @@ fn repl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_file(path: String) {
|
fn run_file(path: String) -> ExitCode {
|
||||||
let do_trace = env::var("LOX_TRACE").is_ok();
|
let do_trace = env::var("LOX_TRACE").is_ok();
|
||||||
let source = fs::read_to_string(path).unwrap();
|
let source = fs::read_to_string(path).unwrap();
|
||||||
compile_and_run(source.as_str(), do_trace);
|
compile_and_run(source.as_str(), do_trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> ExitCode {
|
||||||
let num_args = env::args().len();
|
let num_args = env::args().len();
|
||||||
|
|
||||||
if num_args == 1 {
|
if num_args == 1 {
|
||||||
repl()
|
repl();
|
||||||
|
ExitCode::SUCCESS
|
||||||
} else if num_args == 2 {
|
} else if num_args == 2 {
|
||||||
let source = env::args().nth(1).unwrap();
|
let source = env::args().nth(1).unwrap();
|
||||||
run_file(source);
|
run_file(source)
|
||||||
} else {
|
} else {
|
||||||
println!("Usage: rlox [path]")
|
println!("Usage: rlox [path]");
|
||||||
|
ExitCode::from(64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
rlox/src/vm.rs
145
rlox/src/vm.rs
@@ -1,19 +1,55 @@
|
|||||||
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
||||||
use crate::gc::{GcHandle, ObjString, ObjectType, GC};
|
use crate::gc::{GcHandle, ObjString, ObjectType, GC};
|
||||||
use std::collections::{hash_map, HashMap, LinkedList};
|
use std::collections::{hash_map, HashMap, LinkedList};
|
||||||
use std::io;
|
use std::{fmt, io};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
pub trace: bool,
|
pub trace: bool,
|
||||||
stack: Vec<Value>,
|
stack: Vec<Value>,
|
||||||
pc: usize,
|
pc: usize,
|
||||||
|
line: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum VMError {
|
pub struct VMError {
|
||||||
// Compile,
|
line: usize,
|
||||||
Runtime(Rc<str>, usize),
|
kind: VMErrorKind,
|
||||||
|
msg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VMError {
|
||||||
|
fn new(kind: VMErrorKind, line: usize) -> Self {
|
||||||
|
VMError { line, kind, msg: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum VMErrorKind {
|
||||||
|
InvalidAddOperands,
|
||||||
|
InvalidMathOperands,
|
||||||
|
InvalidMathOperand,
|
||||||
|
UndefinedVariable,
|
||||||
|
PopFromEmptyStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VMError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self.kind {
|
||||||
|
VMErrorKind::InvalidAddOperands =>
|
||||||
|
{write!(f, "Operands must be two numbers or two strings.")?; }
|
||||||
|
VMErrorKind::InvalidMathOperands =>
|
||||||
|
{write!(f, "Operands must be numbers.")?; }
|
||||||
|
VMErrorKind::InvalidMathOperand =>
|
||||||
|
{write!(f, "Operand must be a number.")?; }
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(msg) = &self.msg {
|
||||||
|
write!(f, "{}", msg)?
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "\n[line {}]", self.line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, VMError>;
|
type Result<T> = std::result::Result<T, VMError>;
|
||||||
@@ -24,6 +60,7 @@ impl VM {
|
|||||||
trace: false,
|
trace: false,
|
||||||
stack: Vec::new(),
|
stack: Vec::new(),
|
||||||
pc: 0,
|
pc: 0,
|
||||||
|
line: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,34 +68,40 @@ impl VM {
|
|||||||
self.trace = trace;
|
self.trace = trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_err(&self, msg: &'static str) -> VMError {
|
|
||||||
VMError::Runtime(msg.into(), self.pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_err(&self, expected: &'static str, found: Value) -> VMError {
|
|
||||||
VMError::Runtime(
|
|
||||||
format!("Expected: {:}, Found: {:?}", expected, found).into(),
|
|
||||||
self.pc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, value: Value) {
|
fn push(&mut self, value: Value) {
|
||||||
self.stack.push(value);
|
self.stack.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn err(&mut self, kind: VMErrorKind) -> VMError {
|
||||||
|
VMError::new(
|
||||||
|
kind,
|
||||||
|
self.line
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn pop(&mut self) -> Result<Value> {
|
fn pop(&mut self) -> Result<Value> {
|
||||||
self.stack
|
self.stack
|
||||||
.pop()
|
.pop()
|
||||||
.ok_or(self.runtime_err("Attempt to pop of empty stack."))
|
.ok_or(self.err(VMErrorKind::PopFromEmptyStack))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_num(&mut self) -> Result<f64> {
|
fn pop_num(&mut self) -> Result<f64> {
|
||||||
let top_of_stack = self.pop()?;
|
let top_of_stack = self.pop()?;
|
||||||
top_of_stack
|
top_of_stack
|
||||||
.as_num()
|
.as_num()
|
||||||
.ok_or(self.type_err("Number", top_of_stack))
|
.ok_or(self.err(VMErrorKind::InvalidMathOperand))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_nums(&mut self) -> Result<(f64, f64)> {
|
||||||
|
let a = self.pop()?;
|
||||||
|
let b = self.pop()?;
|
||||||
|
match (a.as_num(), b.as_num()) {
|
||||||
|
(Some(a), Some(b)) => Ok((a, b)),
|
||||||
|
_ => Err(self.err(VMErrorKind::InvalidMathOperands))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn stdrun(
|
pub fn stdrun(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &Chunk,
|
chunk: &Chunk,
|
||||||
@@ -74,8 +117,17 @@ impl VM {
|
|||||||
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
||||||
let mut globals: HashMap<ObjString, Value> = HashMap::new();
|
let mut globals: HashMap<ObjString, Value> = HashMap::new();
|
||||||
|
|
||||||
|
let err = |kind: VMErrorKind, msg: Option<String>| -> VMError {
|
||||||
|
VMError {
|
||||||
|
line: chunk.debug_info[self.pc - 1],
|
||||||
|
kind,
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
let instr = chunk.code[self.pc];
|
let instr = chunk.code[self.pc];
|
||||||
|
self.line = chunk.debug_info[self.pc];
|
||||||
self.pc += 1;
|
self.pc += 1;
|
||||||
|
|
||||||
if self.trace {
|
if self.trace {
|
||||||
@@ -110,7 +162,8 @@ impl VM {
|
|||||||
let new_val = match top_of_stack {
|
let new_val = match top_of_stack {
|
||||||
Value::Nil => Ok(true),
|
Value::Nil => Ok(true),
|
||||||
Value::Bool(val) => Ok(!val),
|
Value::Bool(val) => Ok(!val),
|
||||||
_ => Err(self.type_err("Boolean or Nil", top_of_stack)),
|
_ => Err(self.err(VMErrorKind::InvalidMathOperand)),
|
||||||
|
//_ => Err(self.type_err("Boolean or Nil", top_of_stack)),
|
||||||
}?;
|
}?;
|
||||||
self.push(new_val.into());
|
self.push(new_val.into());
|
||||||
}
|
}
|
||||||
@@ -118,8 +171,9 @@ impl VM {
|
|||||||
let b = self.pop()?;
|
let b = self.pop()?;
|
||||||
match b {
|
match b {
|
||||||
Value::Number(num) => {
|
Value::Number(num) => {
|
||||||
let a = self.pop_num()?;
|
let a = self.pop_num().or(Err(self.err(VMErrorKind::InvalidAddOperands)))?;
|
||||||
self.push(Value::from(num + a));
|
self.push(Value::from(num + a));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Value::Obj(b) => match b.get_otype() {
|
Value::Obj(b) => match b.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
@@ -134,21 +188,15 @@ impl VM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => Err(self.type_err("String", a)),
|
_ => Err(self.err(VMErrorKind::InvalidAddOperands)),
|
||||||
}?
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => Err(self.err(VMErrorKind::InvalidAddOperands)),
|
||||||
return Err(VMError::Runtime(
|
}?
|
||||||
"Operands of + need to be numbers or strings".into(),
|
|
||||||
self.pc,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Op::Subtract | Op::Multiply | Op::Divide => {
|
Op::Subtract | Op::Multiply | Op::Divide => {
|
||||||
let b = self.pop_num()?;
|
let (b, a) = self.pop_nums()?;
|
||||||
let a = self.pop_num()?;
|
|
||||||
let r = match instr {
|
let r = match instr {
|
||||||
Op::Subtract => a - b,
|
Op::Subtract => a - b,
|
||||||
Op::Multiply => a * b,
|
Op::Multiply => a * b,
|
||||||
@@ -158,8 +206,7 @@ impl VM {
|
|||||||
self.push(r.into())
|
self.push(r.into())
|
||||||
}
|
}
|
||||||
Op::Greater | Op::Less => {
|
Op::Greater | Op::Less => {
|
||||||
let b = self.pop_num()?;
|
let (b, a) = self.pop_nums()?;
|
||||||
let a = self.pop_num()?;
|
|
||||||
let r = match instr {
|
let r = match instr {
|
||||||
Op::Greater => a > b,
|
Op::Greater => a > b,
|
||||||
Op::Less => a < b,
|
Op::Less => a < b,
|
||||||
@@ -175,8 +222,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
Op::Print => {
|
Op::Print => {
|
||||||
let value = self.pop()?;
|
let value = self.pop()?;
|
||||||
writeln!(output, "{}", value)
|
writeln!(output, "{}", value).unwrap()
|
||||||
.map_err(|_| VMError::Runtime("Failed to print".into(), self.pc))?
|
|
||||||
},
|
},
|
||||||
Op::Pop => {
|
Op::Pop => {
|
||||||
self.pop()?;
|
self.pop()?;
|
||||||
@@ -185,11 +231,10 @@ impl VM {
|
|||||||
let ident = chunk.constants[offset as usize].clone();
|
let ident = chunk.constants[offset as usize].clone();
|
||||||
if let Value::Obj(name) = ident {
|
if let Value::Obj(name) = ident {
|
||||||
let name = name.downcast::<ObjString>().unwrap();
|
let name = name.downcast::<ObjString>().unwrap();
|
||||||
globals.entry(name).or_insert(self.pop()?);
|
globals.entry(name).insert_entry(self.pop()?);
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}?
|
};
|
||||||
},
|
},
|
||||||
Op::GetGlobal { offset } => {
|
Op::GetGlobal { offset } => {
|
||||||
let ident = match chunk.constants[offset as usize] {
|
let ident = match chunk.constants[offset as usize] {
|
||||||
@@ -202,12 +247,11 @@ impl VM {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(
|
Err(VMError {
|
||||||
VMError::Runtime(
|
line: self.line,
|
||||||
format!("Undefined variable '{}'.", ident).into(),
|
kind: VMErrorKind::UndefinedVariable,
|
||||||
self.pc,
|
msg: Some(format!("Undefined variable '{}'.", ident)),
|
||||||
)
|
})
|
||||||
)
|
|
||||||
}?
|
}?
|
||||||
},
|
},
|
||||||
Op::SetGlobal { offset } => {
|
Op::SetGlobal { offset } => {
|
||||||
@@ -223,12 +267,11 @@ impl VM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
hash_map::Entry::Vacant(_) => {
|
hash_map::Entry::Vacant(_) => {
|
||||||
Err(
|
Err(VMError {
|
||||||
VMError::Runtime(
|
line: self.line,
|
||||||
format!("Undefined variable '{}'.", ident).into(),
|
kind: VMErrorKind::UndefinedVariable,
|
||||||
self.pc,
|
msg: Some(format!("Undefined variable '{}'.", ident)),
|
||||||
)
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}?
|
}?
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user