diff --git a/src/compiler.rs b/src/compiler.rs index 052d0b3..6d07bef 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,4 +1,5 @@ use crate::parser::Ast; +use crate::parser::StringPart; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; @@ -6,6 +7,7 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::FromPrimitive; use std::cell::OnceCell; use std::rc::Rc; +use std::string; #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] pub enum Op { @@ -53,6 +55,10 @@ pub enum Op { Decrement, Truncate, MatchDepth, + Panic, + EmptyString, + ConcatStrings, + Stringify, Call, @@ -156,6 +162,10 @@ impl std::fmt::Display for Op { Truncate => "truncate", Duplicate => "duplicate", MatchDepth => "match_depth", + Panic => "panic", + EmptyString => "empty_string", + ConcatStrings => "concat_strings", + Stringify => "stringify", Eq => "eq", Add => "add", @@ -199,7 +209,8 @@ impl Chunk { Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse | PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq - | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not => { + | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic + | EmptyString | ConcatStrings | Stringify => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -1094,10 +1105,40 @@ impl Compiler { self.emit_op(Op::JumpBack); self.emit_byte(self.len() - self.loop_idx()); } - Interpolated(..) - | Arguments(..) + Panic(msg) => { + self.visit(msg); + self.emit_op(Op::Panic); + } + Interpolated(parts) => { + self.emit_op(Op::EmptyString); + self.stack_depth += 1; + for part in parts { + let str = &part.0; + match str { + StringPart::Inline(_) => unreachable!(), + StringPart::Data(str) => { + let allocated = Value::String(Rc::new(str.clone())); + self.emit_constant(allocated); + self.emit_op(Op::ConcatStrings); + self.stack_depth -= 1; + } + StringPart::Word(word) => { + self.emit_op(Op::PushBinding); + let biter = self.bindings.iter().rev(); + for binding in biter { + if binding.name == word.as_str() { + self.emit_byte(binding.stack_pos); + break; + } + } + self.emit_op(Op::Stringify); + self.emit_op(Op::ConcatStrings); + } + } + } + } + Arguments(..) | Placeholder - | Panic(..) | Do(..) | Splat(..) | InterpolatedPattern(..) @@ -1119,7 +1160,7 @@ impl Compiler { | MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At - | Not => { + | Not | Panic | EmptyString | ConcatStrings | Stringify => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/main.rs b/src/main.rs index 833f275..770a628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,7 @@ pub fn run(src: &'static str) { let result = vm.run(); let output = match result { Ok(val) => val.show(&compiler.chunk), - Err(panic) => format!("{:?}", panic), + Err(panic) => format!("Ludus panicked! {panic}"), }; vm.print_stack(); println!("{output}"); @@ -74,7 +74,10 @@ pub fn run(src: &'static str) { pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = " -or (true, true, 42) +let x = 42 +let y = :foo +let z = \"thing\" +\"{x} {y} {z}\" "; run(src); } diff --git a/src/value.rs b/src/value.rs index fd2ee3a..caf3b6a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -51,6 +51,7 @@ impl std::fmt::Display for Value { False => write!(f, "false"), Keyword(idx) => write!(f, ":{idx}"), Interned(idx) => write!(f, "\"@{idx}\""), + String(str) => write!(f, "\"{str}\""), Number(n) => write!(f, "{n}"), Tuple(members) => write!( f, @@ -98,6 +99,10 @@ impl Value { let str_str = ctx.strings[*i]; format!("\"{str_str}\"") } + String(str) => { + let str_str = str.to_string(); + format!("\"{str_str}\"") + } Keyword(i) => { let kw_str = ctx.keywords[*i]; format!(":{kw_str}") @@ -122,13 +127,62 @@ impl Value { .join(", "); format!("#{{{members}}}") } - String(s) => s.as_ref().clone(), Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show(ctx)), Fn(lfn) => format!("fn {}", lfn.get().unwrap().name), _ => todo!(), } } + pub fn stringify(&self, ctx: &Chunk) -> String { + use Value::*; + match &self { + Nil => "nil".to_string(), + True => "true".to_string(), + False => "false".to_string(), + Number(n) => format!("{n}"), + Interned(i) => { + let str_str = ctx.strings[*i]; + str_str.to_string() + } + Keyword(i) => { + let kw_str = ctx.keywords[*i]; + format!(":{kw_str}") + } + Tuple(t) => { + let members = t + .iter() + .map(|e| e.stringify(ctx)) + .collect::>() + .join(", "); + members.to_string() + } + List(l) => { + let members = l + .iter() + .map(|e| e.stringify(ctx)) + .collect::>() + .join(", "); + members.to_string() + } + Dict(d) => { + let members = d + .iter() + .map(|(k, v)| { + let key_show = Value::Keyword(*k).show(ctx); + let value_show = v.show(ctx); + format!("{key_show} {value_show}") + }) + .collect::>() + .join(", "); + members.to_string() + } + String(s) => s.as_ref().clone(), + Box(x) => x.as_ref().borrow().stringify(ctx), + Fn(lfn) => lfn.get().unwrap().name.to_string(), + _ => todo!(), + } + } + pub fn type_of(&self) -> &'static str { use Value::*; match self { diff --git a/src/vm.rs b/src/vm.rs index 5bb0804..ca6088d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,6 +6,7 @@ use chumsky::prelude::SimpleSpan; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; use std::cell::RefCell; +use std::fmt; use std::mem::swap; use std::rc::Rc; @@ -18,7 +19,19 @@ use std::rc::Rc; // pub trace: Vec, // pub extra: String, // } -pub struct Panic(&'static str); +pub enum Panic { + Str(&'static str), + String(String), +} + +impl fmt::Display for Panic { + fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Panic::Str(msg) => write!(f, "{msg}"), + Panic::String(msg) => write!(f, "{msg}"), + } + } +} #[derive(Debug, Clone, PartialEq)] pub struct Trace { @@ -102,7 +115,11 @@ impl<'a> Vm<'a> { } pub fn panic(&mut self, msg: &'static str) { - self.result = Some(Err(Panic(msg))); + self.result = Some(Err(Panic::Str(msg))); + } + + pub fn panic_with(&mut self, msg: String) { + self.result = Some(Err(Panic::String(msg))); } pub fn interpret(&mut self) { @@ -554,6 +571,35 @@ impl<'a> Vm<'a> { self.push(negated); self.ip += 1; } + Panic => { + let msg = self.pop().show(self.chunk); + self.panic_with(msg); + } + EmptyString => { + self.push(Value::String(Rc::new("".to_string()))); + self.ip += 1; + } + ConcatStrings => { + let second = self.pop(); + let first = self.pop(); + let combined = match (first, second) { + (Value::String(first), Value::String(second)) => { + let mut newstr = first.as_ref().clone(); + newstr.push_str(second.as_str()); + Value::String(Rc::new(newstr)) + } + _ => unreachable!(), + }; + self.push(combined); + self.ip += 1; + } + Stringify => { + let to_stringify = self.pop(); + let the_string = to_stringify.stringify(self.chunk); + let stringified = Value::String(Rc::new(the_string)); + self.push(stringified); + self.ip += 1; + } Call => todo!(), } }