From 60c097518aa1a1a096faea71a31ee133eb50acd3 Mon Sep 17 00:00:00 2001 From: paladin Date: Sat, 29 Aug 2026 13:33:48 +0100 Subject: [PATCH] Fixed loop instructions, local registers in other instructions, upvalues and tables. Builds and passes test. --- .gitignore | 3 +- Cargo.lock | 25 +++++++++ Cargo.toml | 3 +- src/compiler.rs | 126 +++++++++++++++++++------------------------ src/lib.rs | 19 ++++--- src/parser.rs | 78 ++++++++++++++++++--------- src/table.rs | 10 ++-- src/tests/script.lua | 11 +++- src/vm.rs | 105 +++++++++++++++++++++++------------- 9 files changed, 229 insertions(+), 151 deletions(-) diff --git a/.gitignore b/.gitignore index ec376bb..af73913 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -target \ No newline at end of file +target +Cargo.lock \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c2dca4f..bbe798c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + [[package]] name = "glam" version = "0.33.3" @@ -12,5 +21,21 @@ checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6" name = "mars" version = "0.1.0" dependencies = [ + "colored", "glam", ] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 55a8c8a..af6a241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ messages = [] assertions = [] [dependencies] -glam = { version = "0.33.3", optional = true } \ No newline at end of file +glam = { version = "0.33.3", optional = true } +colored = { version = "3.1.1" } \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 4e0c7df..66e4c08 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -346,13 +346,13 @@ fn compile_identifier( traversal: &mut Traversal, access: Access, ) -> Result<(), LoadError> { - let length = traversal.contexts.len(); - for i in 0..length { + let contexts_len = traversal.contexts.len(); + for context_index in 0..contexts_len { // For each context - let i = length - i - 1; // From top to bottom - if i == length - 1 { + let context_index = contexts_len - context_index - 1; // From top to bottom + if context_index == contexts_len - 1 { // If this is our context - for scope in traversal.contexts[i].scopes.iter().rev() { + for scope in traversal.contexts[context_index].scopes.iter().rev() { // Go through the scopes if scope.symbol.as_ref().is_some_and(|it| it.eq(&name.it)) { // Find it @@ -366,36 +366,23 @@ fn compile_identifier( } } else { // Otherwise it's another function's 'scope'(s) - let length = traversal.contexts[i].scopes.len(); - for j in 0..length { + let scopes_len = traversal.contexts[context_index].scopes.len(); + for scope_index in 0..scopes_len { // For each scope in that foreign context - let j = length - j - 1; // From top to bottom - if traversal.contexts[i].scopes[j] + let scope_index = scopes_len - scope_index - 1; // From top to bottom + if traversal.contexts[context_index].scopes[scope_index] .symbol .as_ref() .is_some_and(|it| it.eq(&name.it)) { - // Go through... - // todo: fix this comment it's talking about something i ended up dropping - /* - So here we know this 'traversal.contexts[i]' is the function holding the upvalue - in 'scopes[j]', if it has an upvalue already made, we use its 'prototype.closing' - index and set our initial rolling variable 'reference' to Reference::Register('index'). - This is significant because this 'upvalue' MUST be made in this owning function's - prototype's closing list, but 'reference' also acts as a variable holding the upvalue - that the current function in the loop 'for k in j+1..length' produced so that the next - function context can reference it, this also means that the last 'reference' after the - loop is sort of vacuously a 'Reference::Upvalue' that is only storing our 'index' - for the eventual instruction itself. - */ - let register = traversal.contexts[i].scopes[j].index; - traversal.contexts[i].scopes[j].upvalue = true; + let register = traversal.contexts[context_index].scopes[scope_index].index; + traversal.contexts[context_index].scopes[scope_index].upvalue = true; let mut reference = Reference::Closing(register); // its important that a few conditions are held here e.g. length > 2, j < length + 1... - for k in j + 1..length { + for down_context_index in context_index + 1..contexts_len { // Ok now going from every context to the one wanting the upvalue // set reference to what we found for the next context - reference = match traversal.contexts[k] + reference = match traversal.contexts[down_context_index] .prototype .upvalues .iter() @@ -404,8 +391,14 @@ fn compile_identifier( Some(index) => Reference::Upvalue(index.try_into().unwrap()), None => { // we don't even have a reference to this in our upvalues so add it - let length = traversal.contexts[k].prototype.upvalues.len(); - traversal.contexts[k].prototype.upvalues.push(reference); + let length = traversal.contexts[down_context_index] + .prototype + .upvalues + .len(); + traversal.contexts[down_context_index] + .prototype + .upvalues + .push(reference); Reference::Upvalue(length.try_into().unwrap()) // Reference (child) to this reference to that reference (parent) for next context } } @@ -499,6 +492,8 @@ impl Compile for Piece<&Args> { impl Compile for Piece<&Call> { fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { + let index = traversal.index().map(|it| it + 1).unwrap_or(0); // there will always be at least the callable + traversal.compile(&self.it.args.inner_ref())?; // arguments traversal.compile(&self.it.prefix.as_ref().inner_ref())?; // call prefix match &self.it.method { Some(name) => { @@ -507,8 +502,6 @@ impl Compile for Piece<&Call> { } None => {} } - let index = traversal.index().unwrap(); - traversal.compile(&self.it.args.inner_ref())?; // arguments traversal.push_code(self.it.prefix.unit().end().swap(Code::Call(index))); // Our responsibility to discard here Ok(()) } @@ -720,7 +713,7 @@ fn compile_assignment( variable: Option<&Piece>, expression: &Piece<&Expression>| -> Result<(), LoadError> { - let index = traversal.index().unwrap(); + let index = traversal.index().unwrap_or(0); traversal.compile(expression)?; if let Some(variable) = variable { compile_variable(variable.inner_ref(), traversal, Access::Set)?; @@ -794,12 +787,6 @@ impl Compile for Piece<&Block> { fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { let last_scope_index = traversal.index().unwrap_or(0); let mut depth = 0; // Counter for 'dangling' scopes produced by any declarations to be closed. - let mut var_scope = |traversal: &mut Traversal, symbol: String| -> Index { - let index = traversal.temp_scope(); - traversal.last_scope().symbol = Some(symbol); - depth += 1; - index - }; // todo: for some reason, register indices are called "_index" and code indices "index_"? fix it // todo: maybe refactor this so that there are less indents for statement in self.it.statements.iter() { @@ -928,27 +915,16 @@ impl Compile for Piece<&Block> { } } Statement::Range(var, init, limit, step, block) => { - let init_index = traversal.temp_scope(); // init traversal.compile(&init.inner_ref())?; - traversal.push_code(init.swap(Code::RegisterSet(init_index))); - - let limit_index = traversal.temp_scope(); // limit traversal.compile(&limit.inner_ref())?; - traversal.push_code(limit.swap(Code::RegisterSet(limit_index))); - - let step_index = traversal.temp_scope(); // step; match step { Some(expression) => traversal.compile(&expression.inner_ref())?, None => traversal.push_literal(Value::Integer(1), limit.unit().end()), }; - let piece = match step { - Some(expression) => expression.unit().start(), - None => limit.unit().end(), - }; - traversal.push_code(piece.swap(Code::RegisterSet(step_index))); let index_loop = traversal.push_code(block.unit().start().swap(Code::RangeBegin)) + 1; - var_scope(traversal, var.it.0.clone()); + traversal.temp_scope(); + traversal.last_scope().symbol = Some(var.it.0.clone()); traversal.compile(&block.inner_ref())?; traversal.close_scope(); // temporary for v let index_jump = traversal.push_code(block.unit().start().swap(Code::UNSURE)); // Dummy NOP @@ -957,9 +933,7 @@ impl Compile for Piece<&Block> { Code::RangeLoop(jump_from(index_jump, index_loop)?), ); // Reinsert into above resolve_break(traversal, index_loop, index_jump + 1)?; - traversal.close_scope(); - traversal.close_scope(); - traversal.close_scope(); // init, limit, step + traversal.consume_scope(3); // init, limit, step } Statement::Iterator(names, expressions, block) => { let mut iterator_variables = Vec::new(); @@ -1035,10 +1009,9 @@ impl Compile for Piece<&Block> { } } Statement::LocalFunction(name, function) => { - var_scope(traversal, name.it.0.clone()); - let index = traversal.index().unwrap(); traversal.compile(&self.swap(function))?; - traversal.push_code(name.swap(Code::RegisterSet(index)).end()); + traversal.last_scope().symbol = Some(name.it.0.clone()); + depth += 1; } Statement::Declaration(names, expressions) => { let mut expressions_iter = expressions.iter().peekable(); @@ -1068,6 +1041,7 @@ impl Compile for Piece<&Block> { traversal.last_context().scopes.iter_mut().rev(); for name in names.iter().rev() { scopes.next().unwrap().symbol = Some(name.it.0.clone()); + depth += 1; } } Expression::Prefix(ref prefix) => { @@ -1091,28 +1065,38 @@ impl Compile for Piece<&Block> { for name in names.iter().rev() { scopes.next().unwrap().symbol = Some(name.it.0.clone()); + depth += 1; } } - _ => compile_declaration( - traversal, - expression, - names_iter.next().unwrap(), - )?, + _ => { + compile_declaration( + traversal, + expression, + names_iter.next().unwrap(), + )?; + depth += 1; + } } } - _ => compile_declaration( - traversal, - expression, - names_iter.next().unwrap(), - )?, + _ => { + compile_declaration( + traversal, + expression, + names_iter.next().unwrap(), + )?; + depth += 1; + } } break; } - (Some(expression), false) => compile_declaration( - traversal, - expression, - names_iter.next().unwrap(), - )?, // expression is not last, just use it + (Some(expression), false) => { + compile_declaration( + traversal, + expression, + names_iter.next().unwrap(), + )?; + depth += 1; + } // expression is not last, just use it (None, ..) => { // no more expressions let index = traversal.index().unwrap_or(0) as usize; diff --git a/src/lib.rs b/src/lib.rs index a8eeace..84db274 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,29 +1,28 @@ #![warn(clippy::cast_lossless)] -mod parser; mod compiler; -mod vm; -mod table; mod gc; +mod parser; +mod table; +mod vm; #[cfg(test)] mod tests { - use crate::vm::{Callable, Machine}; - use super::*; + use crate::vm::Machine; #[test] fn simple() { let mut machine = match Machine::new() { - Err(error) => panic!("{:?}",error), + Err(error) => panic!("{}", error), Ok(machine) => machine, }; let closure = match machine.load(include_str!("tests/script.lua")) { - Err(error) => panic!("{:?}",error), + Err(error) => panic!("{}", error), Ok(closure) => closure, - _ => panic!() + _ => panic!(), }; - println!("{:?}",closure.borrow().prototype); + println!("{:?}", closure.borrow().prototype); match machine.enter(closure) { - Err(error) => panic!("{:?}",error), + Err(error) => panic!("{}", error), _ => {} }; } diff --git a/src/parser.rs b/src/parser.rs index f5fc0f7..eef2a63 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,9 @@ // PARSER use crate::vm::{Index, RunError}; +use colored::*; +use std::cmp::{max, min}; use std::fmt::Formatter; -use std::ops::Add; // TODO: Vec> is good but sometimes we either need at least one or none, current fix is just to check if there is one and if not skip it, this takes extra performance #[derive(Clone, Copy)] @@ -23,27 +24,43 @@ impl LoadError { LoadError(message.to_string(), piece.unit()) } pub(crate) fn msg(&self, reader: &str) -> String { - let mut roll = format!("Error: {} {}", self.1.index, self.0); - let mut begin = self.1.index; - let mut end = self.1.index + self.1.width; - let mut count = 0; - for line in reader.lines() { - // todo: improve this - count += 1; - if line.len() >= begin { - begin = 0; - roll = roll.add(format!("\n{}\t", count).as_str()); - roll = roll.add(line); - } else { - begin -= line.len(); - } - if line.len() > end { - break; - } else { - end -= line.len() + fn put(it: &mut String, low: usize, high: usize, begin: usize, line: &String) { + let bottom = min(max(low, begin), begin + line.len()); + let top = min(max(high, begin), begin + line.len()); + let part = &line[bottom - begin..top - begin]; + if part.len() != 0 { + it.push_str(part); } } - roll + let begin = self.1.index; + let end = self.1.index + self.1.width; + let mut line_index = 1; + let mut line_num = 0; + let mut col_num = 0; + let mut index; + let mut before = String::new(); + let mut inner = String::new(); + let mut after = String::new(); + for line in reader.lines() { + index = line.as_ptr().addr() - reader.as_ptr().addr(); + if index < begin { + line_num += 1; + } + if (index..index + line.len()).contains(&begin) { + col_num = begin - index; + } + let line = format!("{}\t{}\n", line_index, line); + if begin <= index + line.len() && end >= index { + put(&mut before, 0, begin, index, &line); + put(&mut inner, begin, end, index, &line); + put(&mut after, end, reader.len(), index, &line); + } + line_index += 1; + } + format!( + "Error: {}:{} \"{}\"\n{}{}{}", + line_num, col_num, self.0, before, inner, after + ) } fn from_runtime(piece: Piece<()>, error: RunError) -> LoadError { LoadError(error.0, piece) @@ -913,9 +930,16 @@ impl Parse for Args { reader.trim()?; let start = reader.marker(); let result = if reader.consume_white("(").is_ok() { - let expressions = reader.take::>>()?; + let mut expressions = Vec::new(); + reader.trim()?; + if let Ok(first) = reader.take::() { + expressions.push(first); + while reader.consume_white(",").is_ok() { + expressions.push(reader.take::()?); + } + }; reader.consume_white(")")?; - Args(expressions.it) + Args(expressions) } else if let Ok(table) = reader.take::() { Args(vec![table.map(|it| Expression::Table(it))]) } else if let Ok(string) = reader.take::() { @@ -1054,9 +1078,15 @@ impl Parse for Statement { )) } else if reader.white_consume("for").is_ok() { let names = reader.take::>>()?.it; - let result = if names.len() < 1 { + let before = reader.marker(); + let result = if reader.consume_white("=").is_ok() { + if names.len() != 1 { + Err(LoadError::from_str( + "Expected exactly one name for range loop", + &before, + ))?; + } let name = names.first().unwrap(); - reader.consume_white("=")?; let begin = reader.take::()?; reader.consume_white(",")?; let finish = reader.take::()?; diff --git a/src/table.rs b/src/table.rs index 8eaad6c..64902d0 100644 --- a/src/table.rs +++ b/src/table.rs @@ -104,8 +104,6 @@ pub struct Map { } impl Map { - pub(crate) fn string_cache() { // todo: this isn't a clean way, maybe make a generic Map and use a specific one for the cache and the rest of the language - } fn exchange_table(&mut self, len: usize) { let old = std::mem::replace( &mut self.map, @@ -118,8 +116,9 @@ impl Map { len ], ); + self.map_bounds = self.table_lower()..self.table_upper(); for Entry { entry, .. } in old { - if entry.is_vacant() { + if !entry.is_vacant() { self.set_table(entry) } } @@ -142,7 +141,6 @@ impl Map { // too much free space self.exchange_table(self.map.len() / 2) } - self.map_bounds = self.table_lower()..self.table_upper() } pub(crate) fn set_table(&mut self, entry: E) { #[cfg(feature = "assertions")] @@ -234,7 +232,7 @@ impl Map { if self.map[home].displacement != Displacement::MAX { let mut largest = 0; for i in 1..self.map[home].displacement { - let neighbour = &mut self.map[(home + i as usize) % range]; + let neighbour = &mut self.map[(home + i as usize) % range]; // todo: maybe mod slows this down if neighbour.entry.matches(&index) { neighbour.entry = E::new_vacant(); self.map_count -= 1; @@ -288,7 +286,7 @@ impl Map { Some(&home.entry) } else { for i in 1..home.displacement as usize { - let neighbour = &self.map[location + i]; + let neighbour = &self.map[(location + i) % self.map.len()]; if neighbour.entry.matches(&index) { return Some(&neighbour.entry); } diff --git a/src/tests/script.lua b/src/tests/script.lua index 22ed711..a854da9 100644 --- a/src/tests/script.lua +++ b/src/tests/script.lua @@ -1,2 +1,9 @@ -local table = { "hello", "friend" } -print(table[1], table[2]) \ No newline at end of file +local var = 2 +local function thing() + print(var) + var = 3 +end + +print(var) +thing() +thing() diff --git a/src/vm.rs b/src/vm.rs index 6d4acd3..f2067a0 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -3,12 +3,13 @@ use crate::parser::{BinaryOp, Piece, UnaryOp, parse}; use crate::table::{Equivalent, Hashed, Hashes, Map, Table, TableEntry}; use std::any::Any; -use std::fmt::Formatter; +use std::fmt::{Display, Formatter}; use std::hash::{DefaultHasher, Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::rc::Rc; // Rust function to call +// todo: make needles more private pub struct Native(fn(&mut Machine, &mut Needle, usize) -> Result<(), RunError>); // A native function in Rust #[derive(Clone, Debug, Copy, PartialEq)] @@ -138,6 +139,12 @@ impl std::fmt::Debug for Callable { #[derive(Debug)] pub struct RunError(pub(crate) String); +impl Display for RunError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + pub trait Object: std::fmt::Debug { fn meta_string(&mut self) -> Result { Ok(Value::Nil) @@ -638,11 +645,11 @@ impl Machine { -> Result { frame.counter += 1; // return to this frame at the next instruction let offset = frame.offset; - match needle.stack[offset + index as usize].value.clone() { + match needle.pop() { Value::Function(callable) => { match callable { Callable::Rust(function) => { - function.0(machine, needle, offset + index as usize + 1)?; + function.0(machine, needle, offset + index as usize)?; Ok(frame) // nothing changes } Callable::Mars(closure) => { @@ -653,8 +660,8 @@ impl Machine { .resize(offset + index as usize + nargs as usize, Register::NIL); Ok(Frame { // where to find the function, what about missing arguments?, - vararg: offset + index as usize + 1, // start of frame points to the register after the function - offset: offset + index as usize + 1 + nargs as usize, // points to first argument (or last vararg if nargs = zero) + vararg: offset + index as usize, // start of frame points to the register after the function + offset: offset + index as usize + nargs as usize, // points to first argument (or last vararg if nargs = zero) counter: 0, closure, }) @@ -668,9 +675,9 @@ impl Machine { } } }; - let mut jump = |frame: &mut Frame, offset: Offset| { + let jump = |frame: &mut Frame, offset: Offset| { if offset < 0 { - frame.counter -= -(offset) as usize + frame.counter -= -(offset - 1) as usize } else { frame.counter += offset as usize } @@ -685,7 +692,7 @@ impl Machine { .prototype .code .get(frame.counter) - .cloned(); + .cloned(); // todo: this is probably slow for every instruction let code = match code { Some(code) => code, None => { @@ -763,7 +770,7 @@ impl Machine { Code::Discard(index) => { needle .stack - .resize(frame.offset + index as usize, Register::NIL); + .resize(frame.offset + index as usize + 1, Register::NIL); } Code::VarArg(count) => { if count == 0 { @@ -784,7 +791,6 @@ impl Machine { #[cfg(feature = "assertions")] assert_ne!(offset, 0); jump(&mut frame, offset); - continue; // Skip the extra offset++ } Code::Skip => { if needle.pop().bool() { @@ -862,9 +868,9 @@ impl Machine { } Code::RangeBegin => { let len = needle.stack.len(); - let init = len - 2; - let limit = len - 1; - let step = len; + let init = len - 3; + let limit = len - 2; + let step = len - 1; let init_value = self.try_number(&mut needle.stack[init].value)?; let limit_value = self.try_number(&mut needle.stack[limit].value)?; let step_value = self.try_number(&mut needle.stack[step].value)?; @@ -877,12 +883,8 @@ impl Machine { _ => panic!(), }; // All integers so we can carry this out nicely - needle.stack[init].value = init_value.clone(); - // https://www.lua.org/source/5.5/lvm.c.html#floatforloop - // but vmcase(OP_FORLOOP) actually preempts the number of iterations for integers! - needle.stack[limit].value = Value::Integer( - (get(limit_value) - get(init_value)) / get(step_value.clone()), - ); // todo: is this actually correct? + needle.stack[init].value = init_value; + needle.stack[limit].value = limit_value; // todo: is this actually correct? needle.stack[step].value = step_value; } else { // Make them all as floats @@ -898,10 +900,11 @@ impl Machine { needle.push(needle.stack[init].value.clone()); } Code::RangeLoop(offset) => { + needle.pop(); // get rid of temp let len = needle.stack.len(); - let init_index = len - 2; - let limit_index = len - 1; - let step_index = len; + let init_index = len - 3; + let limit_index = len - 2; + let step_index = len - 1; let init_value = self.try_number(&mut needle.stack[init_index].value)?; let limit_value = self.try_number(&mut needle.stack[limit_index].value)?; let step_value = self.try_number(&mut needle.stack[step_index].value)?; @@ -915,12 +918,14 @@ impl Machine { Value::Integer(it) => it, _ => panic!(), }; - if limit == 0 { + if init < limit { + needle.stack[init_index].value = Value::Integer(init + step); + needle.push(Value::Integer(init + step)); // owned variable for loop block jump(&mut frame, offset); } else { - needle.stack[init_index].value = Value::Integer(init + step); - needle.stack[limit_index].value = Value::Integer(limit - 1); - needle.push(Value::Integer(init)); + needle.pop(); + needle.pop(); + needle.pop(); } } Value::Number(init) => { @@ -933,10 +938,13 @@ impl Machine { _ => panic!(), }; if (step >= 0.0 && init > limit) || (step < 0.0 && init < limit) { + needle.stack[init_index].value = Value::Number(init + step); + needle.push(Value::Number(init)); // owned variable for loop block jump(&mut frame, offset); } else { - needle.stack[init_index].value = Value::Number(init + step); - needle.push(Value::Number(init)); + needle.pop(); + needle.pop(); + needle.pop(); } } _ => panic!(), @@ -972,15 +980,21 @@ impl Machine { pub(crate) fn enter(&mut self, closure: Gc) -> Result<(), RunError> { let mut needle = Needle { frames: vec![Frame { - vararg: 1, // Both 1 since there is a single NIL on the stack - offset: 1, + vararg: 0, + offset: 0, counter: 0, closure, }], - stack: vec![Register::NIL], // When a function gets called, it expects to be able to place its return values over the value in the stack too + stack: vec![], }; self.dispatch(&mut needle) } + pub fn register_global(&mut self, name: &str, native: Native) -> Result<(), RunError> { + let name = self.new_string(name)?; + self.global + .borrow_mut() + .set(name, Value::Function(Callable::Rust(Rc::from(native)))) + } pub fn new() -> Result { let mut allocator = Allocator::new(); let mut machine = Machine { @@ -988,10 +1002,9 @@ impl Machine { allocator, cache: Map::new(), }; - let string = machine.new_string("print").unwrap(); - machine.global.borrow_mut().set( - string, - Value::Function(Callable::Rust(Rc::from(Native( + machine.register_global( + "print", + Native( |machine: &mut Machine, needle: &mut Needle, index: usize| @@ -1002,7 +1015,22 @@ impl Machine { println!(); Ok(()) }, - )))), + ), + )?; + machine.register_global( + "debug", + Native( + |machine: &mut Machine, + needle: &mut Needle, + index: usize| + -> Result<(), RunError> { + for i in index..needle.stack.len() { + print!("{:?} ", machine.try_string(&mut needle.pop())?.unwrap_str()) + } + println!(); + Ok(()) + }, + ), )?; Ok(machine) } @@ -1010,3 +1038,8 @@ impl Machine { #[derive(Debug, Clone)] pub struct MarsError(String); +impl Display for MarsError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +}