diff --git a/Cargo.lock b/Cargo.lock index 28234b7..3d0ac9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,10 +12,52 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.80" +name = "anstream" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] [[package]] name = "ascii-canvas" @@ -65,6 +107,48 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "clipboard-win" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297" +dependencies = [ + "error-code", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "crunchy" version = "0.2.2" @@ -113,12 +197,45 @@ dependencies = [ "log", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "error-code" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" + +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -148,6 +265,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + [[package]] name = "indexmap" version = "2.2.3" @@ -226,6 +352,12 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "lock_api" version = "0.4.11" @@ -254,6 +386,26 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + [[package]] name = "num-complex" version = "0.4.5" @@ -364,6 +516,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -419,12 +581,47 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rustyline" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -456,6 +653,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "syn" version = "2.0.50" @@ -471,7 +674,8 @@ dependencies = [ name = "talc-bin" version = "0.1.0" dependencies = [ - "anyhow", + "clap", + "rustyline", "talc-lang", ] @@ -479,7 +683,6 @@ dependencies = [ name = "talc-lang" version = "0.1.0" dependencies = [ - "anyhow", "lalrpop", "lalrpop-util", "num-complex", @@ -537,12 +740,30 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/talc-bin/Cargo.toml b/talc-bin/Cargo.toml index d395aea..a85f938 100644 --- a/talc-bin/Cargo.toml +++ b/talc-bin/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] talc-lang = { path = "../talc-lang" } -anyhow = "1.0" +rustyline = "13.0" +clap = "4.5" diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs index 50fa494..0d01072 100644 --- a/talc-bin/src/main.rs +++ b/talc-bin/src/main.rs @@ -1,23 +1,9 @@ -use talc_lang::{Parser, Vm, Symbol, value::{Value, NativeFunc}, compiler::repl}; -use std::{io::Write, rc::Rc, cell::RefCell}; +use rustyline::error::ReadlineError; +use talc_lang::{Parser, Vm, Symbol, value::{Value, Function}, compiler::repl}; +use std::{rc::Rc, io::Write}; -fn do_thing() -> impl Fn(Value, Vec) -> anyhow::Result { - let x = RefCell::new(0); - move |_, _| { - let v = *x.borrow(); - if v > 5 { - Ok(Value::Nil) - } else { - *x.borrow_mut() += 1; - Ok(Value::Int(v)) - } - } -} - - -fn main() { +fn main() -> Result<(), Box> { let parser = Parser::new(); - let mut lines = std::io::stdin().lines().map_while(Result::ok); let mut vm = Vm::new(256); let mut globals = Vec::new(); @@ -29,36 +15,44 @@ fn main() { vm.set_global(prev2_sym, Value::Nil); vm.set_global(prev3_sym, Value::Nil); - vm.set_global(Symbol::get("test"), Value::NativeFunc(NativeFunc { - arity: 0, - f: Box::new(do_thing()), - }.into())); + let mut rl = rustyline::DefaultEditor::new()?; loop { - print!(">> "); - std::io::stdout().flush().expect("could not flush stdout"); - let line = lines.next().expect("could not get next line"); + let line = rl.readline(">> "); + let line = match line { + Ok(line) => line, + Err(ReadlineError::Eof) => break, + Err(ReadlineError::Interrupted) => continue, + Err(e) => { + eprintln!("Error: {e}"); + continue + }, + }; let ex = match parser.parse(&line) { Ok(ex) => ex, - Err(e) => { println!("Error: {e}"); continue }, + Err(e) => { eprintln!("Error: {e}"); continue }, }; - let func = match repl(&ex, &globals) { - Ok((f, g)) => { globals = g; f }, - Err(e) => { println!("Error: {e}"); continue }, - }; + let (f, g) = repl(&ex, &globals); + globals = g; + let func = Rc::new(f); - match vm.run(Rc::new(func)) { + Function::disasm_recursive(func.clone(), &mut std::io::stdout())?; + std::io::stdout().flush()?; + + match vm.run(func) { Ok(v) => { vm.set_global(prev3_sym, vm.get_global(prev2_sym).unwrap().clone()); vm.set_global(prev2_sym, vm.get_global(prev1_sym).unwrap().clone()); vm.set_global(prev1_sym, v.clone()); if v != Value::Nil { - println!("{v}"); + println!("{v:#}"); } } - Err(e) => println!("Error: {e}"), + Err(e) => eprintln!("{e}"), } } + + Ok(()) } diff --git a/talc-lang/Cargo.toml b/talc-lang/Cargo.toml index c12de10..07144db 100644 --- a/talc-lang/Cargo.toml +++ b/talc-lang/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" lalrpop-util = { version = "0.20", features = ["lexer", "unicode"] } num-complex = "0.4" num-rational = { version = "0.4", default-features = false, features = [] } -anyhow = "1.0" thiserror = "1.0" [build-dependencies] diff --git a/talc-lang/src/ast.rs b/talc-lang/src/ast.rs index 6bfdb5b..3fcc2a8 100644 --- a/talc-lang/src/ast.rs +++ b/talc-lang/src/ast.rs @@ -1,4 +1,4 @@ -use crate::value::Value; +use crate::{value::Value, Symbol}; #[derive(Clone, Copy, Debug)] pub enum BinaryOp { @@ -39,6 +39,14 @@ pub enum Expr<'s> { While(Box>, Box>), For(&'s str, Box>, Box>), Lambda(Vec<&'s str>, Box>), + Try(Box>, Vec>), +} + +#[derive(Debug)] +pub struct CatchBlock<'s> { + pub name: Option<&'s str>, + pub types: Option>, + pub body: Expr<'s>, } #[derive(Debug)] diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs index 3f370fe..d16581f 100644 --- a/talc-lang/src/chunk.rs +++ b/talc-lang/src/chunk.rs @@ -119,56 +119,72 @@ pub enum Instruction { IterBegin, IterTest(Arg24), + BeginTry(Arg24), EndTry, + Call(u8), Return, } impl std::fmt::Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Instruction as I; match *self { - I::Nop => write!(f, "nop"), - I::LoadLocal(a) => write!(f, "load {}", usize::from(a)), - I::StoreLocal(a) => write!(f, "store {}", usize::from(a)), - I::NewLocal => write!(f, "newlocal"), - I::DropLocal(n) => write!(f, "discardlocal {}", usize::from(n)), - I::LoadGlobal(s) => write!(f, "loadglobal {}", + Self::Nop => write!(f, "nop"), + Self::LoadLocal(a) => write!(f, "load {}", usize::from(a)), + Self::StoreLocal(a) => write!(f, "store {}", usize::from(a)), + Self::NewLocal => write!(f, "newlocal"), + Self::DropLocal(n) => write!(f, "discardlocal {}", usize::from(n)), + Self::LoadGlobal(s) => write!(f, "loadglobal {}", Symbol::try_from(s).expect("symbol does not exist").name()), - I::StoreGlobal(s) => write!(f, "storeglobal {}", + Self::StoreGlobal(s) => write!(f, "storeglobal {}", Symbol::try_from(s).expect("symbol does not exist").name()), - I::Const(c) => write!(f, "const {}", usize::from(c)), - I::Int(i) => write!(f, "int {}", i64::from(i)), - I::Symbol(s) => write!(f, "symbol {}", + Self::Const(c) => write!(f, "const {}", usize::from(c)), + Self::Int(i) => write!(f, "int {}", i64::from(i)), + Self::Symbol(s) => write!(f, "symbol {}", Symbol::try_from(s).expect("symbol does not exist").name()), - I::Bool(b) => write!(f, "bool {b}"), - I::Nil => write!(f, "nil"), - I::Dup => write!(f, "dup"), - I::DupTwo => write!(f, "duptwo"), - I::Drop(n) => write!(f, "discard {}", usize::from(n)), - I::Swap => write!(f, "swap"), - I::UnaryOp(o) => write!(f, "unary {o:?}"), - I::BinaryOp(o) => write!(f, "binary {o:?}"), - I::NewList(n) => write!(f, "newlist {n}"), - I::GrowList(n) => write!(f, "growlist {n}"), - I::NewTable(n) => write!(f, "newtable {n}"), - I::GrowTable(n) => write!(f, "growtable {n}"), - I::Index => write!(f, "index"), - I::StoreIndex => write!(f, "storeindex"), - I::Jump(a) => write!(f, "jump {}", usize::from(a)), - I::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)), - I::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)), - I::IterBegin => write!(f, "iterbegin"), - I::IterTest(a) => write!(f, "itertest {}", usize::from(a)), - I::Call(n) => write!(f, "call {n}"), - I::Return => write!(f, "return"), + Self::Bool(b) => write!(f, "bool {b}"), + Self::Nil => write!(f, "nil"), + Self::Dup => write!(f, "dup"), + Self::DupTwo => write!(f, "duptwo"), + Self::Drop(n) => write!(f, "discard {}", usize::from(n)), + Self::Swap => write!(f, "swap"), + Self::UnaryOp(o) => write!(f, "unary {o:?}"), + Self::BinaryOp(o) => write!(f, "binary {o:?}"), + Self::NewList(n) => write!(f, "newlist {n}"), + Self::GrowList(n) => write!(f, "growlist {n}"), + Self::NewTable(n) => write!(f, "newtable {n}"), + Self::GrowTable(n) => write!(f, "growtable {n}"), + Self::Index => write!(f, "index"), + Self::StoreIndex => write!(f, "storeindex"), + Self::Jump(a) => write!(f, "jump {}", usize::from(a)), + Self::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)), + Self::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)), + Self::IterBegin => write!(f, "iterbegin"), + Self::IterTest(a) => write!(f, "itertest {}", usize::from(a)), + Self::BeginTry(t) => write!(f, "begintry {}", usize::from(t)), + Self::EndTry => write!(f, "endtry"), + Self::Call(n) => write!(f, "call {n}"), + Self::Return => write!(f, "return"), } } } +#[derive(Clone, Debug)] +pub struct Catch { + pub addr: usize, + pub types: Option>, +} + +#[derive(Clone, Debug, Default)] +pub struct TryTable { + pub catches: Vec, + pub local_count: usize, +} + #[derive(Clone, Debug, Default)] pub struct Chunk { pub consts: Vec, pub instrs: Vec, + pub try_tables: Vec, } impl Chunk { @@ -187,19 +203,19 @@ impl Chunk { self.instrs.push(i); self.instrs.len() - 1 } -} -impl std::fmt::Display for Chunk { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "constants")?; - for (i, c) in self.consts.iter().enumerate() { - writeln!(f, " {i:04}: {c}")?; - } - writeln!(f, "instructions")?; - for (i, n) in self.instrs.iter().enumerate() { - writeln!(f, " {i:04}: {n}")?; - } - Ok(()) + pub fn begin_try_table(&mut self, local_count: usize) -> (usize, TryTable) { + assert!(self.try_tables.len() < 0xff_ffff, "too many catch tables in a chunk"); + let table = TryTable { + catches: Vec::new(), + local_count, + }; + self.try_tables.push(table.clone()); + (self.try_tables.len() - 1, table) + } + + pub fn finish_catch_table(&mut self, idx: usize, table: TryTable) { + self.try_tables[idx] = table; } } diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index 69b8c9a..e127812 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -1,11 +1,10 @@ use std::rc::Rc; -use crate::ast::{BinaryOp, Expr, LValue}; -use crate::chunk::{Instruction as I, Chunk, Arg24}; +use crate::ast::{BinaryOp, Expr, LValue, CatchBlock}; +use crate::chunk::{Instruction as I, Chunk, Arg24, Catch}; use crate::symbol::Symbol; use crate::value::Function; use crate::value::Value; -use anyhow::Result; #[derive(Debug, Clone)] pub struct Local { @@ -27,7 +26,7 @@ struct Compiler<'a> { globals: Vec, } -pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec)> { +pub fn repl(expr: &Expr, globals: &[Local]) -> (Function, Vec) { let locals = vec![Local { name: "self".into(), scope: 0, @@ -40,9 +39,9 @@ pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec)> { locals, globals: globals.to_vec(), }; - comp.expr(expr)?; + comp.expr(expr); comp.emit(I::Return); - Ok((comp.func, comp.globals)) + (comp.func, comp.globals) } impl<'a> Compiler<'a> { @@ -259,45 +258,48 @@ impl<'a> Compiler<'a> { // Expressions // - fn expr(&mut self, e: &Expr) -> Result<()> { + fn expr(&mut self, e: &Expr) { match e { Expr::Block(xs) if xs.is_empty() => { self.emit(I::Nil); }, Expr::Block(xs) => { self.begin_scope(); for x in &xs[0..xs.len()-1] { - self.expr(x)?; + self.expr(x); self.emit_discard(1); } - self.expr(&xs[xs.len()-1])?; + self.expr(&xs[xs.len()-1]); self.end_scope(); }, Expr::Literal(v) => self.expr_literal(v), Expr::Ident(ident) => self.load_var(ident), Expr::UnaryOp(o, a) => { - self.expr(a)?; + self.expr(a); self.emit(I::UnaryOp(*o)); }, Expr::BinaryOp(o, a, b) => { - self.expr(a)?; - self.expr(b)?; + self.expr(a); + self.expr(b); self.emit(I::BinaryOp(*o)); }, - Expr::Assign(o, lv, a) => self.expr_assign(*o, lv, a)?, + Expr::Assign(o, lv, a) => self.expr_assign(*o, lv, a), Expr::AssignVar(name, a) => { - self.expr(a)?; + self.expr(a); self.emit(I::Dup); self.declare_local(name); }, Expr::AssignGlobal(name, a) => { - self.expr(a)?; + self.expr(a); self.emit(I::Dup); self.store_global(name); }, + Expr::List(xs) if xs.is_empty() => { + self.emit(I::NewList(0)); + }, Expr::List(xs) => { let mut first = true; for chunk in xs.chunks(16) { for e in chunk { - self.expr(e)?; + self.expr(e); } if first { self.emit(I::NewList(chunk.len() as u8)); @@ -307,12 +309,15 @@ impl<'a> Compiler<'a> { } } }, + Expr::Table(xs) if xs.is_empty() => { + self.emit(I::NewTable(0)); + }, Expr::Table(xs) => { let mut first = true; for chunk in xs.chunks(8) { for (k, v) in chunk { - self.expr(k)?; - self.expr(v)?; + self.expr(k); + self.expr(v); } if first { self.emit(I::NewTable(chunk.len() as u8)); @@ -323,47 +328,48 @@ impl<'a> Compiler<'a> { } }, Expr::Index(ct, idx) => { - self.expr(ct)?; - self.expr(idx)?; + self.expr(ct); + self.expr(idx); self.emit(I::Index); }, Expr::FnCall(f, args) => { - self.expr(f)?; + self.expr(f); for a in args { - self.expr(a)?; + self.expr(a); } self.emit(I::Call(args.len() as u8)); }, Expr::Pipe(a, f) => { - self.expr(a)?; - self.expr(f)?; + self.expr(a); + self.expr(f); self.emit(I::Swap); self.emit(I::Call(1)); }, + Expr::Lambda(args, body) => self.expr_lambda(args, body), Expr::And(a, b) => { - self.expr(a)?; + self.expr(a); self.emit(I::Dup); let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); self.emit_discard(1); - self.expr(b)?; + self.expr(b); self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); }, Expr::Or(a, b) => { - self.expr(a)?; + self.expr(a); self.emit(I::Dup); let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0))); self.emit_discard(1); - self.expr(b)?; + self.expr(b); self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip()))); }, Expr::If(cond, b1, b2) => { - self.expr(cond)?; + self.expr(cond); let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); - self.expr(b1)?; + self.expr(b1); let j2 = self.emit(I::Jump(Arg24::from_usize(0))); self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); if let Some(b2) = b2 { - self.expr(b2)?; + self.expr(b2); } else { self.emit(I::Nil); } @@ -373,63 +379,102 @@ impl<'a> Compiler<'a> { }, Expr::While(cond, body) => { let start = self.ip(); - self.expr(cond)?; + self.expr(cond); let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); - self.expr(body)?; + self.expr(body); self.emit_discard(1); self.emit(I::Jump(Arg24::from_usize(start))); self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); self.emit(I::Nil); }, - Expr::For(name, iter, body) => { - // load iterable and convert to iterator - self.expr(iter)?; - self.emit(I::IterBegin); - - // declare loop variable - self.begin_scope(); - self.emit(I::Nil); - let local = self.declare_local(name); - - // begin loop - let start = self.ip(); - - // call iterator and jump if nil, otherwise store - self.emit(I::Dup); - self.emit(I::Call(0)); - let j1 = self.emit(I::IterTest(Arg24::from_usize(0))); - self.store_local(local); - - // body - self.expr(body)?; - self.emit_discard(1); - - // end loop - self.emit(I::Jump(Arg24::from_usize(start))); - - self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip()))); - self.end_scope(); - self.emit(I::Nil); - }, - Expr::Lambda(args, body) => self.expr_lambda(args, body)?, + Expr::For(name, iter, body) => self.expr_for(name, iter, body), + Expr::Try(body, catches) => self.expr_try(body, catches), } - Ok(()) } - fn expr_lambda(&mut self, args: &[&str], body: &Expr) -> Result<()> { + fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) { + let (idx, mut table) = self.func.chunk.begin_try_table(self.locals.len()); + + self.emit(I::BeginTry(Arg24::from_usize(idx))); + self.expr(body); + self.emit(I::EndTry); + let body_end_addr = self.emit(I::Jump(Arg24::from_usize(0))); + let mut catch_end_addrs = Vec::new(); + + for catch_block in catch_blocks { + table.catches.push(Catch { + addr: self.ip(), + types: catch_block.types.clone(), + }); + + self.begin_scope(); + + if let Some(name) = catch_block.name { + self.declare_local(name); + } else { + self.emit_discard(1); + } + + self.expr(&catch_block.body); + self.end_scope(); + + let end_addr = self.emit(I::Jump(Arg24::from_usize(0))); + catch_end_addrs.push(end_addr); + } + + let ip = Arg24::from_usize(self.ip()); + self.update_instr(body_end_addr, I::Jump(ip)); + for addr in catch_end_addrs { + self.update_instr(addr, I::Jump(ip)); + } + + self.func.chunk.finish_catch_table(idx, table); + } + + fn expr_for(&mut self, name: &str, iter: &Expr, body: &Expr) { + // load iterable and convert to iterator + self.expr(iter); + self.emit(I::IterBegin); + + // declare loop variable + self.begin_scope(); + self.emit(I::Nil); + let local = self.declare_local(name); + + // begin loop + let start = self.ip(); + + // call iterator and jump if nil, otherwise store + self.emit(I::Dup); + self.emit(I::Call(0)); + let j1 = self.emit(I::IterTest(Arg24::from_usize(0))); + self.store_local(local); + + // body + self.expr(body); + self.emit_discard(1); + + // end loop + self.emit(I::Jump(Arg24::from_usize(start))); + + self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip()))); + self.end_scope(); + self.emit(I::Nil); + } + + fn expr_lambda(&mut self, args: &[&str], body: &Expr) { let func = Function { arity: args.len(), chunk: Chunk::new() }; let mut inner = self.new_function(func, args); inner.parent = Some(self); - inner.expr(body)?; + inner.expr(body); let func = inner.finish(); let n = self.add_const(Value::Function(Rc::new(func))); self.emit(I::Const(Arg24::from_usize(n))); - Ok(()) } fn expr_literal(&mut self, val: &Value) { @@ -449,37 +494,36 @@ impl<'a> Compiler<'a> { } } - fn expr_assign(&mut self, o: Option, lv: &LValue, a: &Expr) -> Result<()> { + fn expr_assign(&mut self, o: Option, lv: &LValue, a: &Expr) { match (lv, o) { (LValue::Ident(i), None) => { - self.expr(a)?; + self.expr(a); self.emit(I::Dup); self.store_default(i); }, (LValue::Ident(i), Some(o)) => { self.load_var(i); - self.expr(a)?; + self.expr(a); self.emit(I::BinaryOp(o)); self.emit(I::Dup); self.store_default(i); }, (LValue::Index(ct, i), None) => { - self.expr(ct)?; - self.expr(i)?; - self.expr(a)?; + self.expr(ct); + self.expr(i); + self.expr(a); self.emit(I::StoreIndex); }, (LValue::Index(ct, i), Some(o)) => { - self.expr(ct)?; - self.expr(i)?; + self.expr(ct); + self.expr(i); self.emit(I::DupTwo); self.emit(I::Index); - self.expr(a)?; + self.expr(a); self.emit(I::BinaryOp(o)); self.emit(I::StoreIndex); }, } - Ok(()) } } diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs index 4528984..54d9efc 100644 --- a/talc-lang/src/lib.rs +++ b/talc-lang/src/lib.rs @@ -6,6 +6,7 @@ #[allow(clippy::just_underscores_and_digits)] #[allow(clippy::pedantic)] mod parser { + pub use __intern_token::new_builder as new_builder; include!(concat!(env!("OUT_DIR"),"/parser.rs")); } mod parser_util; @@ -21,3 +22,28 @@ pub mod compiler; pub use parser::BlockParser as Parser; pub use vm::Vm; pub use symbol::Symbol; + + +type LexResult<'input> = Result< + (usize, Token<'input>, usize), + ParseError, parser_util::ParseError> +>; + +use lalrpop_util::{ParseError, lexer::{Token, MatcherBuilder}}; + +pub struct Lexer { + builder: MatcherBuilder, +} + +impl Default for Lexer { + fn default() -> Self { Self::new() } +} + +impl Lexer { + pub fn new() -> Self { + Self { builder: crate::parser::new_builder() } + } + pub fn lex<'s>(&'s self, input: &'s str) -> impl Iterator> { + self.builder.matcher::(input) + } +} diff --git a/talc-lang/src/parser.lalrpop b/talc-lang/src/parser.lalrpop index 7d4f102..4c50924 100644 --- a/talc-lang/src/parser.lalrpop +++ b/talc-lang/src/parser.lalrpop @@ -1,6 +1,6 @@ use std::rc::Rc; use crate::ast::*; -use crate::value::*; +use crate::value::Value; use crate::symbol::Symbol; use crate::parser_util::*; use num_complex::Complex64; @@ -9,6 +9,7 @@ grammar; extern { type Error = ParseError; + } match { @@ -45,6 +46,8 @@ match { "in", "continue", "break", + "try", + "catch", } else { // identifiers r"[a-zA-Z_][a-zA-Z0-9_]*" => TokIdentifier, @@ -56,7 +59,7 @@ match { r"0s[0-5][0-5]*" => TokSexInteger, r"0b[01][01_]*" => TokBinInteger, - r"\d[0-9_]*([eE][-+]?[0-9_]*\d[0-9_]*i?|i)|(\d[0-9_]*)?\.[0-9_]+([eE]_*[-+]?[0-9_]*\d[0-9_]*)?i?" => TokFloat, + r"[0-9][0-9_]*([eE][-+]?[0-9_]*[0-9][0-9_]*i?|i)|([0-9][0-9_]*)?\.[0-9_]+([eE]_*[-+]?[0-9_]*[0-9][0-9_]*)?i?" => TokFloat, r#""([^\\"]|\\.)*""# => TokStringDouble, r#"'[^']*'"# => TokStringSingle, @@ -272,10 +275,18 @@ TermNotIdent: Box> = { "$" => Box::new(Expr::Ident("$")), ":" "(" ")" => Box::new(Expr::Lambda(vec!["$"], e)), "\\" "->" => Box::new(Expr::Lambda(xs, e)), + "do" "end" => <>, "if" => <>, - "while" "do" "end" => Box::new(Expr::While(a, b)), - "for" "in" "do" "end" => Box::new(Expr::For(v, a, b)), + "while" "do" "end" + => Box::new(Expr::While(a, b)), + "for" "in" "do" "end" + => Box::new(Expr::For(v, a, b)), + "try" => { + ch.reverse(); + Box::new(Expr::Try(b, ch)) + }, + Literal => Box::new(Expr::Literal(<>)), } @@ -285,6 +296,26 @@ IfStmtChain: Box> = { "then" "elif" => Box::new(Expr::If(a, b, Some(c))), } +CatchChain: Vec> = { + "catch" )?> "do" => { + + ch.push(CatchBlock { name, types: Some(types), body: *b }); + ch + }, + "catch" "*" )?> "do" => { + ch.push(CatchBlock { name, types: None, body: *b }); + ch + }, + "end" => Vec::new(), +} + +SymbolList: Vec = { + ",")*> => { + if let Some(x) = x { xs.push(x) }; + xs + }, +} + ExprList: Vec> = { ",")*> => { let mut xs: Vec<_> = xs.into_iter().map(|x| *x).collect(); diff --git a/talc-lang/src/value.rs b/talc-lang/src/value.rs index 0aa5540..1feb263 100644 --- a/talc-lang/src/value.rs +++ b/talc-lang/src/value.rs @@ -1,13 +1,12 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::fmt::Display; use std::ops::{Add, Sub, Mul, Div, Neg}; use std::cmp::Ordering; use std::rc::Rc; use std::hash::Hash; -use anyhow::bail; use num_rational::Rational64; use num_complex::Complex64; -use anyhow::{anyhow, Result}; use crate::chunk::Chunk; use crate::symbol::Symbol; @@ -15,12 +14,130 @@ use crate::symbol::Symbol; type RcList = Rc>>; type RcTable = Rc>>; -#[derive(Debug)] +#[derive(Clone, Debug)] +pub struct Exception { + pub ty: Symbol, + pub msg: Rc, + pub data: Option, +} + +impl Exception { + pub fn new(ty: Symbol, msg: Rc) -> Self { + Self { ty, msg, data: None } + } + + pub fn from_table(table: RcTable) -> Option { + let table = table.borrow(); + let ty = table.get(&Symbol::get("type").into())?; + let msg = table.get(&Symbol::get("msg").into())?; + if let (Value::Symbol(ty), Value::String(msg)) = (ty, msg) { + let data = table.get(&Symbol::get("data").into()); + Some(Self { + ty: *ty, + msg: msg.clone(), + data: data.cloned(), + }) + } else { + None + } + } + + pub fn to_table(self) -> RcTable { + let mut table = HashMap::new(); + table.insert(Symbol::get("type").into(), self.ty.into()); + table.insert(Symbol::get("msg").into(), Value::String(self.msg)); + if let Some(data) = self.data { + table.insert(Symbol::get("data").into(), data); + } + Rc::new(RefCell::new(table)) + } +} + +impl Display for Exception { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.ty.name(), self.msg) + } +} + +pub type Result = std::result::Result; + +macro_rules! exception { + ($exc_ty:ident, $fstr:literal, $($arg:expr),*) => {{ + $crate::value::Exception::new( + $crate::Symbol::get(stringify!($exc_ty)), + format!($fstr, $($arg),*).into() + ) + }}; + ($exc_ty:ident, $fstr:literal) => {{ + $crate::value::exception!($exc_ty, $fstr,) + }}; +} + +pub(crate) use exception; + +macro_rules! throw { + ($($args:tt)*) => { + return Err($crate::value::exception!($($args)*)) + }; +} + +pub(crate) use throw; + + +#[derive(Debug, Default)] pub struct Function { pub arity: usize, pub chunk: Chunk, } +impl Function { + pub fn disasm_recursive(f: Rc, w: &mut impl std::io::Write) -> std::io::Result<()> { + writeln!(w, "{} (argc={})", Value::Function(f.clone()), f.arity)?; + if !f.chunk.consts.is_empty() { + writeln!(w, "constants")?; + for (i, c) in f.chunk.consts.iter().enumerate() { + writeln!(w, " {i:04}: {c}")?; + } + } + if !f.chunk.try_tables.is_empty() { + writeln!(w, "catch tables")?; + for (i, n) in f.chunk.try_tables.iter().enumerate() { + write!(w, " {i:04}: ")?; + if n.catches.is_empty() { + writeln!(w)?; + } + for (i, catch) in n.catches.iter().enumerate() { + if i != 0 { + write!(w, " : ")?; + } + if let Some(types) = &catch.types { + write!(w, "{:04} [", catch.addr)?; + for (i, ty) in types.iter().enumerate() { + if i != 0 { write!(w, ", ")?; } + write!(w, "{}", ty.name())?; + } + writeln!(w, "]")?; + } else { + writeln!(w, "{:04} *", catch.addr)?; + } + } + } + } + writeln!(w, "instructions")?; + for (i, n) in f.chunk.instrs.iter().enumerate() { + writeln!(w, " {i:04}: {n}")?; + } + writeln!(w)?; + + for c in &f.chunk.consts { + if let Value::Function(f) = c { + Function::disasm_recursive(f.clone(), w)?; + } + } + Ok(()) + } +} + pub struct NativeFunc { pub arity: usize, pub f: Box) -> Result>, @@ -40,7 +157,7 @@ pub struct HashValue(Value); impl Eq for HashValue {} impl TryFrom for HashValue { - type Error = anyhow::Error; + type Error = Exception; fn try_from(value: Value) -> std::result::Result { match value { Value::Nil @@ -49,7 +166,7 @@ impl TryFrom for HashValue { | Value::Int(_) | Value::Ratio(_) | Value::String(_) => Ok(Self(value)), - _ => bail!("value {} cannot be hashed", value), + _ => throw!(hash, "value {value:#} cannot be hashed"), } } } @@ -134,7 +251,14 @@ pub enum Value { NativeFunc(Rc), } -impl std::fmt::Display for Value { +impl From for Value { + fn from(value: Symbol) -> Self { Self::Symbol(value) } +} +impl From for HashValue { + fn from(value: Symbol) -> Self { Self(value.into()) } +} + +impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Value::Nil => write!(f, "nil"), @@ -150,14 +274,24 @@ impl std::fmt::Display for Value { Value::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()), Value::Complex(z) => write!(f, "{z}"), - Value::String(s) => write!(f, "{s}"), + Value::String(s) => { + if f.alternate() { + write!(f, "{s:?}") + } else { + write!(f, "{s}") + } + }, Value::List(l) => { write!(f, "[")?; for (i, item) in l.borrow().iter().enumerate() { if i != 0 { write!(f, ", ")?; } - write!(f, "{item}")?; + if f.alternate() { + write!(f, "{item:#}")?; + } else { + write!(f, "{item}")?; + } } write!(f, "]") }, @@ -167,12 +301,18 @@ impl std::fmt::Display for Value { if i != 0 { write!(f, ", ")?; } - write!(f, "({}) = {v}", k.0)?; + if f.alternate() { + write!(f, "({:#}) = {v:#}", k.0)?; + } else { + write!(f, "({}) = {v}", k.0)?; + } } write!(f, " }}") }, - Value::Function(_) => write!(f, ""), - Value::NativeFunc(_) => write!(f, ""), + Value::Function(g) + => write!(f, "", Rc::as_ptr(g)), + Value::NativeFunc(g) + => write!(f, "", Rc::as_ptr(g)), } } } @@ -231,7 +371,7 @@ impl Neg for Value { V::Ratio(x) => Ok(V::Ratio(-x)), V::Float(x) => Ok(V::Float(-x)), V::Complex(x) => Ok(V::Complex(-x)), - a => Err(anyhow!("cannot negate {a}")) + a => throw!(type_error, "cannot negate {a:#}") } } } @@ -245,7 +385,7 @@ impl Add for Value { (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x + y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x + y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x + y)), - (l, r) => Err(anyhow!("cannot add {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot add {l:#} and {r:#}") } } } @@ -259,7 +399,7 @@ impl Sub for Value { (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x - y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x - y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x - y)), - (l, r) => Err(anyhow!("cannot subtract {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot subtract {l:#} and {r:#}") } } } @@ -273,7 +413,7 @@ impl Mul for Value { (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x * y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x * y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x * y)), - (l, r) => Err(anyhow!("cannot multiply {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot multiply {l:#} and {r:#}") } } } @@ -287,7 +427,7 @@ impl Div for Value { (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x / y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x / y)), - (l, r) => Err(anyhow!("cannot divide {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot divide {l:#} and {r:#}") } } } @@ -323,7 +463,7 @@ impl Value { (V::Ratio(_x), V::Ratio(_y)) => todo!("ratio modulo"), (V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(y))), (V::Complex(_x), V::Complex(_y)) => todo!("complex modulo"), - (l, r) => Err(anyhow!("cannot modulo {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot modulo {l:#} and {r:#}") } } @@ -334,7 +474,7 @@ impl Value { (V::Ratio(_x), V::Ratio(_y)) => todo!("ratio integer division"), (V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(y))), (V::Complex(_x), V::Complex(_y)) => todo!("complex integer division"), - (l, r) => Err(anyhow!("cannot integer divide {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot integer divide {l:#} and {r:#}") } } @@ -352,7 +492,7 @@ impl Value { => Ok(V::Float(ratio_to_f64(x).powf(ratio_to_f64(y)))), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x.powc(y))), - (l, r) => Err(anyhow!("cannot exponentiate {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot exponentiate {l:#} and {r:#}") } } } @@ -422,10 +562,10 @@ impl PartialOrd for Value { } impl Value { - pub fn val_cmp(&self, other: &Self) -> anyhow::Result { + pub fn val_cmp(&self, other: &Self) -> Result { match self.partial_cmp(other) { Some(o) => Ok(o), - None => Err(anyhow!("cannot compare {self:?} and {other:?}")), + None => throw!(type_error, "cannot compare {self:#} and {other:#}"), } } @@ -439,7 +579,7 @@ impl Value { if i >= 0 && (i as usize) < l.len() { Ok(l[i as usize].clone()) } else { - Err(anyhow!("index {i} out of bounds for list of length {}", l.len())) + throw!(index_error, "index {i} out of bounds for list of length {}", l.len()) } }, (V::Range(r), V::Int(i)) => { @@ -450,7 +590,7 @@ impl Value { ) { Ok(Value::Int(r.start + i)) } else { - Err(anyhow!("index {i} out of bounds for range {}", self)) + throw!(index_error, "index {i} out of bounds for range {self}") } }, (V::Table(t), i) => { @@ -458,7 +598,7 @@ impl Value { let i = i.try_into()?; Ok(t.get(&i).cloned().unwrap_or(Value::Nil)) }, - (lhs, rhs) => Err(anyhow!("cannot assign to index {lhs} with {rhs}")) + (col, idx) => throw!(type_error, "cannot index {col:#} with {idx:#}") } } @@ -471,7 +611,7 @@ impl Value { l[i as usize] = val; Ok(()) } else { - Err(anyhow!("index {i} out of bounds for list of length {}", l.len())) + throw!(index_error, "index {i} out of bounds for list of length {}", l.len()) } }, (V::Table(t), i) => { @@ -480,7 +620,7 @@ impl Value { t.insert(i, val); Ok(()) }, - (l, r) => Err(anyhow!("cannot index {l:?} with {r:?}")) + (col, idx) => throw!(index_error, "cannot index {col:#} with {idx:#}") } } @@ -497,7 +637,7 @@ impl Value { s.push_str(s2); Ok(V::String(s.into())) } - (l, r) => Err(anyhow!("cannot concatenate {l:?} and {r:?}")) + (l, r) => throw!(type_error, "cannot concatenate {l:#} and {r:#}"), } } @@ -506,7 +646,7 @@ impl Value { let ty = if closed { RangeType::Closed } else { RangeType::Open }; Ok(Value::Range(Range { start: *start, stop: *stop, ty })) } else { - bail!("cannot create range between {self} and {other}") + throw!(type_error, "cannot create range between {self:#} and {other:#}") } } @@ -514,7 +654,7 @@ impl Value { if let Value::Int(start) = self { Ok(Value::Range(Range { start: *start, stop: 0, ty: RangeType::Endless })) } else { - bail!("cannot create endless range from {self}") + throw!(type_error, "cannot create endless range from {self:#}") } } @@ -566,7 +706,7 @@ impl Value { f: Box::new(f), }))) }, - _ => bail!("cannot iterate {self}"), + _ => throw!(type_error, "cannot iterate {self:#}"), } } } diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index ac0db27..8e55b42 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -1,13 +1,36 @@ use std::{rc::Rc, cmp::Ordering, cell::RefCell, collections::HashMap}; -use crate::{chunk::Instruction, value::{Value, Function}, ast::{BinaryOp, UnaryOp}, symbol::Symbol}; +use crate::{chunk::Instruction, value::{Value, Function, Result, throw, Exception}, ast::{BinaryOp, UnaryOp}, symbol::Symbol}; -use anyhow::{Result, anyhow, bail}; +struct TryFrame { idx: usize, stack_len: usize } +#[derive(Default)] struct CallFrame { - locals: Vec, func: Rc, + locals: Vec, + try_frames: Vec, ip: usize, + root: bool, +} + + +impl CallFrame { + fn new(func: Rc, locals: Vec) -> Self { + Self { + func, + locals, + ..Self::default() + } + } + + fn new_root(func: Rc) -> Self { + Self { + func: func.clone(), + locals: vec![Value::Function(func)], + root: true, + ..Self::default() + } + } } pub struct Vm { @@ -87,169 +110,219 @@ impl Vm { println!("({:04x}) {:04}: {instr}", framecode, frame.ip); } - pub fn run(&mut self, func: Rc) -> Result { + fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result> { use Instruction as I; + match instr { + I::Nop => (), + I::LoadLocal(n) + => self.push(frame.locals[usize::from(n)].clone()), + I::StoreLocal(n) + => frame.locals[usize::from(n)] = self.pop(), + I::NewLocal + => frame.locals.push(self.pop()), + I::DropLocal(n) + => frame.locals.truncate(frame.locals.len() - usize::from(n)), + I::LoadGlobal(s) => { + let sym = unsafe { s.to_symbol_unchecked() }; + let v = match self.globals.get(&sym) { + Some(v) => v.clone(), + None => throw!(name_error, "undefined global {}", sym.name()), + }; + self.push(v); + }, + I::StoreGlobal(s) => { + let sym = unsafe { s.to_symbol_unchecked() }; + let v = self.pop(); + self.globals.insert(sym, v); + }, + I::Const(n) + => self.push(frame.func.chunk.consts[usize::from(n)].clone()), + I::Nil => self.push(Value::Nil), + I::Bool(b) => self.push(Value::Bool(b)), + I::Symbol(n) => { + let sym = unsafe { Symbol::from_id_unchecked(u32::from(n)) }; + self.push(Value::Symbol(sym)); + }, + I::Int(n) => self.push(Value::Int(i64::from(n))), + I::Dup => self.push(self.stack[self.stack.len() - 1].clone()), + I::DupTwo => { + self.push(self.stack[self.stack.len() - 2].clone()); + self.push(self.stack[self.stack.len() - 2].clone()); + }, + I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); }, + I::Swap => { + let len = self.stack.len(); + self.stack.swap(len - 1, len - 2); + }, + I::BinaryOp(op) => { + let b = self.pop(); + let a = self.pop(); + self.push(binary_op(op, a, b)?); + }, + I::UnaryOp(op) => { + let a = self.pop(); + self.push(unary_op(op, a)?); + }, + I::NewList(n) => { + let list = self.pop_n(n as usize); + self.push(Value::List(Rc::new(RefCell::new(list)))); + }, + I::GrowList(n) => { + let ext = self.pop_n(n as usize); + let list = self.pop(); + let Value::List(list) = list else { panic!("not a list") }; + list.borrow_mut().extend(ext); + self.push(Value::List(list)); + }, + I::NewTable(n) => { + let mut table = HashMap::new(); + for _ in 0..n { + let v = self.pop(); + let k = self.pop(); + table.insert(k.try_into()?, v); + } + self.push(Value::Table(Rc::new(RefCell::new(table)))); + }, + I::GrowTable(n) => { + let mut ext = self.pop_n(2 * n as usize); + let table = self.pop(); + let Value::Table(table) = table else { panic!("not a table") }; + let mut table_ref = table.borrow_mut(); + for _ in 0..n { + // can't panic: pop_n checked that ext would have len 2*n + let v = ext.pop().unwrap(); + let k = ext.pop().unwrap(); + table_ref.insert(k.try_into()?, v); + } + drop(table_ref); + self.push(Value::Table(table)); + }, + I::Index => { + let idx = self.pop(); + let ct = self.pop(); + self.push(ct.index(idx)?); + }, + I::StoreIndex => { + let v = self.pop(); + let idx = self.pop(); + let ct = self.pop(); + ct.store_index(idx, v.clone())?; + self.push(v); + }, + I::Jump(n) => frame.ip = usize::from(n), + I::JumpTrue(n) => if self.pop().truthy() { frame.ip = usize::from(n) }, + I::JumpFalse(n) => if !self.pop().truthy() { frame.ip = usize::from(n) }, + I::IterBegin => { + let iter = self.pop().to_iter_function()?; + self.push(iter); + }, + I::IterTest(n) => { + let v = &self.stack[self.stack.len() - 1]; + if v == &Value::Nil { + self.pop(); + self.pop(); + frame.ip = usize::from(n); + } + }, + I::BeginTry(t) => { + let tryframe = TryFrame { + idx: usize::from(t), + stack_len: self.stack.len() + }; + frame.try_frames.push(tryframe); + }, + I::EndTry => { + frame.try_frames.pop().expect("no try to pop"); + }, + I::Call(n) => { + let n = usize::from(n); + let func = &self.stack[self.stack.len() - n - 1]; + if let Value::NativeFunc(nf) = func { + let nf = nf.clone(); + if nf.arity != n { + throw!(type_error, "function call with wrong argument count"); + } + let args = self.pop_n(n); + let func = self.pop(); + + self.call_stack.push(std::mem::take(frame)); + let res = (nf.f)(func, args)?; + *frame = self.call_stack.pop().expect("no frame left on stack"); + + self.stack.push(res); + } else if let Value::Function(func) = func { + if func.arity != n { + throw!(type_error, "function call with wrong argument count"); + } + + if self.call_stack.len() + 1 >= self.stack_max { + throw!(call_stack_overflow, "call stack overflow") + } + self.call_stack.push(std::mem::take(frame)); + + let func = func.clone(); + let args = self.pop_n(n + 1); + *frame = CallFrame::new(func, args); + } else { + throw!(type_error, "attempt to call non-function {func}"); + } + }, + I::Return if frame.root => { + let v = self.pop(); + self.stack.clear(); + return Ok(Some(v)); + }, + I::Return => { + *frame = self.call_stack.pop().expect("no root frame"); + }, + } + + Ok(None) + } + + fn handle_exception(&mut self, frame: &mut CallFrame, exc: Exception) -> Result<()> { + loop { + while let Some(try_frame) = frame.try_frames.pop() { + let table = &frame.func.chunk.try_tables[try_frame.idx]; + for catch in &table.catches { + if catch.types.is_none() || catch.types.as_ref().unwrap().contains(&exc.ty) { + frame.ip = catch.addr; + frame.locals.truncate(table.local_count); + self.stack.truncate(try_frame.stack_len); + self.stack.push(Value::Table(exc.to_table())); + return Ok(()) + } + } + } + if frame.root { + return Err(exc) + } + *frame = self.call_stack.pop().expect("no root frame"); + } + } + + pub fn run(&mut self, func: Rc) -> Result { assert!(func.arity == 0, "root function must not take arguments"); - let mut frame = CallFrame { - func: func.clone(), - locals: Vec::with_capacity(16), - ip: 0, - }; - frame.locals.push(Value::Function(func)); + let init_stack_len = self.stack.len(); + let mut frame = CallFrame::new_root(func.clone()); loop { let instr = frame.func.chunk.instrs[frame.ip]; self.debug_instr(&frame, instr); frame.ip += 1; - match instr { - I::Nop => (), - I::LoadLocal(n) - => self.push(frame.locals[usize::from(n)].clone()), - I::StoreLocal(n) - => frame.locals[usize::from(n)] = self.pop(), - I::NewLocal - => frame.locals.push(self.pop()), - I::DropLocal(n) - => frame.locals.truncate(frame.locals.len() - usize::from(n)), - I::LoadGlobal(s) => { - let sym = unsafe { s.to_symbol_unchecked() }; - let v = self.globals.get(&sym) - .ok_or_else(|| anyhow!("global not defined"))?.clone(); - self.push(v); - }, - I::StoreGlobal(s) => { - let sym = unsafe { s.to_symbol_unchecked() }; - let v = self.pop(); - self.globals.insert(sym, v); - }, - I::Const(n) - => self.push(frame.func.chunk.consts[usize::from(n)].clone()), - I::Nil => self.push(Value::Nil), - I::Bool(b) => self.push(Value::Bool(b)), - I::Symbol(n) => { - let sym = unsafe { Symbol::from_id_unchecked(u32::from(n)) }; - self.push(Value::Symbol(sym)); - }, - I::Int(n) => self.push(Value::Int(i64::from(n))), - I::Dup => self.push(self.stack[self.stack.len() - 1].clone()), - I::DupTwo => { - self.push(self.stack[self.stack.len() - 2].clone()); - self.push(self.stack[self.stack.len() - 2].clone()); - }, - I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); }, - I::Swap => { - let len = self.stack.len(); - self.stack.swap(len - 1, len - 2); - }, - I::BinaryOp(op) => { - let b = self.pop(); - let a = self.pop(); - self.push(binary_op(op, a, b)?); - }, - I::UnaryOp(op) => { - let a = self.pop(); - self.push(unary_op(op, a)?); - }, - I::NewList(n) => { - let list = self.pop_n(n as usize); - self.push(Value::List(Rc::new(RefCell::new(list)))); - }, - I::GrowList(n) => { - let ext = self.pop_n(n as usize); - let list = self.pop(); - let Value::List(list) = list else { panic!("not a list") }; - list.borrow_mut().extend(ext); - self.push(Value::List(list)); - }, - I::NewTable(n) => { - let mut table = HashMap::new(); - for _ in 0..n { - let v = self.pop(); - let k = self.pop().try_into()?; - table.insert(k, v); + match self.run_instr(&mut frame, instr) { + Ok(None) => (), + Ok(Some(v)) => { + self.stack.truncate(init_stack_len); + return Ok(v) + } + Err(e) => { + if let Err(e) = self.handle_exception(&mut frame, e) { + self.stack.truncate(init_stack_len); + return Err(e) } - self.push(Value::Table(Rc::new(RefCell::new(table)))); - }, - I::GrowTable(n) => { - let mut ext = self.pop_n(2 * n as usize); - let table = self.pop(); - let Value::Table(table) = table else { panic!("not a table") }; - for _ in 0..n { - // can't panic: - let v = ext.pop().unwrap(); - let k = ext.pop().unwrap().try_into()?; - table.borrow_mut().insert(k, v); - } - self.push(Value::Table(table)); - }, - I::Index => { - let idx = self.pop(); - let ct = self.pop(); - self.push(ct.index(idx)?); - }, - I::StoreIndex => { - let v = self.pop(); - let idx = self.pop(); - let ct = self.pop(); - ct.store_index(idx, v.clone())?; - self.push(v); - }, - I::Jump(n) => frame.ip = usize::from(n), - I::JumpTrue(n) => if self.pop().truthy() { frame.ip = usize::from(n) }, - I::JumpFalse(n) => if !self.pop().truthy() { frame.ip = usize::from(n) }, - I::IterBegin => { - let v = self.pop().to_iter_function()?; - self.push(v); - }, - I::IterTest(n) => { - let v = &self.stack[self.stack.len() - 1]; - if v == &Value::Nil { - self.pop(); - self.pop(); - frame.ip = usize::from(n); - } - }, - I::Call(n) => { - let n = usize::from(n); - let func = &self.stack[self.stack.len() - n - 1]; - if let Value::NativeFunc(nf) = func { - let nf = nf.clone(); - let args = self.pop_n(n); - let func = self.pop(); - if nf.arity != n { - bail!("function call with wrong argument count"); - } - let res = (nf.f)(func, args)?; - self.stack.push(res); - } else if let Value::Function(func) = func { - if func.arity != n { - bail!("function call with wrong argument count"); - } - if self.call_stack.len() + 1 >= self.stack_max { - bail!("call stack overflow") - } - self.call_stack.push(frame); - let func = func.clone(); - let args = self.pop_n(n + 1); - frame = CallFrame { - func, - ip: 0, - locals: args, - }; - } else { - bail!("attempt to call non-function {}", func); - } - }, - I::Return if self.call_stack.is_empty() => { - let v = self.pop(); - self.stack.clear(); - return Ok(v); - }, - I::Return => { - // can't panic: checked that call_stack wasn't empty - frame = self.call_stack.pop().unwrap(); - }, + } } } }