From a82a5fa1c6e3b1e81b00c81ec9c7176cf443c1f1 Mon Sep 17 00:00:00 2001 From: trimill Date: Tue, 12 Nov 2024 15:40:51 -0500 Subject: [PATCH] talc 0.2.1 --- Cargo.lock | 21 +- Cargo.toml | 2 + examples/closures.talc | 23 -- examples/closures2.talc | 23 -- examples/totient.talc | 9 - talc-bin/Cargo.toml | 3 +- talc-bin/src/main.rs | 95 +++++-- talc-bin/src/repl.rs | 140 ++++++--- talc-lang/Cargo.toml | 2 +- talc-lang/src/chunk.rs | 50 ++-- talc-lang/src/compiler.rs | 486 +++++++++++++++++++++----------- talc-lang/src/exception.rs | 42 +-- talc-lang/src/lib.rs | 5 + talc-lang/src/lstring.rs | 16 ++ talc-lang/src/optimize.rs | 229 +++++++++++++++ talc-lang/src/parser/ast.rs | 65 ++++- talc-lang/src/parser/lexer.rs | 12 +- talc-lang/src/parser/parser.rs | 50 +++- talc-lang/src/serial/mod.rs | 216 ++++++++++++++ talc-lang/src/symbol.rs | 1 - talc-lang/src/value/function.rs | 2 +- talc-lang/src/vm.rs | 59 +++- talc-lang/tests/parser.rs | 76 +++++ talc-lang/tests/vm.rs | 130 +++++++++ talc-macros/Cargo.toml | 2 +- talc-std/Cargo.toml | 2 +- talc-std/src/exception.rs | 10 +- talc-std/src/file.rs | 2 +- talc-std/src/io.rs | 68 +++++ talc-std/src/value.rs | 7 +- 30 files changed, 1444 insertions(+), 404 deletions(-) delete mode 100755 examples/closures.talc delete mode 100755 examples/closures2.talc delete mode 100755 examples/totient.talc create mode 100644 talc-lang/src/optimize.rs create mode 100644 talc-lang/src/serial/mod.rs create mode 100644 talc-lang/tests/parser.rs create mode 100644 talc-lang/tests/vm.rs diff --git a/Cargo.lock b/Cargo.lock index 33f018a..637c51f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,9 +177,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "linux-raw-sys" @@ -350,9 +350,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -367,9 +367,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags", "errno", @@ -419,10 +419,11 @@ dependencies = [ [[package]] name = "talc-bin" -version = "0.2.0" +version = "0.2.1" dependencies = [ "clap", "ctrlc", + "lazy_static", "rustyline", "talc-lang", "talc-std", @@ -430,7 +431,7 @@ dependencies = [ [[package]] name = "talc-lang" -version = "0.2.0" +version = "0.2.1" dependencies = [ "lazy_static", "num-complex", @@ -441,7 +442,7 @@ dependencies = [ [[package]] name = "talc-macros" -version = "0.2.0" +version = "0.2.1" dependencies = [ "quote", "syn", @@ -449,7 +450,7 @@ dependencies = [ [[package]] name = "talc-std" -version = "0.2.0" +version = "0.2.1" dependencies = [ "lazy_static", "rand", diff --git a/Cargo.toml b/Cargo.toml index dbc4fa2..01d4c64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"] resolver = "2" +[profile.release] + [profile.release-opt] inherits = "release" lto = "fat" diff --git a/examples/closures.talc b/examples/closures.talc deleted file mode 100755 index 9a9d53e..0000000 --- a/examples/closures.talc +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env talc - --- adapted from Crafting Interpreters 10.6 - -make_counter = \-> do - var i = 0 - \-> do - i += 1 - println(i) - end -end - -var counter1 = make_counter() -counter1() -- 1 -counter1() -- 2 -counter1() -- 3 -counter1() -- 4 -var counter2 = make_counter() -counter2() -- 1 -counter2() -- 2 -counter1() -- 5 -counter1() -- 6 -counter2() -- 3 diff --git a/examples/closures2.talc b/examples/closures2.talc deleted file mode 100755 index 96a2494..0000000 --- a/examples/closures2.talc +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env talc - -var i = 0; - -var outer = \n -> do - var inner = \-> do - i = i + n - end -end - -var by3 = outer(3) -var by5 = outer(5) - -by3() -println(i) -- 3 -by3() -println(i) -- 6 -by5() -println(i) -- 11 -by5() -println(i) -- 16 -by3() -println(i) -- 19 diff --git a/examples/totient.talc b/examples/totient.talc deleted file mode 100755 index f076cab..0000000 --- a/examples/totient.talc +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env talc - -totient = \n -> do - count(factors(n)) | pairs | map(\v -> do - v[0]^v[1] - v[0]^(v[1] - 1) - end) | prod -end - -println(totient(6615)) -- 3024 diff --git a/talc-bin/Cargo.toml b/talc-bin/Cargo.toml index b06f586..e2377aa 100644 --- a/talc-bin/Cargo.toml +++ b/talc-bin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "talc-bin" -version = "0.2.0" +version = "0.2.1" edition = "2021" [[bin]] @@ -13,3 +13,4 @@ talc-std = { path = "../talc-std" } rustyline = "14.0" clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false } ctrlc = "3.4" +lazy_static = "1.5" diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs index b76114d..53e0b1f 100644 --- a/talc-bin/src/main.rs +++ b/talc-bin/src/main.rs @@ -1,7 +1,12 @@ use clap::{ColorChoice, Parser}; use std::{path::PathBuf, process::ExitCode, rc::Rc}; use talc_lang::{ - compiler::compile, parser, symbol::Symbol, value::function::disasm_recursive, Vm, + compiler::compile, + lstring::LString, + optimize, parser, serial, + symbol::Symbol, + value::{function::disasm_recursive, Value}, + Vm, }; mod helper; @@ -10,31 +15,40 @@ mod repl; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { - /// file to run - file: Option, - - /// start the repl + /// Start the repl #[arg(short, long)] repl: bool, - /// show disassembled bytecode + /// Show disassembled bytecode #[arg(short, long)] disasm: bool, - /// show disassembled bytecode + /// Show generated AST + #[arg(short = 'a', long)] + show_ast: bool, + + /// Compile to a bytecode file + #[arg(short, long)] + compile: Option, + + /// Set history file #[arg(short = 'H', long)] histfile: Option, /// enable or disable color #[arg(short, long, default_value = "auto")] color: ColorChoice, + + // file to run and arguments to pass + #[arg()] + args: Vec, } fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode { - let mut vm = Vm::new(256); + let mut vm = Vm::new(256, args.args.clone()); talc_std::load_all(&mut vm); - let ex = match parser::parse(src) { + let mut ex = match parser::parse(src) { Ok(ex) => ex, Err(e) => { eprintln!("Error: {e}"); @@ -42,7 +56,19 @@ fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode { } }; - let func = Rc::new(compile(&ex, Some(name))); + optimize(&mut ex); + + if args.show_ast { + eprintln!("{}", ex); + } + + let func = match compile(&ex, Some(name)) { + Ok(f) => Rc::new(f), + Err(e) => { + eprintln!("Error: {e}"); + return ExitCode::FAILURE + } + }; if args.disasm { if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) { @@ -51,27 +77,56 @@ fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode { } } - if let Err(e) = vm.run_function(func.clone(), vec![func.into()]) { - eprintln!("{e}"); - ExitCode::FAILURE - } else { - ExitCode::SUCCESS + if let Some(path) = &args.compile { + let f = std::fs::File::options() + .write(true) + .truncate(true) + .create(true) + .open(path); + let mut w = match f { + Ok(f) => f, + Err(e) => { + eprintln!("Error opening output file: {e}"); + return ExitCode::FAILURE + } + }; + if let Err(e) = serial::write_program(&mut w, &func) { + eprintln!("Error writing bytecode: {e}"); + return ExitCode::FAILURE + } + return ExitCode::SUCCESS + } + + let res = vm.run_function(func.clone(), vec![func.into()]); + + match res { + Err(e) => { + eprintln!("{e}"); + ExitCode::FAILURE + } + Ok(Value::Bool(false)) => ExitCode::FAILURE, + Ok(Value::Int(n)) => ExitCode::from(n as u8), + _ => ExitCode::SUCCESS, } } fn main() -> ExitCode { let args = Args::parse(); - if args.repl || args.file.is_none() { + if args.repl || args.args.is_empty() { + if args.compile.is_some() { + eprintln!("Error: cannot compile in REPL mode"); + return ExitCode::FAILURE + } return repl::repl(&args) } - let file = args.file.as_ref().unwrap(); + let file = args.args[0].as_ref(); - match std::fs::read_to_string(file) { - Ok(s) => exec(Symbol::get(file.as_os_str()), &s, &args), + match std::fs::read_to_string(file.to_os_str()) { + Ok(s) => exec(Symbol::get(file), &s, &args), Err(e) => { - eprintln!("Error: {e}"); + eprintln!("Error opening source file: {e}"); ExitCode::FAILURE } } diff --git a/talc-bin/src/repl.rs b/talc-bin/src/repl.rs index 26acbc6..07d3f86 100644 --- a/talc-bin/src/repl.rs +++ b/talc-bin/src/repl.rs @@ -8,14 +8,20 @@ use rustyline::{ }; use talc_lang::{ compiler::compile_repl, - parser, - symbol::Symbol, + lstr, optimize, parser, + symbol::{symbol, Symbol}, value::{function::disasm_recursive, Value}, Vm, }; use crate::{helper::TalcHelper, Args}; +lazy_static::lazy_static! { + pub static ref SYM_PREV1: Symbol = symbol!("_"); + pub static ref SYM_PREV2: Symbol = symbol!("__"); + pub static ref SYM_PREV3: Symbol = symbol!("___"); +} + #[derive(Clone, Copy, Default)] pub struct ReplColors { pub reset: &'static str, @@ -79,13 +85,82 @@ pub fn init_rustyline(args: &Args) -> Result, Ex } } +fn read_init_file(args: &Args) -> Result, std::io::Error> { + if args.args.is_empty() { + return Ok(None) + } + let file = &args.args[0]; + if file == lstr!("-") { + return Ok(None) + } + std::fs::read_to_string(file.to_os_str()).map(Some) +} + +fn exec_line( + vm: Rc>, + line: &str, + args: &Args, + c: &ReplColors, + globals: &mut Vec, +) { + let mut ex = match parser::parse(line) { + Ok(ex) => ex, + Err(e) => { + eprintln!("{}Error:{} {e}", c.error, c.reset); + return + } + }; + + optimize(&mut ex); + + if args.show_ast { + eprintln!("{}", ex); + } + + let (f, g) = match compile_repl(&ex, globals) { + Ok(pair) => pair, + Err(e) => { + eprintln!("{}Error:{} {e}", c.error, c.reset); + return + } + }; + + *globals = g; + let func = Rc::new(f); + + if args.disasm { + if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) { + eprintln!("{}Error:{} {e}", c.error, c.reset); + } + } + + let mut vm = vm.borrow_mut(); + match vm.run_function(func.clone(), vec![func.into()]) { + Ok(v) => { + let prev2 = vm.get_global(*SYM_PREV2).unwrap().clone(); + vm.set_global(*SYM_PREV3, prev2); + let prev1 = vm.get_global(*SYM_PREV1).unwrap().clone(); + vm.set_global(*SYM_PREV2, prev1); + vm.set_global(*SYM_PREV1, v.clone()); + if v != Value::Nil { + println!("{v:#}"); + } + } + Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset), + } +} + pub fn repl(args: &Args) -> ExitCode { + if args.show_ast { + eprintln!("AST printing enabled"); + } + if args.disasm { eprintln!("input disassembly enabled"); } - let mut compiler_globals = Vec::new(); - let mut vm = Vm::new(256); + let mut globals = Vec::new(); + let mut vm = Vm::new(256, args.args.clone()); talc_std::load_all(&mut vm); let interrupt = vm.get_interrupt(); @@ -96,16 +171,20 @@ pub fn repl(args: &Args) -> ExitCode { eprintln!("Warn: couldn't set ctrl+c handler: {e}"); } - let prev1_sym = Symbol::get("_"); - let prev2_sym = Symbol::get("__"); - let prev3_sym = Symbol::get("___"); - - vm.set_global(prev1_sym, Value::Nil); - vm.set_global(prev2_sym, Value::Nil); - vm.set_global(prev3_sym, Value::Nil); - let c = ReplColors::new(args.color); + vm.set_global(*SYM_PREV1, Value::Nil); + vm.set_global(*SYM_PREV2, Value::Nil); + vm.set_global(*SYM_PREV3, Value::Nil); + + let init_src = match read_init_file(args) { + Ok(s) => s, + Err(e) => { + eprintln!("Error opening source file: {e}"); + return ExitCode::FAILURE + } + }; + let mut rl = match init_rustyline(args) { Ok(rl) => rl, Err(e) => return e, @@ -115,6 +194,10 @@ pub fn repl(args: &Args) -> ExitCode { rl.set_helper(Some(TalcHelper::new(vm.clone()))); + if let Some(src) = init_src { + exec_line(vm.clone(), &src, args, &c, &mut globals); + } + loop { if let Some(f) = &args.histfile { let _ = rl.save_history(f); @@ -130,37 +213,6 @@ pub fn repl(args: &Args) -> ExitCode { } }; - let ex = match parser::parse(&line) { - Ok(ex) => ex, - Err(e) => { - eprintln!("{}Error:{} {e}", c.error, c.reset); - continue - } - }; - - let (f, g) = compile_repl(&ex, &compiler_globals); - compiler_globals = g; - let func = Rc::new(f); - - if args.disasm { - if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) { - eprintln!("{}Error:{} {e}", c.error, c.reset); - } - } - - let mut vm = vm.borrow_mut(); - match vm.run_function(func.clone(), vec![func.into()]) { - Ok(v) => { - let prev2 = vm.get_global(prev2_sym).unwrap().clone(); - vm.set_global(prev3_sym, prev2); - let prev1 = vm.get_global(prev1_sym).unwrap().clone(); - vm.set_global(prev2_sym, prev1); - vm.set_global(prev1_sym, v.clone()); - if v != Value::Nil { - println!("{v:#}"); - } - } - Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset), - } + exec_line(vm.clone(), &line, args, &c, &mut globals); } } diff --git a/talc-lang/Cargo.toml b/talc-lang/Cargo.toml index b6938ca..ec41ef7 100644 --- a/talc-lang/Cargo.toml +++ b/talc-lang/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "talc-lang" -version = "0.2.0" +version = "0.2.1" edition = "2021" [dependencies] diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs index 24b55ae..925345e 100644 --- a/talc-lang/src/chunk.rs +++ b/talc-lang/src/chunk.rs @@ -4,7 +4,7 @@ use crate::{ value::Value, }; -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Arg24([u8; 3]); impl Arg24 { @@ -97,36 +97,36 @@ impl TryFrom for Symbol { } #[repr(u8, C, align(4))] -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Instruction { #[default] - Nop, // do nothing + Nop, - LoadLocal(Arg24), // push nth local onto stack - StoreLocal(Arg24), // pop stack into nth local - NewLocal, // pop stack into a new local - DropLocal(Arg24), // remove last n locals + LoadLocal(Arg24), + StoreLocal(Arg24), + NewLocal, + DropLocal(Arg24), - LoadGlobal(Arg24), // load global by id - StoreGlobal(Arg24), // store global by id + LoadGlobal(Arg24), + StoreGlobal(Arg24), - CloseOver(Arg24), // load nth local and convert to cell, write back a copy - Closure(Arg24), // load constant function and fill state from stack - LoadUpvalue(Arg24), // load - StoreUpvalue(Arg24), // store a cell from closure state to new local - ContinueUpvalue(Arg24), // - LoadClosedLocal(Arg24), // load through cell in nth local - StoreClosedLocal(Arg24), // store through cell in nth local + CloseOver(Arg24), + Closure(Arg24), + LoadUpvalue(Arg24), + StoreUpvalue(Arg24), + ContinueUpvalue(Arg24), + LoadClosedLocal(Arg24), + StoreClosedLocal(Arg24), - Const(Arg24), // push nth constant - Int(Arg24), // push integer - Symbol(Arg24), // push symbol - Bool(bool), // push boolean - Nil, // push nil + Const(Arg24), + Int(Arg24), + Symbol(Arg24), + Bool(bool), + Nil, Dup, DupTwo, - Drop(Arg24), + Drop, Swap, UnaryOp(UnaryOp), @@ -151,6 +151,7 @@ pub enum Instruction { EndTry, Call(u8), + Tail(u8), Return, } @@ -161,7 +162,7 @@ impl std::fmt::Display for Instruction { 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::DropLocal(n) => write!(f, "droplocal ${}", usize::from(n)), Self::LoadGlobal(s) => write!( f, "loadglobal {}", @@ -190,7 +191,7 @@ impl std::fmt::Display for Instruction { 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::Drop => write!(f, "drop"), Self::Swap => write!(f, "swap"), Self::UnaryOp(o) => write!(f, "unary {o:?}"), Self::BinaryOp(o) => write!(f, "binary {o:?}"), @@ -208,6 +209,7 @@ impl std::fmt::Display for Instruction { Self::BeginTry(t) => write!(f, "begintry #{}", usize::from(t)), Self::EndTry => write!(f, "endtry"), Self::Call(n) => write!(f, "call #{n}"), + Self::Tail(n) => write!(f, "tail #{n}"), Self::Return => write!(f, "return"), } } diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index 541aa50..873be72 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -1,12 +1,37 @@ -use std::collections::{BTreeMap, HashMap}; +use core::fmt; +use std::collections::HashMap; use std::rc::Rc; use crate::chunk::{Arg24, Catch, Chunk, Instruction as I}; use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind}; +use crate::parser::Pos; use crate::symbol::{Symbol, SYM_SELF}; +use crate::throw; use crate::value::function::{FuncAttrs, Function}; use crate::value::Value; +#[derive(Clone, Debug)] +pub struct CompileError { + pos: Pos, + msg: String, +} + +impl std::error::Error for CompileError {} + +impl fmt::Display for CompileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} | {}", self.pos, self.msg) + } +} + +type Result = std::result::Result; + +macro_rules! throw { + ($pos:expr, $($t:tt)*) => { + return Err(CompileError { pos: $pos, msg: format!($($t)*) }) + }; +} + enum ResolveOutcome { Var(VarKind), InParent, @@ -33,27 +58,39 @@ enum CompilerMode { Module, } +struct BreakFrame { + // ip at beginning of frame + start: usize, + // indices of jumps to reposition to end + end_jumps: Vec, +} + struct Compiler<'a> { mode: CompilerMode, parent: Option<&'a Compiler<'a>>, + // function properties chunk: Chunk, attrs: FuncAttrs, + // variables scope: HashMap, shadowed: Vec<(Symbol, Option)>, - closes: BTreeMap, + closes: Vec<(Symbol, usize)>, local_count: usize, + // break and continue + break_frames: Vec, + drop_barrier: usize, } -pub fn compile(expr: &Expr, name: Option) -> Function { +pub fn compile(expr: &Expr, name: Option) -> Result { let mut comp = Compiler::new_module(name, None); - comp.expr(expr); - comp.finish() + comp.expr(expr)?; + Ok(comp.finish()) } -pub fn compile_repl(expr: &Expr, globals: &[Symbol]) -> (Function, Vec) { +pub fn compile_repl(expr: &Expr, globals: &[Symbol]) -> Result<(Function, Vec)> { let mut comp = Compiler::new_repl(globals); - comp.expr(expr); - comp.finish_repl() + comp.expr(expr)?; + Ok(comp.finish_repl()) } impl<'a> Default for Compiler<'a> { @@ -74,7 +111,9 @@ impl<'a> Default for Compiler<'a> { scope, shadowed: Vec::new(), local_count: 1, - closes: BTreeMap::new(), + closes: Vec::new(), + break_frames: Vec::new(), + drop_barrier: 0, } } } @@ -128,7 +167,7 @@ impl<'a> Compiler<'a> { Function::new(Rc::new(self.chunk), self.attrs, self.closes.len()) } - pub fn finish_inner(mut self) -> (Function, BTreeMap) { + pub fn finish_inner(mut self) -> (Function, Vec<(Symbol, usize)>) { self.emit(I::Return); // TODO closure ( @@ -161,54 +200,47 @@ impl<'a> Compiler<'a> { self.chunk.add_instr(instr) } - fn emit_discard(&mut self, mut n: usize) { - while n > 0 { - let instrs = &mut self.chunk.instrs; - - // dup followed by store: remove the dup - if instrs.len() >= 2 - && matches!(instrs.get(instrs.len() - 2), Some(I::Dup)) - && matches!( - instrs.last(), - Some( - I::NewLocal - | I::StoreLocal(_) | I::StoreGlobal(_) - | I::StoreClosedLocal(_) - | I::StoreUpvalue(_) - ) - ) { - // can't panic: checked that instrs.len() >= 2 - let i = self.chunk.instrs.pop().unwrap(); - self.chunk.instrs.pop().unwrap(); - self.chunk.instrs.push(i); - n -= 1; - continue - } - - // final side-effectless instruction - let poppable = matches!( - instrs.last(), - Some( - I::Dup - | I::Const(_) | I::Int(_) - | I::Nil | I::Bool(_) - | I::Symbol(_) | I::LoadLocal(_) - | I::LoadClosedLocal(_) - | I::LoadUpvalue(_) - ) - ); - if poppable { - // can't panic: checked that instrs.last() was Some - instrs.pop().unwrap(); - n -= 1; - continue - } - - // no more optimizations possible - break + fn emit_discard(&mut self) { + if self.ip() <= self.drop_barrier { + self.emit(I::Drop); + return } - if n > 0 { - self.emit(I::Drop(Arg24::from_usize(n))); + + let instrs = &mut self.chunk.instrs; + + match instrs.last() { + Some( + I::Dup + | I::Const(_) + | I::Int(_) + | I::Nil + | I::Bool(_) + | I::Symbol(_) + | I::LoadLocal(_) + | I::LoadClosedLocal(_) + | I::LoadUpvalue(_), + ) => { + // final side-effectless instruction + instrs.pop().unwrap(); + } + Some( + I::NewLocal + | I::StoreLocal(_) + | I::StoreGlobal(_) + | I::StoreClosedLocal(_) + | I::StoreUpvalue(_), + ) if instrs.len() >= 2 => { + if instrs[instrs.len() - 2] == I::Dup { + // dup followed by store: eliminate the dup + // can't panic: checked that instrs.len() >= 2 + let i = self.chunk.instrs.pop().unwrap(); + self.chunk.instrs.pop().unwrap(); + self.chunk.instrs.push(i); + } + } + _ => { + self.emit(I::Drop); + } } } @@ -220,6 +252,49 @@ impl<'a> Compiler<'a> { self.chunk.instrs[n] = new; } + fn set_drop_barrier(&mut self) { + self.drop_barrier = self.ip(); + } + + fn begin_break_frame(&mut self) { + self.break_frames.push(BreakFrame { + start: self.ip(), + end_jumps: Vec::new(), + }); + } + + fn emit_break(&mut self, pos: Pos) -> Result<()> { + let i = self.emit(I::Nop); + let Some(bf) = self.break_frames.last_mut() else { + throw!(pos, "break statement outside of any loop") + }; + bf.end_jumps.push(i); + Ok(()) + } + + fn emit_continue(&mut self, pos: Pos) -> Result<()> { + let Some(bf) = self.break_frames.last_mut() else { + throw!(pos, "continue statement outside of any loop") + }; + let start = bf.start; + self.emit(I::Jump(Arg24::from_usize(start))); + Ok(()) + } + + fn end_break_frame(&mut self) { + let bf = self + .break_frames + .pop() + .expect("attempt to close unopened break frame"); + let count = bf.end_jumps.len(); + for j in bf.end_jumps { + self.update_instr(j, I::Jump(Arg24::from_usize(self.ip()))); + } + if count > 0 { + self.set_drop_barrier(); + } + } + #[must_use] fn begin_scope(&mut self) -> usize { self.shadowed.len() @@ -230,10 +305,13 @@ impl<'a> Compiler<'a> { while self.shadowed.len() > scope { let (name, var) = self.shadowed.pop().expect("scope bad"); - if let Some(var) = var { - if var.kind != VarKind::Global { + if let Some(in_var) = self.scope.get(&name) { + if in_var.kind != VarKind::Global { locals += 1; } + } + + if let Some(var) = var { self.scope.insert(name, var); } else { self.scope.remove(&name); @@ -257,13 +335,14 @@ impl<'a> Compiler<'a> { let Some(parent) = self.parent else { return ResolveOutcome::None }; - if let ResolveOutcome::None = parent.resolve_name(name) { - return ResolveOutcome::None + match parent.resolve_name(name) { + ResolveOutcome::Var(VarKind::Global) => ResolveOutcome::Var(VarKind::Global), + ResolveOutcome::None => ResolveOutcome::None, + _ => ResolveOutcome::InParent, } - ResolveOutcome::InParent } - fn load_var(&mut self, name: Symbol) { + fn load_var(&mut self, name: Symbol) -> Result<()> { match self.resolve_name(name) { ResolveOutcome::Var(VarKind::Local(n)) => { self.emit(I::LoadLocal(Arg24::from_usize(n))); @@ -272,11 +351,11 @@ impl<'a> Compiler<'a> { self.emit(I::LoadClosedLocal(Arg24::from_usize(n))); } ResolveOutcome::InParent => { - let n = match self.closes.get(&name) { - Some(n) => *n, + let n = match self.closes.iter().position(|(n, _)| *n == name) { + Some(n) => n, None => { let n = self.closes.len(); - self.closes.insert(name, n); + self.closes.push((name, n)); n } }; @@ -286,6 +365,7 @@ impl<'a> Compiler<'a> { self.emit(I::LoadGlobal(Arg24::from_symbol(name))); } } + Ok(()) } fn declare_local(&mut self, name: Symbol) -> usize { @@ -328,11 +408,11 @@ impl<'a> Compiler<'a> { self.emit(I::StoreClosedLocal(Arg24::from_usize(n))); } ResolveOutcome::InParent => { - let n = match self.closes.get(&name) { - Some(n) => *n, + let n = match self.closes.iter().position(|(n, _)| *n == name) { + Some(n) => n, None => { let n = self.closes.len(); - self.closes.insert(name, n); + self.closes.push((name, n)); n } }; @@ -354,8 +434,8 @@ impl<'a> Compiler<'a> { // Expressions // - fn expr(&mut self, e: &Expr) { - let Expr { kind, .. } = e; + fn expr(&mut self, e: &Expr) -> Result<()> { + let Expr { kind, span } = e; match kind { ExprKind::Block(xs) if xs.is_empty() => { self.emit(I::Nil); @@ -363,33 +443,41 @@ impl<'a> Compiler<'a> { ExprKind::Block(xs) => { let scope = self.begin_scope(); for x in &xs[0..xs.len() - 1] { - self.expr(x); - self.emit_discard(1); + self.expr(x)?; + self.emit_discard(); } - self.expr(&xs[xs.len() - 1]); + self.expr(&xs[xs.len() - 1])?; self.end_scope(scope); } ExprKind::Literal(v) => self.expr_literal(v), - ExprKind::Ident(ident) => self.load_var(*ident), + ExprKind::Ident(ident) => self.load_var(*ident)?, ExprKind::UnaryOp(o, a) => { - self.expr(a); + self.expr(a)?; self.emit(I::UnaryOp(*o)); } ExprKind::BinaryOp(o, a, b) => { - self.expr(a); - self.expr(b); + self.expr(a)?; + self.expr(b)?; self.emit(I::BinaryOp(*o)); } - ExprKind::Assign(o, lv, a) => self.expr_assign(*o, lv, a), + ExprKind::Assign(o, lv, a) => self.expr_assign(*o, lv, a)?, ExprKind::AssignVar(name, a) => { - self.expr(a); + if let Some(a) = a { + self.expr(a)?; + } else { + self.emit(I::Nil); + } self.emit(I::Dup); self.assign_local(*name); } ExprKind::AssignGlobal(name, a) => { - self.expr(a); - self.emit(I::Dup); - self.assign_global(*name); + if let Some(a) = a { + self.expr(a)?; + self.emit(I::Dup); + self.assign_global(*name); + } else { + self.declare_global(*name); + } } ExprKind::List(xs) if xs.is_empty() => { self.emit(I::NewList(0)); @@ -398,7 +486,7 @@ impl<'a> Compiler<'a> { 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)); @@ -415,8 +503,8 @@ impl<'a> Compiler<'a> { 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)); @@ -427,91 +515,152 @@ impl<'a> Compiler<'a> { } } ExprKind::Index(ct, idx) => { - self.expr(ct); - self.expr(idx); + self.expr(ct)?; + self.expr(idx)?; self.emit(I::Index); } ExprKind::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)); } + ExprKind::TailCall(f, args) => { + self.expr(f)?; + for a in args { + self.expr(a)?; + } + self.emit(I::Tail(args.len() as u8)); + } ExprKind::AssocFnCall(o, f, args) => { - self.expr(o); + self.expr(o)?; self.emit(I::Dup); self.emit(I::Symbol(Arg24::from_symbol(*f))); self.emit(I::Index); self.emit(I::Swap); for a in args { - self.expr(a); + self.expr(a)?; } self.emit(I::Call((args.len() + 1) as u8)); } - ExprKind::Return(e) => { - self.expr(e); - self.emit(I::Return); - } - ExprKind::Pipe(a, f) => { - self.expr(a); - self.expr(f); + ExprKind::AssocTailCall(o, f, args) => { + self.expr(o)?; + self.emit(I::Dup); + self.emit(I::Symbol(Arg24::from_symbol(*f))); + self.emit(I::Index); self.emit(I::Swap); - self.emit(I::Call(1)); + for a in args { + self.expr(a)?; + } + self.emit(I::Tail((args.len() + 1) as u8)); } - ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body), - ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body), - ExprKind::And(a, b) => { - 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.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); - } - ExprKind::Or(a, b) => { - 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.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip()))); - } - ExprKind::If(cond, b1, b2) => { - self.expr(cond); - let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); - 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); + ExprKind::Return(e) => { + if let Some(e) = e { + self.expr(e)?; } else { self.emit(I::Nil); } + self.emit(I::Return); + } + ExprKind::Break(e) => { + if let Some(e) = e { + self.expr(e)?; + } else { + self.emit(I::Nil); + } + self.emit_break(span.start)?; + } + ExprKind::Continue => { + self.emit_continue(span.start)?; + } + ExprKind::Pipe(a, f) => { + self.expr(a)?; + self.expr(f)?; + self.emit(I::Swap); + self.emit(I::Call(1)); + } + ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body)?, + ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body)?, + ExprKind::And(a, b) => { + self.expr(a)?; + self.emit(I::Dup); + let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); + self.emit_discard(); + self.expr(b)?; + self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); + } + ExprKind::Or(a, b) => { + self.expr(a)?; + self.emit(I::Dup); + let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0))); + self.emit_discard(); + self.expr(b)?; + self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip()))); + } + ExprKind::If(cond, b1, b2) => { + self.expr(cond)?; + let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); + 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)?; + } else { + self.emit(I::Nil); + } + self.set_drop_barrier(); self.update_instr(j2, I::Jump(Arg24::from_usize(self.ip()))); } - ExprKind::While(cond, body) => { - let start = self.ip(); - self.expr(cond); - - let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); - 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); - } - ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body), - ExprKind::Try(body, catches) => self.expr_try(body, catches), + ExprKind::While(cond, body) => self.expr_while(cond, body)?, + ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body)?, + ExprKind::Try(body, catches) => self.expr_try(body, catches)?, } + Ok(()) } - fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) { + fn expr_infinite_loop(&mut self, body: &Expr) -> Result<()> { + let start = self.ip(); + self.begin_break_frame(); + + self.expr(body)?; + self.emit_discard(); + self.emit(I::Jump(Arg24::from_usize(start))); + + self.end_break_frame(); + + Ok(()) + } + + fn expr_while(&mut self, cond: &Expr, body: &Expr) -> Result<()> { + if let ExprKind::Literal(v) = &cond.kind { + if v.truthy() { + self.expr_infinite_loop(body)?; + return Ok(()) + } + } + let start = self.ip(); + self.begin_break_frame(); + + self.expr(cond)?; + + let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0))); + self.expr(body)?; + self.emit_discard(); + self.emit(I::Jump(Arg24::from_usize(start))); + self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip()))); + + self.emit(I::Nil); + self.end_break_frame(); + + Ok(()) + } + + fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) -> Result<()> { let (idx, mut table) = self.chunk.begin_try_table(self.local_count); self.emit(I::BeginTry(Arg24::from_usize(idx))); - self.expr(body); + 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(); @@ -527,10 +676,10 @@ impl<'a> Compiler<'a> { if let Some(name) = catch_block.name { self.assign_local(name); } else { - self.emit_discard(1); + self.emit_discard(); } - self.expr(&catch_block.body); + self.expr(&catch_block.body)?; self.end_scope(scope); let end_addr = self.emit(I::Jump(Arg24::from_usize(0))); @@ -544,11 +693,13 @@ impl<'a> Compiler<'a> { } self.chunk.finish_catch_table(idx, table); + + Ok(()) } - fn expr_for(&mut self, name: Symbol, iter: &Expr, body: &Expr) { + fn expr_for(&mut self, name: Symbol, iter: &Expr, body: &Expr) -> Result<()> { // load iterable and convert to iterator - self.expr(iter); + self.expr(iter)?; self.emit(I::IterBegin); // declare loop variable @@ -558,6 +709,7 @@ impl<'a> Compiler<'a> { // begin loop let start = self.ip(); + self.begin_break_frame(); // call iterator and jump if nil, otherwise store self.emit(I::Dup); @@ -566,21 +718,30 @@ impl<'a> Compiler<'a> { self.store_var(name); // body - self.expr(body); - self.emit_discard(1); + self.expr(body)?; + self.emit_discard(); // end loop self.emit(I::Jump(Arg24::from_usize(start))); self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip()))); self.end_scope(scope); + self.emit(I::Nil); + self.end_break_frame(); + + Ok(()) } - fn expr_fndef(&mut self, name: Option, args: &[Symbol], body: &Expr) { + fn expr_fndef( + &mut self, + name: Option, + args: &[Symbol], + body: &Expr, + ) -> Result<()> { let mut inner = self.new_function(name, args); inner.parent = Some(self); - inner.expr(body); + inner.expr(body)?; let (func, closes) = inner.finish_inner(); let func_const = self.add_const(func.into()); @@ -600,7 +761,7 @@ impl<'a> Compiler<'a> { } ResolveOutcome::InParent => { let n = self.closes.len(); - self.closes.insert(name, n); + self.closes.push((name, n)); self.emit(I::ContinueUpvalue(Arg24::from_usize(n))); } ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => { @@ -619,6 +780,8 @@ impl<'a> Compiler<'a> { self.emit(I::Dup); self.store_var(name); } + + Ok(()) } fn expr_literal(&mut self, val: &Value) { @@ -642,35 +805,36 @@ impl<'a> Compiler<'a> { } } - fn expr_assign(&mut self, o: Option, lv: &LValue, a: &Expr) { + fn expr_assign(&mut self, o: Option, lv: &LValue, a: &Expr) -> Result<()> { match (&lv.kind, o) { (LValueKind::Ident(i), None) => { - self.expr(a); + self.expr(a)?; self.emit(I::Dup); self.store_var(*i); } (LValueKind::Ident(i), Some(o)) => { - self.load_var(*i); - self.expr(a); + self.load_var(*i)?; + self.expr(a)?; self.emit(I::BinaryOp(o)); self.emit(I::Dup); self.store_var(*i); } (LValueKind::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); } (LValueKind::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/exception.rs b/talc-lang/src/exception.rs index 98065dc..febebb5 100644 --- a/talc-lang/src/exception.rs +++ b/talc-lang/src/exception.rs @@ -1,6 +1,6 @@ use crate::{ lstring::LStr, - symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE}, + symbol::{Symbol, SYM_MSG, SYM_TYPE}, value::{HashValue, Value}, }; use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; @@ -11,57 +11,26 @@ pub type Result = std::result::Result; pub struct Exception { pub ty: Symbol, pub msg: Option>, - pub data: Option, } impl Exception { pub fn new(ty: Symbol) -> Self { - Self { - ty, - msg: None, - data: None, - } + Self { ty, msg: None } } pub fn new_with_msg(ty: Symbol, msg: Rc) -> Self { - Self { - ty, - msg: Some(msg), - data: None, - } - } - - pub fn new_with_data(ty: Symbol, data: Value) -> Self { - Self { - ty, - msg: None, - data: Some(data), - } - } - - pub fn new_with_msg_data(ty: Symbol, msg: Rc, data: Value) -> Self { - Self { - ty, - msg: Some(msg), - data: Some(data), - } + Self { ty, msg: Some(msg) } } pub fn from_table(table: &Rc>>) -> Option { let table = table.borrow(); let ty = table.get(&(*SYM_TYPE).into())?; if let Value::Symbol(ty) = ty { - let data = table.get(&(*SYM_DATA).into()).cloned(); let msg = table.get(&(*SYM_MSG).into()); match msg { - None => Some(Self { - ty: *ty, - data, - msg: None, - }), + None => Some(Self { ty: *ty, msg: None }), Some(Value::String(msg)) => Some(Self { ty: *ty, - data, msg: Some(msg.clone()), }), Some(_) => None, @@ -77,9 +46,6 @@ impl Exception { if let Some(msg) = self.msg { table.insert((*SYM_MSG).into(), Value::String(msg)); } - if let Some(data) = self.data { - table.insert((*SYM_DATA).into(), data); - } Rc::new(RefCell::new(table)) } } diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs index 78f84a5..ff3a7e9 100644 --- a/talc-lang/src/lib.rs +++ b/talc-lang/src/lib.rs @@ -1,12 +1,17 @@ #![allow(clippy::mutable_key_type)] #![warn(clippy::semicolon_if_nothing_returned)] #![warn(clippy::allow_attributes)] +#![warn(clippy::inconsistent_struct_constructor)] +#![warn(clippy::uninlined_format_args)] pub mod chunk; pub mod compiler; pub mod exception; pub mod lstring; +mod optimize; +pub use optimize::optimize; pub mod parser; +pub mod serial; pub mod symbol; pub mod value; diff --git a/talc-lang/src/lstring.rs b/talc-lang/src/lstring.rs index 9de9765..10c4088 100644 --- a/talc-lang/src/lstring.rs +++ b/talc-lang/src/lstring.rs @@ -515,6 +515,22 @@ impl<'a> Extend<&'a char> for LString { } } +// +// Equality +// + +impl PartialEq for LStr { + fn eq(&self, other: &LString) -> bool { + self == other.as_ref() + } +} + +impl PartialEq for LString { + fn eq(&self, other: &LStr) -> bool { + self.as_ref() == other + } +} + // // write // diff --git a/talc-lang/src/optimize.rs b/talc-lang/src/optimize.rs new file mode 100644 index 0000000..a43d2bb --- /dev/null +++ b/talc-lang/src/optimize.rs @@ -0,0 +1,229 @@ +use core::panic; + +use crate::{ + parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind}, + value::Value, + vm, +}; + +impl Expr { + fn value(&self) -> Option<&Value> { + if let ExprKind::Literal(v) = &self.kind { + Some(v) + } else { + None + } + } +} + +fn expr_take(e: &mut Expr) -> Expr { + std::mem::replace(e, ExprKind::Literal(Value::Nil).span(e.span)) +} + +#[derive(Clone, Copy, Debug)] +struct OptState { + ret: bool, +} + +impl OptState { + #[inline] + pub const fn empty() -> Self { + Self { ret: false } + } + + #[inline] + pub const fn ret(ret: bool) -> Self { + Self { ret } + } +} + +pub fn optimize(body: &mut Expr) { + let state = OptState::ret(true); + optimize_ex_with(body, state); +} + +#[inline] +fn optimize_ex(expr: &mut Expr) { + optimize_ex_with(expr, OptState::empty()); +} + +fn optimize_ex_with(expr: &mut Expr, state: OptState) { + let span = expr.span; + match &mut expr.kind { + ExprKind::Literal(_) => (), + ExprKind::Ident(_) => (), + ExprKind::UnaryOp(o, e) => { + optimize_ex(e); + if let Some(a) = e.value() { + if let Ok(v) = vm::unary_op(*o, a.clone()) { + *expr = ExprKind::Literal(v).span(span); + } + } + } + ExprKind::BinaryOp(o, l, r) => { + optimize_ex(l); + optimize_ex(r); + if let (Some(a), Some(b)) = (l.value(), r.value()) { + if let Ok(v) = vm::binary_op(*o, a.clone(), b.clone()) { + *expr = ExprKind::Literal(v).span(span); + } + } + } + ExprKind::Assign(_, l, r) => { + optimize_lv(l); + optimize_ex(r); + } + ExprKind::AssignVar(_, e) => { + if let Some(e) = e { + optimize_ex(e); + } + } + ExprKind::AssignGlobal(_, e) => { + if let Some(e) = e { + optimize_ex(e); + } + } + ExprKind::FnDef(_, _, e) => { + optimize_ex_with(e, OptState::ret(true)); + } + ExprKind::Index(l, r) => { + optimize_ex(l); + optimize_ex(r); + } + ExprKind::FnCall(f, xs) => { + optimize_ex(f); + for e in &mut *xs { + optimize_ex(e); + } + if state.ret { + *expr = ExprKind::TailCall(Box::new(expr_take(f)), std::mem::take(xs)) + .span(span); + } + } + ExprKind::AssocFnCall(f, a, xs) => { + optimize_ex(f); + for e in &mut *xs { + optimize_ex(e); + } + if state.ret { + *expr = + ExprKind::AssocTailCall(Box::new(expr_take(f)), *a, std::mem::take(xs)) + .span(span); + } + } + ExprKind::Pipe(l, r) => { + optimize_ex(l); + optimize_ex(r); + } + ExprKind::Block(xs) => { + let len = xs.len(); + if len > 1 { + for e in &mut xs[..len - 1] { + optimize_ex(e); + } + } + if let Some(e) = xs.last_mut() { + optimize_ex_with(e, state); + } + } + ExprKind::List(xs) => { + for e in xs { + optimize_ex(e); + } + } + ExprKind::Table(t) => { + for (k, v) in t { + optimize_ex(k); + optimize_ex(v); + } + } + ExprKind::Return(e) => { + if let Some(e) = e { + optimize_ex_with(e, OptState::ret(true)); + } + } + ExprKind::Break(e) => { + if let Some(e) = e { + optimize_ex(e); + } + } + ExprKind::Continue => (), + ExprKind::And(l, r) => { + optimize_ex(l); + optimize_ex(r); + if let Some(v) = l.value() { + if v.truthy() { + *expr = expr_take(r); + } else { + *expr = expr_take(l); + } + } + } + ExprKind::Or(l, r) => { + optimize_ex(l); + optimize_ex(r); + if let Some(v) = l.value() { + if v.truthy() { + *expr = expr_take(l); + } else { + *expr = expr_take(r); + } + } + } + ExprKind::If(c, t, e) => { + optimize_ex(c); + optimize_ex_with(t, state); + if let Some(e) = e { + optimize_ex_with(e, state); + } + if let Some(v) = c.value() { + if v.truthy() { + *expr = expr_take(t); + } else if let Some(e) = e { + *expr = expr_take(e); + } else { + *expr = ExprKind::Literal(Value::Nil).span(span); + } + } + } + ExprKind::While(c, b) => { + optimize_ex(c); + optimize_ex(b); + if let Some(v) = c.value() { + if !v.truthy() { + *expr = ExprKind::Literal(Value::Nil).span(span); + } + } + } + ExprKind::For(_, i, b) => { + optimize_ex(i); + optimize_ex(b); + } + ExprKind::Lambda(_, b) => { + optimize_ex_with(b, OptState::ret(true)); + } + ExprKind::Try(b, cx) => { + optimize_ex_with(b, state); + for c in cx { + optimize_cb(c); + } + } + ExprKind::TailCall(..) | ExprKind::AssocTailCall(..) => { + panic!("cannot optimize expression generated by optimizer") + } + } +} + +pub fn optimize_lv(e: &mut LValue) { + match &mut e.kind { + LValueKind::Ident(_) => (), + LValueKind::Index(l, r) => { + optimize_ex(l); + optimize_ex(r); + } + } +} + +pub fn optimize_cb(e: &mut CatchBlock) { + optimize_ex(&mut e.body); +} diff --git a/talc-lang/src/parser/ast.rs b/talc-lang/src/parser/ast.rs index d9cdec1..1ea684f 100644 --- a/talc-lang/src/parser/ast.rs +++ b/talc-lang/src/parser/ast.rs @@ -4,7 +4,7 @@ use crate::{symbol::Symbol, value::Value}; use super::Span; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BinaryOp { Add, Sub, @@ -30,7 +30,7 @@ pub enum BinaryOp { RangeIncl, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnaryOp { Neg, Not, @@ -52,8 +52,8 @@ pub enum ExprKind { BinaryOp(BinaryOp, Box, Box), Assign(Option, Box, Box), - AssignVar(Symbol, Box), - AssignGlobal(Symbol, Box), + AssignVar(Symbol, Option>), + AssignGlobal(Symbol, Option>), FnDef(Option, Vec, Box), Index(Box, Box), @@ -65,7 +65,9 @@ pub enum ExprKind { List(Vec), Table(Vec<(Expr, Expr)>), - Return(Box), + Return(Option>), + Break(Option>), + Continue, And(Box, Box), Or(Box, Box), If(Box, Box, Option>), @@ -73,6 +75,10 @@ pub enum ExprKind { For(Symbol, Box, Box), Lambda(Vec, Box), Try(Box, Vec), + + // Created by optimizer + TailCall(Box, Vec), + AssocTailCall(Box, Symbol, Vec), } impl ExprKind { @@ -189,21 +195,28 @@ impl Expr { } ExprKind::AssignVar(l, r) => { writeln!(w, "var {}", l.name())?; - r.write_to(w, depth) + if let Some(r) = r { + r.write_to(w, depth)?; + } + Ok(()) } ExprKind::AssignGlobal(l, r) => { writeln!(w, "global {}", l.name())?; - r.write_to(w, depth) + if let Some(r) = r { + r.write_to(w, depth)?; + } + Ok(()) } ExprKind::FnDef(n, p, b) => { if let Some(n) = n { - writeln!(w, "fndef ${}", n.name())?; + write!(w, "fndef ${}", n.name())?; } else { - writeln!(w, "fndef anon")?; + write!(w, "fndef anon")?; } for arg in p { - writeln!(w, " ${}", arg.name())?; + write!(w, " ${}", arg.name())?; } + writeln!(w)?; b.write_to(w, depth) } ExprKind::Index(l, r) => { @@ -256,7 +269,20 @@ impl Expr { } ExprKind::Return(e) => { writeln!(w, "return")?; - e.write_to(w, depth) + if let Some(e) = e { + e.write_to(w, depth)?; + } + Ok(()) + } + ExprKind::Break(e) => { + writeln!(w, "break")?; + if let Some(e) = e { + e.write_to(w, depth)?; + } + Ok(()) + } + ExprKind::Continue => { + writeln!(w, "continue") } ExprKind::And(l, r) => { writeln!(w, "and")?; @@ -303,6 +329,23 @@ impl Expr { } Ok(()) } + // generated by optimized + ExprKind::TailCall(f, a) => { + writeln!(w, "tail")?; + f.write_to(w, depth)?; + for arg in a { + arg.write_to(w, depth)?; + } + Ok(()) + } + ExprKind::AssocTailCall(d, f, a) => { + writeln!(w, "assoc tail {}", f.name())?; + d.write_to(w, depth)?; + for arg in a { + arg.write_to(w, depth)?; + } + Ok(()) + } } } } diff --git a/talc-lang/src/parser/lexer.rs b/talc-lang/src/parser/lexer.rs index 05b45d9..f0dad76 100644 --- a/talc-lang/src/parser/lexer.rs +++ b/talc-lang/src/parser/lexer.rs @@ -219,8 +219,8 @@ impl<'s> Lexer<'s> { fn invalid_char(&self, c: char) -> ParserError { let span = Span::new(self.pos, self.pos.advance(c)); let msg = match c as u32 { - c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{:2x})", c), - c => format!("invalid character (codepoint U+{:04x})", c), + c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{c:2x})"), + c => format!("invalid character (codepoint U+{c:04x})"), }; ParserError { span, msg } } @@ -326,7 +326,7 @@ impl<'s> Lexer<'s> { let c = self.peek()?; if c == '_' || c.is_digit(radix) { self.next()?; - } else if is_xid_start(c) { + } else if is_xid_start(c) || c.is_ascii_digit() { return self.unexpected() } else { return self.emit(K::Integer) @@ -569,11 +569,13 @@ impl<'s> Lexer<'s> { _ => self.emit(K::Dot), }, ':' => match self.and_peek()? { - c if is_xid_start(c) || c == '"' || c == '\'' => self.next_symbol(), + c if is_xid_start(c) || c == '_' || c == '"' || c == '\'' => { + self.next_symbol() + } _ => self.emit(K::Colon), }, '0'..='9' => self.next_number(), - c if is_xid_start(c) => self.next_ident(), + c if c == '_' || is_xid_start(c) => self.next_ident(), '"' | '\'' => self.next_string(), _ => self.unexpected(), } diff --git a/talc-lang/src/parser/parser.rs b/talc-lang/src/parser/parser.rs index c7ce3c6..fc89322 100644 --- a/talc-lang/src/parser/parser.rs +++ b/talc-lang/src/parser/parser.rs @@ -162,9 +162,10 @@ impl TokenKind { matches!( self, T::Return - | T::Var | T::Global - | T::Fn | T::Not - | T::Backslash + | T::Continue + | T::Break | T::Var + | T::Global | T::Fn + | T::Not | T::Backslash | T::Colon | T::Minus | T::Identifier | T::LParen | T::LBrack @@ -569,7 +570,7 @@ impl<'s> Parser<'s> { let lhs_span = lhs.span; if let Some(op) = self.peek()?.kind.assign_op() { let Some(lval) = LValue::from_expr(lhs) else { - throw!(lhs_span, "invalid lvalue for assingment") + throw!(lhs_span, "invalid lvalue for assignment") }; self.next()?; let rhs = self.parse_decl()?; @@ -582,16 +583,19 @@ impl<'s> Parser<'s> { fn parse_var_decl(&mut self) -> Result { let first = expect!(self, T::Var | T::Global); - let name = expect!(self, T::Identifier); - expect!(self, T::Equal); - let val = self.parse_decl()?; - let val_span = val.span; let kind = if first.kind == T::Global { E::AssignGlobal } else { E::AssignVar }; - Ok(kind(Symbol::get(name.content), b(val)).span(first.span + val_span)) + let name = expect!(self, T::Identifier); + if try_next!(self, T::Equal).is_some() { + let val = self.parse_decl()?; + let val_span = val.span; + Ok(kind(Symbol::get(name.content), Some(b(val))).span(first.span + val_span)) + } else { + Ok(kind(Symbol::get(name.content), None).span(first.span + name.span)) + } } fn parse_fn_decl(&mut self) -> Result { @@ -624,10 +628,30 @@ impl<'s> Parser<'s> { } fn parse_expr(&mut self) -> Result { - if let Some(tok) = try_next!(self, T::Return) { - let expr = self.parse_decl()?; - let span = expr.span; - Ok(E::Return(b(expr)).span(tok.span + span)) + let tok = try_next!(self, T::Return | T::Break | T::Continue); + if let Some(tok) = tok { + match tok.kind { + T::Return => { + if self.peek()?.kind.expr_first() { + let expr = self.parse_decl()?; + let span = expr.span; + Ok(E::Return(Some(b(expr))).span(tok.span + span)) + } else { + Ok(E::Return(None).span(tok.span)) + } + } + T::Break => { + if self.peek()?.kind.expr_first() { + let expr = self.parse_decl()?; + let span = expr.span; + Ok(E::Break(Some(b(expr))).span(tok.span + span)) + } else { + Ok(E::Break(None).span(tok.span)) + } + } + T::Continue => Ok(E::Continue.span(tok.span)), + _ => unreachable!("parse_expr: guaranteed by try_next!"), + } } else { self.parse_decl() } diff --git a/talc-lang/src/serial/mod.rs b/talc-lang/src/serial/mod.rs new file mode 100644 index 0000000..d987959 --- /dev/null +++ b/talc-lang/src/serial/mod.rs @@ -0,0 +1,216 @@ +use core::fmt; +use std::io::{self, Write}; + +use crate::{ + chunk::{Chunk, Instruction, TryTable}, + lstring::LStr, + symbol::Symbol, + value::{function::Function, range::RangeType, Value}, +}; + +const MAGIC: [u8; 8] = *b"\x7fTALC\0\0\0"; +const VERSION: u32 = 1; + +#[derive(Debug)] +pub enum SerialError { + Io(io::Error), + Serial(String), +} + +impl std::error::Error for SerialError {} + +impl fmt::Display for SerialError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SerialError::Io(e) => e.fmt(f), + SerialError::Serial(e) => e.fmt(f), + } + } +} + +type Result = std::result::Result; + +impl From for SerialError { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for SerialError { + fn from(value: String) -> Self { + Self::Serial(value) + } +} + +impl From<&str> for SerialError { + fn from(value: &str) -> Self { + Self::Serial(value.to_owned()) + } +} + +pub fn write_program(w: &mut impl Write, prog: &Function) -> Result<()> { + ProgramWriter::new(w).write_program(prog) +} + +struct ProgramWriter<'w, W: Write> { + w: &'w mut W, +} + +impl<'w, W: Write> ProgramWriter<'w, W> { + fn new(w: &'w mut W) -> Self { + Self { w } + } + + fn write_program(&mut self, prog: &Function) -> Result<()> { + self.w.write_all(&MAGIC)?; + self.w.write_all(&VERSION.to_le_bytes())?; + self.write_function(prog)?; + Ok(()) + } + + fn write_function(&mut self, func: &Function) -> Result<()> { + self.write_bool(func.attrs.name.is_some())?; + if let Some(name) = func.attrs.name { + self.write_sym(name)?; + } + self.write_u32(func.attrs.arity as u32)?; + self.write_u32(func.state.len() as u32)?; + self.write_chunk(&func.chunk)?; + Ok(()) + } + + fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> { + self.write_u32(chunk.consts.len() as u32)?; + self.write_u32(chunk.instrs.len() as u32)?; + self.write_u32(chunk.try_tables.len() as u32)?; + + for c in &chunk.consts { + self.write_value(c)?; + } + for i in &chunk.instrs { + self.write_instr(*i)?; + } + for t in &chunk.try_tables { + self.write_try_table(t)?; + } + Ok(()) + } + + fn write_value(&mut self, val: &Value) -> Result<()> { + match val { + Value::Nil => self.write_u8(0), + Value::Bool(b) => { + self.write_u8(1)?; + self.write_bool(*b) + } + Value::Symbol(sym) => { + self.write_u8(2)?; + self.write_sym(*sym) + } + Value::Int(n) => { + self.write_u8(3)?; + self.write_i64(*n) + } + Value::Ratio(r) => { + self.write_u8(4)?; + self.write_i64(*r.numer())?; + self.write_i64(*r.denom()) + } + Value::Float(f) => { + self.write_u8(5)?; + self.write_f64(*f) + } + Value::Complex(z) => { + self.write_u8(6)?; + self.write_f64(z.re)?; + self.write_f64(z.im) + } + Value::Range(r) => { + self.write_u8(7)?; + match r.ty { + RangeType::Open => self.write_u8(0)?, + RangeType::Closed => self.write_u8(1)?, + RangeType::Endless => self.write_u8(2)?, + } + self.write_i64(r.start)?; + self.write_i64(r.stop) + } + Value::String(s) => { + self.write_u8(8)?; + self.write_str(s) + } + Value::Function(func) => { + self.write_u8(9)?; + self.write_function(func) + } + Value::Cell(_) + | Value::List(_) + | Value::Table(_) + | Value::NativeFunc(_) + | Value::Native(_) => Err(format!( + "cannot serialize value of type {}", + val.get_type().name() + ) + .into()), + } + } + + fn write_instr(&mut self, i: Instruction) -> Result<()> { + let n: u32 = unsafe { std::mem::transmute(i) }; + self.write_u32(n) + } + + fn write_try_table(&mut self, t: &TryTable) -> Result<()> { + self.write_u32(t.local_count as u32)?; + self.write_u32(t.catches.len() as u32)?; + for catch in &t.catches { + self.write_u32(catch.addr as u32)?; + self.write_bool(catch.types.is_some())?; + if let Some(tys) = &catch.types { + self.write_u32(tys.len() as u32)?; + for ty in tys { + self.write_sym(*ty)?; + } + } + } + Ok(()) + } + + fn write_sym(&mut self, sym: Symbol) -> Result<()> { + self.write_str(sym.name()) + } + + fn write_str(&mut self, s: &LStr) -> Result<()> { + let Ok(n) = s.len().try_into() else { + return Err("string to long to serialize".into()) + }; + self.write_u32(n)?; + self.w.write_all(s.as_bytes())?; + Ok(()) + } + + fn write_f64(&mut self, x: f64) -> Result<()> { + self.w.write_all(&x.to_le_bytes())?; + Ok(()) + } + + fn write_i64(&mut self, n: i64) -> Result<()> { + self.w.write_all(&n.to_le_bytes())?; + Ok(()) + } + + fn write_u32(&mut self, n: u32) -> Result<()> { + self.w.write_all(&n.to_le_bytes())?; + Ok(()) + } + + fn write_u8(&mut self, n: u8) -> Result<()> { + self.w.write_all(&[n])?; + Ok(()) + } + + fn write_bool(&mut self, n: bool) -> Result<()> { + self.w.write_all(&[n as u8])?; + Ok(()) + } +} diff --git a/talc-lang/src/symbol.rs b/talc-lang/src/symbol.rs index 1f00035..8fe021e 100644 --- a/talc-lang/src/symbol.rs +++ b/talc-lang/src/symbol.rs @@ -31,7 +31,6 @@ lazy_static! { pub static ref SYM_END_ITERATION: Symbol = symbol!(end_iteration); pub static ref SYM_TYPE: Symbol = symbol!(type); pub static ref SYM_MSG: Symbol = symbol!(msg); - pub static ref SYM_DATA: Symbol = symbol!(data); pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error); pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error); pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error); diff --git a/talc-lang/src/value/function.rs b/talc-lang/src/value/function.rs index 4b4f164..2d8c85f 100644 --- a/talc-lang/src/value/function.rs +++ b/talc-lang/src/value/function.rs @@ -26,8 +26,8 @@ impl Function { pub fn from_parts(chunk: Rc, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self { Self { - chunk, attrs, + chunk, state, } } diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index 66018c9..a9a6593 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ chunk::Instruction, exception::{throw, Exception, Result}, - lstring::LStr, + lstring::{LStr, LString}, parser::ast::{BinaryOp, UnaryOp}, symbol::{ Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR, @@ -50,6 +50,7 @@ pub struct Vm { stack_max: usize, globals: HashMap, interrupt: Arc, + args: Vec, } pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result { @@ -100,7 +101,9 @@ fn get_call_outcome(args: Vec) -> Result { let argc = args.len() - 1; match argc.cmp(&attrs.arity) { Ordering::Equal => Ok(CallOutcome::Call(args)), - Ordering::Greater => throw!(*SYM_TYPE_ERROR, "too many arguments for function"), + Ordering::Greater => { + throw!(*SYM_TYPE_ERROR, "too many arguments for function {}", f) + } Ordering::Less => { let remaining = attrs.arity - argc; let f = f.clone(); @@ -123,12 +126,13 @@ fn get_call_outcome(args: Vec) -> Result { } impl Vm { - pub fn new(stack_max: usize) -> Self { + pub fn new(stack_max: usize, args: Vec) -> Self { Self { stack: Vec::with_capacity(16), call_stack: Vec::with_capacity(16), globals: HashMap::with_capacity(16), stack_max, + args, interrupt: Arc::new(AtomicBool::new(false)), } } @@ -156,6 +160,10 @@ impl Vm { &self.globals } + pub fn args(&self) -> &Vec { + &self.args + } + pub fn call_value(&mut self, value: Value, args: Vec) -> Result { self.check_interrupt()?; match get_call_outcome(args)? { @@ -351,10 +359,8 @@ impl Vm { self.push(self.stack[self.stack.len() - 2].clone()); } // [a0,a1...an] -> [] - I::Drop(n) => { - for _ in 0..u32::from(n) { - self.pop(); - } + I::Drop => { + self.pop(); } // [x,y] -> [y,x] I::Swap => { @@ -476,6 +482,7 @@ impl Vm { } // [f,a0,a1...an] -> [f(a0,a1...an)] I::Call(n) => { + self.check_interrupt()?; let n = usize::from(n); let args = self.pop_n(n + 1); @@ -526,6 +533,44 @@ impl Vm { unreachable!("already verified by calling get_call_type"); } } + // [f,a0,a1...an] -> [], return f(a0,a1...an) + I::Tail(n) => { + self.check_interrupt()?; + let n = usize::from(n); + + let args = self.pop_n(n + 1); + + let args = match get_call_outcome(args)? { + CallOutcome::Call(args) => args, + CallOutcome::Partial(v) => { + if frame.root { + return Ok(Some(v)) + } + self.check_interrupt()?; + *frame = self.call_stack.pop().expect("no root frame"); + return Ok(None) + } + }; + + if let Value::NativeFunc(nf) = &args[0] { + let nf = nf.clone(); + + let res = (nf.func)(self, args)?; + + if frame.root { + return Ok(Some(res)) + } + + self.push(res); + *frame = self.call_stack.pop().expect("no root frame"); + } else if let Value::Function(func) = &args[0] { + let mut new_frame = CallFrame::new(func.clone(), args); + new_frame.root = frame.root; + *frame = new_frame; + } else { + unreachable!("already verified by calling get_call_type"); + } + } // [v] -> [], return v I::Return if frame.root => return Ok(Some(self.pop())), // [v] -> [], return v diff --git a/talc-lang/tests/parser.rs b/talc-lang/tests/parser.rs new file mode 100644 index 0000000..f4aab35 --- /dev/null +++ b/talc-lang/tests/parser.rs @@ -0,0 +1,76 @@ +use talc_lang::parser::{Lexer, TokenKind}; + +use TokenKind as T; + +fn assert_tokens(src: &str, tokens: &[(TokenKind, &str)]) { + let lexer = Lexer::new(src); + for (i, tok) in lexer.enumerate() { + let tok = if i >= tokens.len() { + tok.expect("end of tokens") + } else { + tok.expect(&format!("token {} {}", tokens[i].0, tokens[i].1)) + }; + assert_eq!(tok.kind, tokens[i].0, "token kind did not match"); + assert_eq!(tok.content, tokens[i].1, "token content did not match"); + if tok.kind == TokenKind::Eof { + break + } + } +} + +fn assert_error(src: &str) { + let lexer = Lexer::new(src); + for tok in lexer { + match tok { + Err(_) => return, + Ok(t) if t.kind == T::Eof => break, + _ => (), + } + } + panic!("expected error in source '{}'", src) +} + +#[test] +fn int_literals() { + let src = "1 100 98_765_432 0x0 0x551 0x_12_34 0xabcABC 0o127 0b01100110 0s012345"; + let tokens = vec![ + (T::Integer, "1"), + (T::Integer, "100"), + (T::Integer, "98_765_432"), + (T::Integer, "0x0"), + (T::Integer, "0x551"), + (T::Integer, "0x_12_34"), + (T::Integer, "0xabcABC"), + (T::Integer, "0o127"), + (T::Integer, "0b01100110"), + (T::Integer, "0s012345"), + (T::Eof, ""), + ]; + assert_tokens(src, &tokens); + + assert_error("0m123"); + assert_error("55p"); + assert_error("0xabcdefg"); + assert_error("0o178"); + assert_error("0s156"); + assert_error("0b012"); +} + +#[test] +fn float_literals() { + let src = "1. 1.0 1e2 1.e2 1.0e2 1e+2 1.e+2 1.0e+2 1e-2 1___2_3_e+_5__"; + let tokens = vec![ + (T::Float, "1."), + (T::Float, "1.0"), + (T::Float, "1e2"), + (T::Float, "1.e2"), + (T::Float, "1.0e2"), + (T::Float, "1e+2"), + (T::Float, "1.e+2"), + (T::Float, "1.0e+2"), + (T::Float, "1e-2"), + (T::Float, "1___2_3_e+_5__"), + (T::Eof, ""), + ]; + assert_tokens(src, &tokens); +} diff --git a/talc-lang/tests/vm.rs b/talc-lang/tests/vm.rs new file mode 100644 index 0000000..b5c1283 --- /dev/null +++ b/talc-lang/tests/vm.rs @@ -0,0 +1,130 @@ +use std::rc::Rc; + +use talc_lang::{compiler::compile, optimize, parser, value::Value, Vm}; + +fn assert_eval(src: &str, value: Value) { + let mut ex = parser::parse(src).expect(&format!("failed to parse expression")); + optimize(&mut ex); + let f = compile(&ex, None).expect("failed to compile expression"); + let f = Rc::new(f); + let mut vm = Vm::new(16, Vec::new()); + let res = vm + .run_function(f.clone(), vec![f.into()]) + .expect("vm produced an exception"); + + assert_eq!( + res.get_type().name(), + value.get_type().name(), + "result and target types differ" + ); + assert_eq!(res, value, "result and target differ"); +} + +#[test] +fn scope() { + assert_eval( + " + var x = 7 + var y = 10 + do + var x = 200 + y = 100 + end + x + y + ", + Value::Int(7 + 100), + ); + assert_eval( + " + var cond = true + var z = 2 + if cond then var z = 5 else var z = 6 end + z + ", + Value::Int(2), + ); + assert_eval( + " + var i = 55 + var j = 66 + for i in 0..10 do + j = i + end + i + j + ", + Value::Int(55 + 9), + ); +} + +#[test] +fn forloop() { + assert_eval( + " + sum = 0 + for i in 0..5 do + for j in 0..10 do + sum += i*j + end + end + sum + ", + Value::Int(45 * 10), + ); + assert_eval( + " + map = {a=3, b=2, c=4, d=7} + prod = 1 + for k in map do + prod *= map[k] + end + prod + ", + Value::Int(3 * 2 * 4 * 7), + ); +} + +#[test] +fn closures() { + assert_eval( + " + var x = 2 + next = \\-> do x = x * 2 + 1 end + next() + next() + next() + ", + Value::Int(5 + 11 + 23), + ); + assert_eval( + " + var x = 0 + fn outer(n) do + fn inner() do + x += n + n += 1 + x + end + end + + var f = outer(2) + var g = outer(6) + f() + f() + g() + g() + f() + ", + Value::Int(2 + 5 + 11 + 18 + 22), + ); +} + +#[test] +fn tailcall() { + assert_eval( + " + fn test(n, a) do + if n <= 0 then + a + else + self(n-1, n+a) + end + end + test(24, 0) + ", + Value::Int(300), + ) // = 24 + 23 + ... + 1 +} diff --git a/talc-macros/Cargo.toml b/talc-macros/Cargo.toml index 82ba62c..7f65a69 100644 --- a/talc-macros/Cargo.toml +++ b/talc-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "talc-macros" -version = "0.2.0" +version = "0.2.1" edition = "2021" [lib] diff --git a/talc-std/Cargo.toml b/talc-std/Cargo.toml index f4be652..881e8a1 100644 --- a/talc-std/Cargo.toml +++ b/talc-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "talc-std" -version = "0.2.0" +version = "0.2.1" edition = "2021" [dependencies] diff --git a/talc-std/src/exception.rs b/talc-std/src/exception.rs index 365801a..98ea0f7 100644 --- a/talc-std/src/exception.rs +++ b/talc-std/src/exception.rs @@ -15,19 +15,13 @@ pub fn throw(_: &mut Vm, args: Vec) -> Result { Value::Symbol(ty) => Exception::new(ty), Value::List(l) => match l.borrow().as_slice() { [Value::Symbol(ty)] | [Value::Symbol(ty), Value::Nil] => Exception::new(*ty), - [Value::Symbol(ty), Value::Nil, data] => { - Exception::new_with_data(*ty, data.clone()) - } [Value::Symbol(ty), Value::String(s)] => { Exception::new_with_msg(*ty, s.clone()) } - [Value::Symbol(ty), Value::String(s), data] => { - Exception::new_with_msg_data(*ty, s.clone(), data.clone()) - } - [] | [_] | [_, _] | [_, _, _] => { + [] | [_] | [_, _] => { throw!(*SYM_TYPE_ERROR, "wrong argument for throw") } - [_, _, _, _, ..] => throw!( + [_, _, _, ..] => throw!( *SYM_VALUE_ERROR, "too many elements in list argument for throw" ), diff --git a/talc-std/src/file.rs b/talc-std/src/file.rs index 0a11ca9..ee3ba62 100644 --- a/talc-std/src/file.rs +++ b/talc-std/src/file.rs @@ -451,7 +451,7 @@ pub fn tcp_connect(_: &mut Vm, args: Vec) -> Result { } } -#[native_func(1)] +#[native_func(2)] pub fn tcp_connect_timeout(_: &mut Vm, args: Vec) -> Result { let [_, addr, timeout] = unpack_args!(args); let Value::String(addr) = addr else { diff --git a/talc-std/src/io.rs b/talc-std/src/io.rs index 5876930..8a047ae 100644 --- a/talc-std/src/io.rs +++ b/talc-std/src/io.rs @@ -45,6 +45,33 @@ pub fn println(_: &mut Vm, args: Vec) -> Result { Ok(Value::Nil) } +#[native_func(1)] +pub fn eprint(_: &mut Vm, args: Vec) -> Result { + let res = match &args[1] { + Value::String(s) => std::io::stderr().write_all(s.as_bytes()), + v => write!(std::io::stderr(), "{v}"), + }; + if let Err(e) = res { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) +} + +#[native_func(1)] +pub fn eprintln(_: &mut Vm, args: Vec) -> Result { + let res = match &args[1] { + Value::String(s) => std::io::stderr().write_all(s.as_bytes()), + v => write!(std::io::stderr(), "{v}"), + }; + if let Err(e) = res { + throw!(*SYM_IO_ERROR, "{e}") + } + if let Err(e) = std::io::stderr().write_all(b"\n") { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) +} + #[native_func(0)] pub fn readln(_: &mut Vm, _: Vec) -> Result { let mut buf = Vec::new(); @@ -154,9 +181,46 @@ pub fn delenv(_: &mut Vm, args: Vec) -> Result { Ok(Value::Nil) } +#[native_func(1)] +pub fn arg(vm: &mut Vm, args: Vec) -> Result { + let [_, n] = unpack_args!(args); + let Value::Int(n) = n else { + throw!(*SYM_TYPE_ERROR, "arg expected integer, got {n:#}") + }; + let cmd_args = vm.args(); + if n < 0 || (n as usize) >= cmd_args.len() { + throw!( + *SYM_VALUE_ERROR, + "arg number {} out of range for {} arguments", + n, + cmd_args.len() + ) + } + Ok(Value::from(cmd_args[n as usize].clone())) +} + +#[native_func(0)] +pub fn argc(vm: &mut Vm, args: Vec) -> Result { + let [_] = unpack_args!(args); + Ok(Value::from(vm.args().len() as i64)) +} + +#[native_func(0)] +pub fn args(vm: &mut Vm, args: Vec) -> Result { + let [_] = unpack_args!(args); + let cmd_args: Vec = vm + .args() + .iter() + .map(|v| Value::from(v.to_owned())) + .collect(); + Ok(Value::from(cmd_args)) +} + pub fn load(vm: &mut Vm) { vm.set_global_name("print", print().into()); vm.set_global_name("println", println().into()); + vm.set_global_name("eprint", eprint().into()); + vm.set_global_name("eprintln", eprintln().into()); vm.set_global_name("readln", readln().into()); vm.set_global_name("time", time().into()); @@ -166,4 +230,8 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("env", env().into()); vm.set_global_name("setenv", setenv().into()); vm.set_global_name("delenv", delenv().into()); + + vm.set_global_name("arg", arg().into()); + vm.set_global_name("argc", argc().into()); + vm.set_global_name("args", args().into()); } diff --git a/talc-std/src/value.rs b/talc-std/src/value.rs index 5102e65..e509b78 100644 --- a/talc-std/src/value.rs +++ b/talc-std/src/value.rs @@ -317,14 +317,19 @@ pub fn compile(_: &mut Vm, args: Vec) -> Result { "compile: argument must be a string, found {src:#}" ) }; + let src = src.to_str().map_err(|e| { exception!( *SYM_VALUE_ERROR, "compile: argument must be valid unicode ({e})" ) })?; + let ast = talc_lang::parser::parse(src) .map_err(|e| exception!(symbol!("parse_error"), "{e}"))?; - let func = talc_lang::compiler::compile(&ast, None); + + let func = talc_lang::compiler::compile(&ast, None) + .map_err(|e| exception!(symbol!("compile_error"), "{e}"))?; + Ok(func.into()) }