exceptions

This commit is contained in:
trimill 2024-02-22 14:27:35 -05:00
parent 7aca8b423c
commit b48c08cb62
Signed by: trimill
GPG Key ID: 4F77A16E17E10BCB
11 changed files with 907 additions and 354 deletions

231
Cargo.lock generated
View File

@ -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"

View File

@ -5,4 +5,5 @@ edition = "2021"
[dependencies]
talc-lang = { path = "../talc-lang" }
anyhow = "1.0"
rustyline = "13.0"
clap = "4.5"

View File

@ -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<Value>) -> anyhow::Result<Value> {
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<dyn std::error::Error>> {
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(())
}

View File

@ -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]

View File

@ -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<Expr<'s>>, Box<Expr<'s>>),
For(&'s str, Box<Expr<'s>>, Box<Expr<'s>>),
Lambda(Vec<&'s str>, Box<Expr<'s>>),
Try(Box<Expr<'s>>, Vec<CatchBlock<'s>>),
}
#[derive(Debug)]
pub struct CatchBlock<'s> {
pub name: Option<&'s str>,
pub types: Option<Vec<Symbol>>,
pub body: Expr<'s>,
}
#[derive(Debug)]

View File

@ -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<Vec<Symbol>>,
}
#[derive(Clone, Debug, Default)]
pub struct TryTable {
pub catches: Vec<Catch>,
pub local_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct Chunk {
pub consts: Vec<Value>,
pub instrs: Vec<Instruction>,
pub try_tables: Vec<TryTable>,
}
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;
}
}

View File

@ -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<Local>,
}
pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec<Local>)> {
pub fn repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
let locals = vec![Local {
name: "self".into(),
scope: 0,
@ -40,9 +39,9 @@ pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec<Local>)> {
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<BinaryOp>, lv: &LValue, a: &Expr) -> Result<()> {
fn expr_assign(&mut self, o: Option<BinaryOp>, 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(())
}
}

View File

@ -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<usize, Token<'input>, 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<Item=LexResult<'s>> {
self.builder.matcher::<parser_util::ParseError>(input)
}
}

View File

@ -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<Expr<'input>> = {
"$" => Box::new(Expr::Ident("$")),
":" "(" <e:Block> ")" => Box::new(Expr::Lambda(vec!["$"], e)),
"\\" <xs:IdentList> "->" <e:Term> => Box::new(Expr::Lambda(xs, e)),
"do" <Block> "end" => <>,
"if" <IfStmtChain> => <>,
"while" <a:Expr> "do" <b:Block> "end" => Box::new(Expr::While(a, b)),
"for" <v:Identifier> "in" <a:Expr> "do" <b:Block> "end" => Box::new(Expr::For(v, a, b)),
"while" <a:Expr> "do" <b:Block> "end"
=> Box::new(Expr::While(a, b)),
"for" <v:Identifier> "in" <a:Expr> "do" <b:Block> "end"
=> Box::new(Expr::For(v, a, b)),
"try" <b:Block> <mut ch:CatchChain> => {
ch.reverse();
Box::new(Expr::Try(b, ch))
},
Literal => Box::new(Expr::Literal(<>)),
}
@ -285,6 +296,26 @@ IfStmtChain: Box<Expr<'input>> = {
<a:Expr> "then" <b:Block> "elif" <c:IfStmtChain> => Box::new(Expr::If(a, b, Some(c))),
}
CatchChain: Vec<CatchBlock<'input>> = {
"catch" <types:SymbolList> <name:("in" <Identifier>)?> "do" <b:Block> <mut ch:CatchChain> => {
ch.push(CatchBlock { name, types: Some(types), body: *b });
ch
},
"catch" "*" <name:("in" <Identifier>)?> "do" <b:Block> <mut ch:CatchChain> => {
ch.push(CatchBlock { name, types: None, body: *b });
ch
},
"end" => Vec::new(),
}
SymbolList: Vec<Symbol> = {
<mut xs:(<SymbolLiteral> ",")*> <x:SymbolLiteral?> => {
if let Some(x) = x { xs.push(x) };
xs
},
}
ExprList: Vec<Expr<'input>> = {
<xs:(<Expr> ",")*> <x:Expr?> => {
let mut xs: Vec<_> = xs.into_iter().map(|x| *x).collect();

View File

@ -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<RefCell<Vec<Value>>>;
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Exception {
pub ty: Symbol,
pub msg: Rc<str>,
pub data: Option<Value>,
}
impl Exception {
pub fn new(ty: Symbol, msg: Rc<str>) -> Self {
Self { ty, msg, data: None }
}
pub fn from_table(table: RcTable) -> Option<Self> {
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<T> = std::result::Result<T, Exception>;
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<Function>, 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<dyn Fn(Value, Vec<Value>) -> Result<Value>>,
@ -40,7 +157,7 @@ pub struct HashValue(Value);
impl Eq for HashValue {}
impl TryFrom<Value> for HashValue {
type Error = anyhow::Error;
type Error = Exception;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
match value {
Value::Nil
@ -49,7 +166,7 @@ impl TryFrom<Value> 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<NativeFunc>),
}
impl std::fmt::Display for Value {
impl From<Symbol> for Value {
fn from(value: Symbol) -> Self { Self::Symbol(value) }
}
impl From<Symbol> 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, "<function>"),
Value::NativeFunc(_) => write!(f, "<native function>"),
Value::Function(g)
=> write!(f, "<function {:?}>", Rc::as_ptr(g)),
Value::NativeFunc(g)
=> write!(f, "<native function {:?}>", 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<Value> 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<Value> 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<Value> 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<Value> 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<Ordering> {
pub fn val_cmp(&self, other: &Self) -> Result<Ordering> {
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:#}"),
}
}
}

View File

@ -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<Value>,
func: Rc<Function>,
locals: Vec<Value>,
try_frames: Vec<TryFrame>,
ip: usize,
root: bool,
}
impl CallFrame {
fn new(func: Rc<Function>, locals: Vec<Value>) -> Self {
Self {
func,
locals,
..Self::default()
}
}
fn new_root(func: Rc<Function>) -> 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<Function>) -> Result<Value> {
fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result<Option<Value>> {
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<Function>) -> Result<Value> {
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();
},
}
}
}
}