diff --git a/examples/hello.sy b/examples/hello.sy index 2d4801e..138a148 100644 --- a/examples/hello.sy +++ b/examples/hello.sy @@ -1,4 +1,4 @@ -[ "Hell world" . ] +[ "Hell world" println! ] :say say! \ No newline at end of file diff --git a/src/compile.rs b/src/compile.rs index edfbef2..6614da0 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -82,12 +82,6 @@ impl<'s> Compile<'s> { Inst::Store(word).into() } Atom::Word(text) => { - // XXX : probably something better than this - // Check builtins - if text == "." { - return Inst::Print.into(); - } - // Look for locally defined symbols first. One can't be found, // then create a local variable. The local variable *should* be // defined already, but sometimes things happen. diff --git a/src/main.rs b/src/main.rs index 842fbec..1bae896 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::io::Read; use std::path::PathBuf; use structopt::StructOpt; use syn::parser::Parser; -use vm::machine::MachineBuilder; +use vm::{error::RuntimeSpanError, machine::MachineBuilder}; #[derive(Debug, StructOpt)] struct Opt { @@ -46,7 +46,21 @@ fn main() -> Result { .max_arena_objects(opt.max_arena_objects) .finish(); - machine.eval(stmts)?; - - Ok(()) + if let Err(e) = machine.eval(stmts) { + // print error message + print_error(&e); + Ok(()) + } else { + Ok(()) + } +} + +fn print_error(error: &RuntimeSpanError) { + match error { + RuntimeSpanError::Span(span, error) => { + println!("at {}", span); + print_error(error) + } + RuntimeSpanError::Error(error) => println!("{}", error), + } } diff --git a/src/syn/span.rs b/src/syn/span.rs index 71e0585..1517cbf 100644 --- a/src/syn/span.rs +++ b/src/syn/span.rs @@ -4,7 +4,7 @@ #![allow(dead_code)] use std::cmp::{Ord, Ordering, PartialOrd}; -use std::fmt::{self, Debug}; +use std::fmt::{self, Debug, Display}; use std::rc::Rc; #[cfg_attr(not(test), derive(PartialEq))] @@ -96,6 +96,20 @@ impl Span { } } +impl Display for Span { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if self.start.line == self.end.line { + write!(fmt, "line {} in {}", self.start.line, self.source) + } else { + write!( + fmt, + "lines {}-{} in {}", + self.start.line, self.end.line, self.source + ) + } + } +} + #[derive(Clone, PartialEq, Eq)] pub struct Spanned { span: Span, diff --git a/src/vm/error.rs b/src/vm/error.rs index 7eeae55..7f46dfa 100644 --- a/src/vm/error.rs +++ b/src/vm/error.rs @@ -1,3 +1,4 @@ +use crate::syn::span::Span; use thiserror::Error; #[derive(Error, Debug, Clone)] @@ -15,7 +16,44 @@ pub enum RuntimeError { CannotCall(String), } -pub type Result = std::result::Result; +pub type Result = std::result::Result; + +pub enum RuntimeSpanError { + Span(Span, Box), + Error(RuntimeError), +} + +/// Add a location to the implemented value. +pub trait WithLocation { + type Out; + fn with_location(self, span: Span) -> Self::Out; +} + +impl WithLocation for Result { + type Out = Result; + + fn with_location(self, span: Span) -> Self::Out { + if let Err(e) = self { + Err(RuntimeSpanError::Span(span, Box::new(e))) + } else { + self + } + } +} + +impl WithLocation for RuntimeError { + type Out = RuntimeSpanError; + + fn with_location(self, span: Span) -> Self::Out { + RuntimeSpanError::Span(span, Box::new(self.into())) + } +} + +impl From for RuntimeSpanError { + fn from(other: RuntimeError) -> Self { + RuntimeSpanError::Error(other) + } +} // TODO // Error building idea: diff --git a/src/vm/inst.rs b/src/vm/inst.rs index e04e99a..882625c 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -16,9 +16,6 @@ pub enum Inst { //Dup, /// Applies the top stack value, which should be a macro. Call, - - /// Pops and prints the top stack value. - Print, } // TODO - do we want separate function definition syntax? We would be able to diff --git a/src/vm/machine.rs b/src/vm/machine.rs index cf8cae2..3381807 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -93,7 +93,7 @@ impl Machine { pub fn stack_push(&mut self, value: Value) -> Result<()> { if let Some(max) = self.max_stack_size() { if self.stack().len() >= max { - return Err(RuntimeError::StackOverflow); + return Err(RuntimeError::StackOverflow.into()); } } self.stack_mut().push(value); @@ -103,7 +103,7 @@ impl Machine { pub fn stack_pop(&mut self) -> Result { self.stack_mut() .pop() - .ok_or_else(|| RuntimeError::StackUnderflow) + .ok_or_else(|| RuntimeError::StackUnderflow.into()) } pub fn lookup_word(&self, word: &Word) -> Option<&Value> { @@ -267,12 +267,8 @@ impl Machine { Value::BuiltinFn(builtin) => { self.call_native(builtin); } - value => return Err(RuntimeError::CannotCall(value.name().to_string())), + value => return Err(RuntimeError::CannotCall(value.name().to_string()).into()), }, - Inst::Print => { - let value = self.stack_pop()?; - println!("{}", value); - } }; // Update this frame's PC @@ -333,9 +329,22 @@ impl MachineBuilder { } else { return Err(RuntimeError::CannotCall( "if statement requires quote value".to_string(), - )); + ) + .into()); } }); + + self.register_builtin_fun("print", |machine, _| { + let value = machine.stack_pop()?; + print!("{}", value); + Ok(BuiltinExit::Return) + }); + + self.register_builtin_fun("println", |machine, _| { + let value = machine.stack_pop()?; + println!("{}", value); + Ok(BuiltinExit::Return) + }); } fn register_builtin_fun(