From 9996ca1574b27e9ea7331c00bbb451ddfbe87144 Mon Sep 17 00:00:00 2001 From: trimill Date: Tue, 27 Feb 2024 00:18:43 -0500 Subject: [PATCH] improvements and such --- Cargo.lock | 53 +- talc-bin/src/helper.rs | 185 +++++++ talc-bin/src/main.rs | 165 ++---- talc-bin/src/repl.rs | 146 ++++++ talc-lang/src/ast.rs | 2 +- talc-lang/src/chunk.rs | 4 +- talc-lang/src/compiler.rs | 119 ++--- talc-lang/src/gc.rs | 0 talc-lang/src/lib.rs | 3 +- talc-lang/src/parser.lalrpop | 30 +- talc-lang/src/symbol.rs | 46 +- talc-lang/src/value/exception.rs | 50 +- talc-lang/src/value/function.rs | 126 +++-- talc-lang/src/value/index.rs | 125 +++++ talc-lang/src/value/mod.rs | 184 ++++--- talc-lang/src/value/ops.rs | 165 +++--- talc-lang/src/value/range.rs | 1 + talc-lang/src/vm.rs | 630 ++++++++++++----------- talc-macros/src/lib.rs | 66 ++- talc-std/Cargo.toml | 5 + talc-std/src/collection.rs | 208 ++++++++ talc-std/src/exception.rs | 47 +- talc-std/src/io.rs | 71 ++- talc-std/src/iter.rs | 849 +++++++++++++++++++++++++++++-- talc-std/src/lib.rs | 42 +- talc-std/src/num.rs | 676 +++++++++++++++++++++++- talc-std/src/random.rs | 50 ++ talc-std/src/value.rs | 170 +++++++ 28 files changed, 3384 insertions(+), 834 deletions(-) create mode 100644 talc-bin/src/helper.rs create mode 100644 talc-bin/src/repl.rs delete mode 100644 talc-lang/src/gc.rs create mode 100644 talc-lang/src/value/index.rs create mode 100644 talc-std/src/collection.rs create mode 100644 talc-std/src/random.rs create mode 100644 talc-std/src/value.rs diff --git a/Cargo.lock b/Cargo.lock index e2e573d..d6720d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,7 +138,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" +checksum = "26a147e1a6641a55d994b3e4e9fa4d9b180c8d652c09b363af8c9bf1b8e04139" [[package]] name = "fd-lock" @@ -290,9 +290,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "home" @@ -527,6 +527,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -561,6 +567,36 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -707,9 +743,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", @@ -752,6 +788,7 @@ name = "talc-std" version = "0.1.0" dependencies = [ "lazy_static", + "rand", "talc-lang", "talc-macros", ] @@ -784,7 +821,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] diff --git a/talc-bin/src/helper.rs b/talc-bin/src/helper.rs new file mode 100644 index 0000000..6afcff6 --- /dev/null +++ b/talc-bin/src/helper.rs @@ -0,0 +1,185 @@ +use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc}; + +use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result}; +use talc_lang::{Lexer, Vm}; + +#[derive(Clone, Copy)] +enum TokenType { + String, Symbol, Number, Literal +} + +pub struct TalcHelper { + vm: Rc>, + lex: Lexer, + token_types: HashMap, +} + +macro_rules! load_tokens { + ($token_types:expr, $lex:expr, {$($($tok:literal)|+ => $ty:expr,)*}) => {{ + $($( + $token_types.insert($lex.lex($tok).next().unwrap().unwrap().1.0, $ty); + )*)* + }}; +} + +impl TalcHelper { + pub fn new(vm: Rc>) -> Self { + let lex = Lexer::new(); + let mut token_types = HashMap::new(); + load_tokens!(token_types, lex, { + "\"\"" | "''" => TokenType::String, + ":a" | ":''" | ":\"\"" => TokenType::Symbol, + "0" | "0.0" | "0x0" | "0b0" | "0o0" | "0s0" => TokenType::Number, + "true" | "false" | "nil" => TokenType::Literal, + }); + Self { + vm, + lex, + token_types, + } + } +} + +impl Helper for TalcHelper {} + +impl Completer for TalcHelper { + type Candidate = String; + + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &rustyline::Context<'_>, + ) -> Result<(usize, Vec)> { + let mut res = String::new(); + for ch in line[..pos].chars().rev() { + if matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '_') { + res.push(ch); + } else { + break + } + } + let res: String = res.chars().rev().collect(); + let mut keys = self.vm.borrow().globals().keys() + .map(|sym| sym.name()) + .filter(|name| name.starts_with(&res)) + .map(|s| s.to_string()) + .collect::>(); + keys.sort(); + Ok((pos - res.as_bytes().len(), keys)) + } +} + +impl Hinter for TalcHelper { + type Hint = String; +} + +impl Highlighter for TalcHelper { + fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { + let mut tokens = self.lex.lex(line).peekable(); + let mut buf = String::new(); + let mut last = 0; + while let Some(Ok((l, tok, r))) = tokens.next() { + buf += &line[last..l]; + last = r; + let tokty = self.token_types.get(&tok.0); + buf += match tokty { + Some(TokenType::Literal) => "\x1b[93m", + Some(TokenType::Number) => "\x1b[93m", + Some(TokenType::String) => "\x1b[92m", + Some(TokenType::Symbol) => "\x1b[96m", + None => "", + }; + buf += tok.1; + if tokty.is_some() { buf += "\x1b[0m" } + } + buf += &line[last..]; + Cow::Owned(buf) + } + + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + _default: bool, + ) -> Cow<'b, str> { + Cow::Owned(format!("\x1b[94m{prompt}\x1b[0m")) + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Cow::Owned(format!("\x1b[37m{}\x1b[0m", hint)) + } + + fn highlight_char(&self, line: &str, _: usize, forced: bool) -> bool { + forced || !line.is_empty() + } +} + +impl Validator for TalcHelper { + fn validate(&self, ctx: &mut ValidationContext) -> Result { + let tokens = self.lex.lex(ctx.input()); + let mut delims = Vec::new(); + let mut mismatch = None; + for token in tokens { + let token = match token { + Ok(t) => t, + Err(e) => return Ok(ValidationResult::Invalid( + Some(e.to_string()))), + }; + let t = token.1.1; + match t { + "(" | "{" | "[" | "if" | "while" | "for" | "try" + => delims.push(token.1.1), + ")" => match delims.pop() { + Some("(") => (), + v => { mismatch = Some((v, t)); break } + }, + "}" => match delims.pop() { + Some("{") => (), + v => { mismatch = Some((v, t)); break } + }, + "]" => match delims.pop() { + Some("[") => (), + v => { mismatch = Some((v, t)); break } + }, + "then" => match delims.pop() { + Some("if") => delims.push(t), + v => { mismatch = Some((v, t)); break } + } + "catch" => match delims.pop() { + Some("try") => delims.push(t), + v => { mismatch = Some((v, t)); break } + } + "do" => match delims.last().copied() { + Some("while") | Some("for") | Some("catch") => { + delims.pop(); + delims.push(t); + }, + _ => delims.push(t) + }, + "elif" | "else" => match delims.pop() { + Some("then") => delims.push(t), + v => { mismatch = Some((v, t)); break } + }, + "end" => match delims.pop() { + Some("then" | "elif" | "else" | "do" | "try") => (), + v => { mismatch = Some((v, t)); break } + }, + _ => (), + } + } + match mismatch { + Some((None, b)) => return Ok(ValidationResult::Invalid(Some( + format!(" found unmatched {b}")))), + Some((Some(a), b)) => return Ok(ValidationResult::Invalid(Some( + format!(" found {a} matched with {b}")))), + _ => (), + } + + if delims.is_empty() { + Ok(ValidationResult::Valid(None)) + } else { + Ok(ValidationResult::Incomplete) + } + + } +} diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs index 04696e2..1e53a9f 100644 --- a/talc-bin/src/main.rs +++ b/talc-bin/src/main.rs @@ -1,136 +1,71 @@ -use clap::Parser; -use rustyline::error::ReadlineError; -use talc_lang::{compiler::{compile, compile_repl}, value::{function::disasm_recursive, Value}, symbol::Symbol, Vm}; -use std::{rc::Rc, path::PathBuf, process::ExitCode}; +use clap::{ColorChoice, Parser}; +use talc_lang::{compiler::compile, value::function::disasm_recursive, Vm}; +use std::{path::PathBuf, process::ExitCode, rc::Rc}; + +mod repl; +mod helper; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { - file: Option, + /// file to run + file: Option, - #[arg(short, long)] - repl: bool, + /// start the repl + #[arg(short, long)] + repl: bool, - #[arg(short, long)] - disasm: bool, + /// show disassembled bytecode + #[arg(short, long)] + disasm: bool, + + /// enable or disable color + #[arg(short, long, default_value="auto")] + color: ColorChoice, } -fn exec(src: String, _args: &Args) -> ExitCode { +fn exec(src: String, args: &Args) -> ExitCode { let parser = talc_lang::Parser::new(); let mut vm = Vm::new(256); - talc_std::load_all(&mut vm); + talc_std::load_all(&mut vm); - let ex = match parser.parse(&src) { - Ok(ex) => ex, - Err(e) => { - eprintln!("Error: {e}"); - return ExitCode::FAILURE - }, - }; + let ex = match parser.parse(&src) { + Ok(ex) => ex, + Err(e) => { + eprintln!("Error: {e}"); + return ExitCode::FAILURE + }, + }; - let func = Rc::new(compile(&ex)); + let func = Rc::new(compile(&ex)); - if let Err(e) = vm.call_no_args(func.clone()) { - eprintln!("{e}"); - ExitCode::FAILURE - } else { - ExitCode::SUCCESS - } -} - -fn repl(args: &Args) -> ExitCode { - if args.disasm { - eprintln!("input disassembly enabled"); - } - - let parser = talc_lang::Parser::new(); - let mut compiler_globals = Vec::new(); - let mut vm = Vm::new(256); - talc_std::load_all(&mut vm); - - let interrupt = vm.get_interrupt(); - let ctrlc_res = ctrlc::set_handler(move || { - interrupt.fetch_or(true, std::sync::atomic::Ordering::Relaxed); - }); - if let Err(e) = ctrlc_res { - 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 mut rl = match rustyline::DefaultEditor::new() { - Ok(rl) => rl, - Err(ReadlineError::Io(e)) => { - eprintln!("Error: {e}"); - return ExitCode::FAILURE; - }, - Err(ReadlineError::Errno(e)) => { - eprintln!("Error: {e}"); - return ExitCode::FAILURE; - }, - Err(_) => return ExitCode::SUCCESS, - }; - - loop { - let line = rl.readline(">> "); - let line = match line { - Ok(line) => line, - Err(ReadlineError::Eof) => return ExitCode::SUCCESS, - Err(ReadlineError::Interrupted) => continue, - Err(e) => { - eprintln!("Error: {e}"); - continue - }, - }; - - let ex = match parser.parse(&line) { - Ok(ex) => ex, - Err(e) => { eprintln!("Error: {e}"); 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}"); - return ExitCode::FAILURE - } - } - - match vm.call_no_args(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:#}"); - } - } - Err(e) => eprintln!("{e}"), + if args.disasm { + if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) { + eprintln!("Error: {e}"); + return ExitCode::FAILURE } } + + if let Err(e) = vm.run_function(func.clone(), vec![func.into()]) { + eprintln!("{e}"); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } } fn main() -> ExitCode { - let args = Args::parse(); + let args = Args::parse(); - if args.repl || args.file.is_none() { - return repl(&args) - } + if args.repl || args.file.is_none() { + return repl::repl(&args) + } - match std::fs::read_to_string(args.file.as_ref().unwrap()) { - Ok(s) => exec(s, &args), - Err(e) => { - eprintln!("Error: {e}"); - ExitCode::FAILURE - } - } + match std::fs::read_to_string(args.file.as_ref().unwrap()) { + Ok(s) => exec(s, &args), + Err(e) => { + eprintln!("Error: {e}"); + ExitCode::FAILURE + } + } } diff --git a/talc-bin/src/repl.rs b/talc-bin/src/repl.rs new file mode 100644 index 0000000..de2c6ce --- /dev/null +++ b/talc-bin/src/repl.rs @@ -0,0 +1,146 @@ +use std::{cell::RefCell, io::IsTerminal, process::ExitCode, rc::Rc}; + +use clap::ColorChoice; +use rustyline::{error::ReadlineError, history::MemHistory, ColorMode, Config, Editor}; +use talc_lang::{compiler::compile_repl, symbol::Symbol, value::{function::disasm_recursive, Value}, Vm}; + +use crate::{helper::TalcHelper, Args}; + +#[derive(Clone, Copy, Default)] +pub struct ReplColors { + pub reset: &'static str, + pub error: &'static str, +} + +impl ReplColors { + fn new(colors: ColorChoice) -> Self { + let colors = match colors { + ColorChoice::Always => true, + ColorChoice::Never => false, + ColorChoice::Auto => std::io::stdout().is_terminal(), + }; + if !colors { + return Self::default() + } + + Self { + reset: "\x1b[0m", + error: "\x1b[91m", + } + } +} + + +fn get_colmode(args: &Args) -> ColorMode { + match args.color { + ColorChoice::Auto => ColorMode::Enabled, + ColorChoice::Always => ColorMode::Forced, + ColorChoice::Never => ColorMode::Disabled, + } +} + +pub fn init_rustyline(args: &Args) -> Result, ExitCode> { + let config = Config::builder() + .auto_add_history(true) + .color_mode(get_colmode(args)) + .check_cursor_position(true) + .completion_type(rustyline::CompletionType::List) + .build(); + match rustyline::Editor::with_history(config, MemHistory::default()) { + Ok(rl) => Ok(rl), + Err(ReadlineError::Io(e)) => { + eprintln!("Error: {e}"); + Err(ExitCode::FAILURE) + }, + Err(ReadlineError::Errno(e)) => { + eprintln!("Error: {e}"); + Err(ExitCode::FAILURE) + }, + Err(_) => Err(ExitCode::SUCCESS) + } +} + +pub fn repl(args: &Args) -> ExitCode { + if args.disasm { + eprintln!("input disassembly enabled"); + } + + let parser = talc_lang::Parser::new(); + let mut compiler_globals = Vec::new(); + let mut vm = Vm::new(256); + talc_std::load_all(&mut vm); + + let interrupt = vm.get_interrupt(); + let ctrlc_res = ctrlc::set_handler(move || { + interrupt.fetch_or(true, std::sync::atomic::Ordering::Relaxed); + }); + if let Err(e) = ctrlc_res { + 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); + + let mut rl = match init_rustyline(args) { + Ok(rl) => rl, + Err(e) => return e, + }; + + let vm = Rc::new(RefCell::new(vm)); + + rl.set_helper(Some(TalcHelper::new(vm.clone()))); + + loop { + let line = rl.readline(">> "); + let line = match line { + Ok(line) => line, + Err(ReadlineError::Eof) => return ExitCode::SUCCESS, + Err(ReadlineError::Interrupted) => continue, + Err(e) => { + eprintln!("{}Error:{} {e}", c.error, c.reset); + continue + }, + }; + + 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), + } + } +} + diff --git a/talc-lang/src/ast.rs b/talc-lang/src/ast.rs index c2b6297..98e8039 100644 --- a/talc-lang/src/ast.rs +++ b/talc-lang/src/ast.rs @@ -33,7 +33,7 @@ pub enum Expr<'s> { List(Vec>), Table(Vec<(Expr<'s>, Expr<'s>)>), - Return(Box>), + Return(Box>), And(Box>, Box>), Or(Box>, Box>), If(Box>, Box>, Option>>), diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs index 2c9f1bf..d16581f 100644 --- a/talc-lang/src/chunk.rs +++ b/talc-lang/src/chunk.rs @@ -117,7 +117,7 @@ pub enum Instruction { JumpTrue(Arg24), JumpFalse(Arg24), - IterBegin, IterNext(Arg24), + IterBegin, IterTest(Arg24), BeginTry(Arg24), EndTry, @@ -159,7 +159,7 @@ impl std::fmt::Display for Instruction { Self::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)), Self::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)), Self::IterBegin => write!(f, "iterbegin"), - Self::IterNext(a) => write!(f, "iternext {}", usize::from(a)), + 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}"), diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index d55ddb0..6537333 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::ast::{BinaryOp, Expr, LValue, CatchBlock}; use crate::chunk::{Instruction as I, Chunk, Arg24, Catch}; use crate::symbol::Symbol; -use crate::value::function::Function; +use crate::value::function::{FuncAttrs, Function}; use crate::value::Value; #[derive(Debug, Clone)] @@ -20,69 +20,67 @@ enum CompilerMode { struct Compiler<'a> { mode: CompilerMode, parent: Option<&'a Compiler<'a>>, - func: Function, + chunk: Chunk, + attrs: FuncAttrs, scope: usize, locals: Vec, globals: Vec, } pub fn compile(expr: &Expr) -> Function { - let mut comp = Compiler::new_module(None); + let mut comp = Compiler::new_module(None); comp.expr(expr); - comp.finish() + comp.finish() } pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec) { - let mut comp = Compiler::new_repl(globals); + let mut comp = Compiler::new_repl(globals); comp.expr(expr); - comp.finish_repl() + comp.finish_repl() } impl<'a> Default for Compiler<'a> { - fn default() -> Self { - let locals = vec![Local { - name: "self".into(), - scope: 0, - }]; - Self { - mode: CompilerMode::Function, - parent: None, - func: Function { arity: 0, chunk: Chunk::new() }, - scope: 0, - locals, - globals: Vec::new(), - } - } + fn default() -> Self { + let locals = vec![Local { + name: "self".into(), + scope: 0, + }]; + Self { + mode: CompilerMode::Function, + parent: None, + chunk: Chunk::new(), + attrs: FuncAttrs { arity: 0, variadic: false }, + scope: 0, + locals, + globals: Vec::new(), + } + } } impl<'a> Compiler<'a> { fn new_repl(globals: &[Local]) -> Self { - Self { - mode: CompilerMode::Repl, - globals: globals.to_vec(), - ..Self::default() - } - } + Self { + mode: CompilerMode::Repl, + globals: globals.to_vec(), + ..Self::default() + } + } fn new_module(parent: Option<&'a Self>) -> Self { - Self { - mode: CompilerMode::Module, - parent, - ..Self::default() - } - } + Self { + mode: CompilerMode::Module, + parent, + ..Self::default() + } + } fn new_function(&'a self, args: &[&str]) -> Self { - let func = Function { - arity: args.len(), - chunk: Chunk::new(), - }; - let mut new = Self { + let mut new = Self { mode: CompilerMode::Function, parent: Some(self), - func, - ..Self::default() - }; + ..Self::default() + }; + new.attrs.arity = args.len(); for arg in args { new.locals.push(Local { @@ -96,12 +94,15 @@ impl<'a> Compiler<'a> { pub fn finish(mut self) -> Function { self.emit(I::Return); - self.func + Function { chunk: Rc::new(self.chunk), attrs: self.attrs } } pub fn finish_repl(mut self) -> (Function, Vec) { self.emit(I::Return); - (self.func, self.globals) + ( + Function { chunk: Rc::new(self.chunk), attrs: self.attrs }, + self.globals + ) } // @@ -109,16 +110,16 @@ impl<'a> Compiler<'a> { // fn add_const(&mut self, val: Value) -> usize { - self.func.chunk.add_const(val) + self.chunk.add_const(val) } fn emit(&mut self, instr: I) -> usize { - self.func.chunk.add_instr(instr) + self.chunk.add_instr(instr) } fn emit_discard(&mut self, mut n: usize) { while n > 0 { - let instrs = &mut self.func.chunk.instrs; + let instrs = &mut self.chunk.instrs; // dup followed by store: remove the dup if instrs.len() >= 2 @@ -128,9 +129,9 @@ impl<'a> Compiler<'a> { )) { // can't panic: checked that instrs.len() >= 2 - let i = self.func.chunk.instrs.pop().unwrap(); - self.func.chunk.instrs.pop().unwrap(); - self.func.chunk.instrs.push(i); + let i = self.chunk.instrs.pop().unwrap(); + self.chunk.instrs.pop().unwrap(); + self.chunk.instrs.push(i); n -= 1; continue; } @@ -160,11 +161,11 @@ impl<'a> Compiler<'a> { } fn ip(&self) -> usize { - self.func.chunk.instrs.len() + self.chunk.instrs.len() } fn update_instr(&mut self, n: usize, new: I) { - self.func.chunk.instrs[n] = new; + self.chunk.instrs[n] = new; } fn begin_scope(&mut self) { @@ -368,10 +369,10 @@ impl<'a> Compiler<'a> { } self.emit(I::Call(args.len() as u8)); }, - Expr::Return(e) => { - self.expr(e); - self.emit(I::Return); - }, + Expr::Return(e) => { + self.expr(e); + self.emit(I::Return); + }, Expr::Pipe(a, f) => { self.expr(a); self.expr(f); @@ -428,7 +429,7 @@ impl<'a> Compiler<'a> { } fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) { - let (idx, mut table) = self.func.chunk.begin_try_table(self.locals.len()); + let (idx, mut table) = self.chunk.begin_try_table(self.locals.len()); self.emit(I::BeginTry(Arg24::from_usize(idx))); self.expr(body); @@ -463,7 +464,7 @@ impl<'a> Compiler<'a> { self.update_instr(addr, I::Jump(ip)); } - self.func.chunk.finish_catch_table(idx, table); + self.chunk.finish_catch_table(idx, table); } fn expr_for(&mut self, name: &str, iter: &Expr, body: &Expr) { @@ -480,7 +481,9 @@ impl<'a> Compiler<'a> { let start = self.ip(); // call iterator and jump if nil, otherwise store - let j1 = self.emit(I::IterNext(Arg24::from_usize(0))); + self.emit(I::Dup); + self.emit(I::Call(0)); + let j1 = self.emit(I::IterTest(Arg24::from_usize(0))); self.store_local(local); // body @@ -490,7 +493,7 @@ impl<'a> Compiler<'a> { // end loop self.emit(I::Jump(Arg24::from_usize(start))); - self.update_instr(j1, I::IterNext(Arg24::from_usize(self.ip()))); + self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip()))); self.end_scope(); self.emit(I::Nil); } diff --git a/talc-lang/src/gc.rs b/talc-lang/src/gc.rs deleted file mode 100644 index e69de29..0000000 diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs index 8fda026..5af8d77 100644 --- a/talc-lang/src/lib.rs +++ b/talc-lang/src/lib.rs @@ -15,13 +15,14 @@ pub mod symbol; pub mod ast; pub mod value; -pub mod gc; pub mod chunk; pub mod compiler; pub use parser::BlockParser as Parser; pub use vm::Vm; +pub use parser_util::{parse_int, parse_float, parse_str_escapes}; + type LexResult<'input> = Result< (usize, Token<'input>, usize), ParseError, parser_util::ParseError> diff --git a/talc-lang/src/parser.lalrpop b/talc-lang/src/parser.lalrpop index 98bdfa0..d59c7c0 100644 --- a/talc-lang/src/parser.lalrpop +++ b/talc-lang/src/parser.lalrpop @@ -26,7 +26,6 @@ match { "false", "nil", // kw variables - "const", "global", "var", // kw logic @@ -117,9 +116,8 @@ AssignOp: Option = { "&=" => Some(BinaryOp::Append), } - // -// operations +// logical ops // // or @@ -140,12 +138,29 @@ UnaryNot: Box> = { Pipe, } -// | +// +// pipe +// + Pipe: Box> = { - "|" => Box::new(Expr::Pipe(l, r)), + "|" => Box::new(Expr::Pipe(l, r)), + Lambda, +} + +// +// lambda +// + +Lambda: Box> = { + "\\" "->" => Box::new(Expr::Lambda(xs, e)), + ":" => Box::new(Expr::Lambda(vec!["$"], e)), + "::" => Box::new(Expr::Lambda(vec!["$", "$$"], e)), BinaryCompare, } +// +// operations +// // == != > < >= <= BinaryCompare: Box> = { @@ -250,7 +265,7 @@ UnaryMinus2: Box> = { // function call FunctionCall: Box> = { - "(" ")" => Box::new(Expr::FnCall(l, r)), + "(" ")" => Box::new(Expr::FnCall(l, r)), FieldAccess, } @@ -277,8 +292,7 @@ TermNotIdent: Box> = { "[" "]" => Box::new(Expr::List(<>)), "{" "}" => Box::new(Expr::Table(<>)), "$" => Box::new(Expr::Ident("$")), - ":" "(" ")" => Box::new(Expr::Lambda(vec!["$"], e)), - "\\" "->" => Box::new(Expr::Lambda(xs, e)), + "$$" => Box::new(Expr::Ident("$$")), "do" "end" => <>, "if" => <>, diff --git a/talc-lang/src/symbol.rs b/talc-lang/src/symbol.rs index c3a3d72..f47718f 100644 --- a/talc-lang/src/symbol.rs +++ b/talc-lang/src/symbol.rs @@ -9,17 +9,31 @@ struct SymbolTable { } lazy_static! { - 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_NIL: Symbol = symbol!(nil); + pub static ref SYM_BOOL: Symbol = symbol!(bool); + pub static ref SYM_SYMBOL: Symbol = symbol!(symbol); + pub static ref SYM_INT: Symbol = symbol!(int); + pub static ref SYM_RATIO: Symbol = symbol!(ratio); + pub static ref SYM_FLOAT: Symbol = symbol!(float); + pub static ref SYM_COMPLEX: Symbol = symbol!(complex); + pub static ref SYM_RANGE: Symbol = symbol!(range); + pub static ref SYM_CELL: Symbol = symbol!(cell); + pub static ref SYM_STRING: Symbol = symbol!(string); + pub static ref SYM_LIST: Symbol = symbol!(list); + pub static ref SYM_TABLE: Symbol = symbol!(table); + pub static ref SYM_FUNCTION: Symbol = symbol!(function); + pub static ref SYM_NATIVE_FUNC: Symbol = symbol!(native_func); - pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error); - pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error); - pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error); - pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error); - pub static ref SYM_CALL_STACK_OVERFLOW: Symbol = symbol!(call_stack_overflow); - pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted); - pub static ref SYM_STOP_ITERATOR: Symbol = symbol!(stop_iterator); + 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_NAME_ERROR: Symbol = symbol!(name_error); + pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error); + pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error); + pub static ref SYM_CALL_STACK_OVERFLOW: Symbol = symbol!(call_stack_overflow); + pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted); } static TABLE: OnceLock> = OnceLock::new(); @@ -93,12 +107,12 @@ impl Symbol { #[macro_export] macro_rules! symbol { - ($sym:ident) => { - $crate::symbol::Symbol::get(stringify!($sym)) - }; - ($sym:literal) => { - $crate::symbol::Symbol::get($sym) - }; + ($sym:ident) => { + $crate::symbol::Symbol::get(stringify!($sym)) + }; + ($sym:literal) => { + $crate::symbol::Symbol::get($sym) + }; } pub use symbol; diff --git a/talc-lang/src/value/exception.rs b/talc-lang/src/value/exception.rs index 6e0b5e9..37e08e7 100644 --- a/talc-lang/src/value/exception.rs +++ b/talc-lang/src/value/exception.rs @@ -20,25 +20,33 @@ impl Exception { 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) } + } + pub fn from_table(table: &Rc>>) -> Option { - let table = table.borrow(); + 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, - }), - Some(Value::String(msg)) => Some(Self { - ty: *ty, - data, - msg: Some(msg.clone()), - }), - Some(_) => None, - } + let msg = table.get(&(*SYM_MSG).into()); + match msg { + None => Some(Self { + ty: *ty, + data, + msg: None, + }), + Some(Value::String(msg)) => Some(Self { + ty: *ty, + data, + msg: Some(msg.clone()), + }), + Some(_) => None, + } } else { None } @@ -59,11 +67,11 @@ impl Exception { impl Display for Exception { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(msg) = &self.msg { - write!(f, "{}: {}", self.ty.name(), msg) - } else { - write!(f, "{}", self.ty.name()) - } + if let Some(msg) = &self.msg { + write!(f, "{}: {}", self.ty.name(), msg) + } else { + write!(f, "{}", self.ty.name()) + } } } @@ -88,7 +96,7 @@ pub use exception; #[macro_export] macro_rules! throw { ($($args:tt)*) => { - return Err($crate::value::exception::exception!($($args)*)) + return Err($crate::value::exception::exception!($($args)*)) }; } diff --git a/talc-lang/src/value/function.rs b/talc-lang/src/value/function.rs index 36ff31f..f3bd7ae 100644 --- a/talc-lang/src/value/function.rs +++ b/talc-lang/src/value/function.rs @@ -4,69 +4,97 @@ use crate::{chunk::Chunk, Vm}; use super::{Value, exception::Result}; -#[derive(Debug, Default)] -pub struct Function { +#[derive(Clone, Copy, Debug, Default)] +pub struct FuncAttrs { pub arity: usize, - pub chunk: Chunk, + pub variadic: bool, } +#[derive(Debug, Default)] +pub struct Function { + pub attrs: FuncAttrs, + pub chunk: Rc, +} + +impl Function { + pub fn new(chunk: Rc, arity: usize) -> Self { + Self { chunk, attrs: FuncAttrs { arity, variadic: false } } + } + pub fn new_variadic(chunk: Rc, arity: usize) -> Self { + Self { chunk, attrs: FuncAttrs { arity, variadic: true } } + } +} + +type FnNative = Box) -> Result>; + pub struct NativeFunc { - pub arity: usize, - #[allow(clippy::type_complexity)] - pub f: Box) -> Result>, + pub attrs: FuncAttrs, + pub func: FnNative, +} + +impl NativeFunc { + pub fn new(func: FnNative, arity: usize) -> Self { + Self { func, attrs: FuncAttrs { arity, variadic: false } } + } + pub fn new_variadic(func: FnNative, arity: usize) -> Self { + Self { func, attrs: FuncAttrs { arity, variadic: true } } + } } impl std::fmt::Debug for NativeFunc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NativeFunc") - .field("arity", &self.arity) + .field("attrs", &self.attrs) .finish_non_exhaustive() } } pub fn disasm_recursive(f: &Rc, w: &mut impl std::io::Write) -> std::io::Result<()> { - writeln!(w, "{} (argc={})", Value::Function(f.clone()), f.arity)?; - if !f.chunk.consts.is_empty() { - writeln!(w, "constants")?; - for (i, c) in f.chunk.consts.iter().enumerate() { - writeln!(w, " {i:04}: {c}")?; - } - } - if !f.chunk.try_tables.is_empty() { - writeln!(w, "catch tables")?; - for (i, n) in f.chunk.try_tables.iter().enumerate() { - write!(w, " {i:04}: ")?; - if n.catches.is_empty() { - writeln!(w)?; - } - for (i, catch) in n.catches.iter().enumerate() { - if i != 0 { - write!(w, " : ")?; - } - if let Some(types) = &catch.types { - write!(w, "{:04} [", catch.addr)?; - for (i, ty) in types.iter().enumerate() { - if i != 0 { write!(w, ", ")?; } - write!(w, "{}", ty.name())?; - } - writeln!(w, "]")?; - } else { - writeln!(w, "{:04} *", catch.addr)?; - } - } - } - } - writeln!(w, "instructions")?; - for (i, n) in f.chunk.instrs.iter().enumerate() { - writeln!(w, " {i:04}: {n}")?; - } - writeln!(w)?; + writeln!(w, "{} ({}{})", + Value::Function(f.clone()), + f.attrs.arity, + if f.attrs.variadic { ".." } else { "" })?; + 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 { - disasm_recursive(f, w)?; - } - } - Ok(()) + for c in &f.chunk.consts { + if let Value::Function(f) = c { + disasm_recursive(f, w)?; + } + } + Ok(()) } diff --git a/talc-lang/src/value/index.rs b/talc-lang/src/value/index.rs new file mode 100644 index 0000000..6ff51ef --- /dev/null +++ b/talc-lang/src/value/index.rs @@ -0,0 +1,125 @@ +use crate::{symbol::{SYM_INDEX_ERROR, SYM_TYPE_ERROR}, value::{cell_take, function::NativeFunc}, vmcalliter, Vm}; + +use super::{Value, exception::{Result, throw}, range::RangeType}; + +impl Value { + pub fn index(&self, idx: Self) -> Result { + use Value as V; + match (self, idx) { + (V::List(l), V::Int(i)) => { + let l = l.borrow(); + if i >= 0 && (i as usize) < l.len() { + Ok(l[i as usize].clone()) + } else { + throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len()) + } + }, + (V::Range(r), V::Int(i)) => { + if i >= 0 && ( + r.ty == RangeType::Endless + || i < r.stop + || (r.ty == RangeType::Closed && i == r.stop) + ) { + Ok((r.start + i).into()) + } else { + throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}") + } + }, + (V::Table(t), i) if i.hashable() => { + let t = t.borrow(); + let i = i.try_into()?; + Ok(t.get(&i).cloned().unwrap_or(Value::Nil)) + }, + (col, idx) => if let Ok(ii) = idx.clone().to_iter_function() { + let col = col.clone(); + let func = move |vm: &mut Vm, _| { + match vmcalliter!(vm; ii.clone())? { + Some(i) => Ok(col.index(cell_take(i))?.to_cell()), + None => Ok(Value::Nil) + } + }; + Ok(NativeFunc::new(Box::new(func), 0).into()) + } else { + throw!(*SYM_TYPE_ERROR, "cannot index {col:#} with {idx:#}") + } + } + } + + pub fn store_index(&self, vm: &mut Vm, idx: Self, val: Self) -> Result<()> { + use Value as V; + match (self, idx) { + (V::List(l), V::Int(i)) => { + let mut l = l.borrow_mut(); + if i >= 0 && (i as usize) < l.len() { + l[i as usize] = val; + Ok(()) + } else { + throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len()) + } + }, + (V::Table(t), i) if i.hashable() => { + let mut t = t.borrow_mut(); + let i = i.try_into()?; + t.insert(i, val); + Ok(()) + }, + (V::List(t), V::Range(r)) => { + let iter = val.to_iter_function()?; + let mut vals = Vec::new(); + while let Some(v) = vmcalliter!(vm; iter.clone())? { + vals.push(cell_take(v)); + } + let mut tm = t.borrow_mut(); + match r.ty { + RangeType::Open => { + if r.start < 0 || r.start > tm.len() as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for list of length {}", r.stop, tm.len()) + } + if r.stop < 0 || r.stop > tm.len() as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for list of length {}", r.stop, tm.len()) + } + let end = tm.split_off(r.stop as usize); + tm.truncate(r.start as usize); + tm.extend(vals.into_iter().chain(end)) + }, + RangeType::Closed => { + if r.start < 0 || r.start > tm.len() as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for list of length {}", r.stop, tm.len()) + } + if r.stop < 0 || r.stop >= tm.len() as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for list of length {}", r.stop, tm.len()) + } + let end = tm.split_off(r.stop as usize + 1); + tm.truncate(r.start as usize); + tm.extend(vals.into_iter().chain(end)) + }, + RangeType::Endless => { + if r.start < 0 || r.start > tm.len() as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for list of length {}", r.stop, tm.len()) + } + tm.truncate(r.start as usize); + tm.extend(vals) + }, + } + Ok(()) + }, + (col, idx) => if let Ok(ii) = idx.clone().to_iter_function() { + let val = val.to_iter_function()?; + while let Some(i) = vmcalliter!(vm; ii.clone())? { + let Some(v) = vmcalliter!(vm; val.clone())? else { + throw!(*SYM_INDEX_ERROR, "not enough values provided for store index") + }; + col.store_index(vm, cell_take(i), cell_take(v))?; + } + Ok(()) + } else { + throw!(*SYM_TYPE_ERROR, "cannot index {self:#} with {idx:#}") + }, + } + } +} diff --git a/talc-lang/src/value/mod.rs b/talc-lang/src/value/mod.rs index 7a9791e..0a02c5d 100644 --- a/talc-lang/src/value/mod.rs +++ b/talc-lang/src/value/mod.rs @@ -1,9 +1,9 @@ -use std::{rc::Rc, cell::RefCell, collections::HashMap, hash::Hash, fmt::Display}; +use std::{cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, rc::Rc}; -use num_complex::Complex64; -use num_rational::Rational64; +pub use num_complex::Complex64; +pub use num_rational::Rational64; -use crate::symbol::{SYM_HASH_ERROR, Symbol}; +use crate::symbol::{Symbol, SYM_HASH_ERROR}; use self::{range::{Range, RangeType}, function::{Function, NativeFunc}, exception::{Exception, throw}}; @@ -11,12 +11,14 @@ pub mod exception; pub mod function; pub mod ops; pub mod range; +pub mod index; type RcList = Rc>>; type RcTable = Rc>>; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum Value { + #[default] Nil, Bool(bool), Symbol(Symbol), @@ -27,6 +29,7 @@ pub enum Value { Ratio(Rational64), Complex(Complex64), + Cell(Rc>), String(Rc), List(RcList), Table(RcTable), @@ -37,62 +40,82 @@ pub enum Value { impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Value::Nil => write!(f, "nil"), - Value::Bool(b) => write!(f, "{b}"), - Value::Symbol(s) => write!(f, ":{}", s.name()), - Value::Range(r) => match r.ty { + Self::Nil => write!(f, "nil"), + Self::Bool(b) => write!(f, "{b}"), + Self::Symbol(s) => write!(f, ":{}", s.name()), + Self::Range(r) => match r.ty { RangeType::Open => write!(f, "{}..{}", r.start, r.stop), RangeType::Closed => write!(f, "{}..={}", r.start, r.stop), RangeType::Endless => write!(f, "{}..*", r.start), }, - Value::Int(n) => write!(f, "{n}"), - Value::Float(x) => write!(f, "{x:?}"), - Value::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()), - Value::Complex(z) => write!(f, "{z}"), + Self::Int(n) => write!(f, "{n}"), + Self::Float(x) => write!(f, "{x:?}"), + Self::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()), + Self::Complex(z) => write!(f, "{z}"), - Value::String(s) => { - if f.alternate() { - write!(f, "{s:?}") - } else { - write!(f, "{s}") - } - }, - Value::List(l) => { + Self::Cell(v) if f.alternate() => write!(f, "cell({:#})", v.borrow()), + Self::Cell(v) => write!(f, "{})", v.borrow()), + + Self::String(s) if f.alternate() => write!(f, "{s:?}"), + Self::String(s) => write!(f, "{s}"), + + Self::List(l) => { write!(f, "[")?; for (i, item) in l.borrow().iter().enumerate() { if i != 0 { write!(f, ", ")?; } - if f.alternate() { - write!(f, "{item:#}")?; - } else { - write!(f, "{item}")?; - } + write!(f, "{item:#}")?; } write!(f, "]") }, - Value::Table(t) => { + Self::Table(t) => { write!(f, "{{ ")?; for (i, (k, v)) in t.borrow().iter().enumerate() { if i != 0 { write!(f, ", ")?; } - if f.alternate() { - write!(f, "({:#}) = {v:#}", k.0)?; - } else { - write!(f, "({}) = {v}", k.0)?; - } + write!(f, "({:#}) = {v:#}", k.0)?; } write!(f, " }}") }, - Value::Function(g) + Self::Function(g) => write!(f, "", Rc::as_ptr(g)), - Value::NativeFunc(g) + Self::NativeFunc(g) => write!(f, "", Rc::as_ptr(g)), } } } +impl Value { + pub fn get_type(&self) -> Symbol { + use crate::symbol::*; + match self { + Value::Nil => *SYM_NIL, + Value::Bool(_) => *SYM_BOOL, + Value::Symbol(_) => *SYM_SYMBOL, + Value::Range(_) => *SYM_RANGE, + Value::Int(_) => *SYM_INT, + Value::Float(_) => *SYM_FLOAT, + Value::Ratio(_) => *SYM_RATIO, + Value::Complex(_) => *SYM_COMPLEX, + Value::Cell(_) => *SYM_CELL, + Value::String(_) => *SYM_STRING, + Value::List(_) => *SYM_LIST, + Value::Table(_) => *SYM_TABLE, + Value::Function(_) => *SYM_FUNCTION, + Value::NativeFunc(_) => *SYM_NATIVE_FUNC, + } + } + + pub fn hashable(&self) -> bool { + matches!( + self, + Value::Nil | Value::Bool(_) | Value::Symbol(_) + | Value::Int(_) | Value::Ratio(_) | Value::String(_) + ) + } +} #[derive(Clone, Debug, PartialEq)] pub struct HashValue(Value); @@ -102,15 +125,10 @@ impl Eq for HashValue {} impl TryFrom for HashValue { type Error = Exception; fn try_from(value: Value) -> std::result::Result { - match value { - Value::Nil - | Value::Bool(_) - | Value::Symbol(_) - | Value::Int(_) - | Value::Ratio(_) - | Value::String(_) => Ok(Self(value)), - _ => throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed"), + if !value.hashable() { + throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed") } + Ok(Self(value)) } } @@ -134,43 +152,43 @@ impl Hash for HashValue { } macro_rules! impl_from { - ($ty:ty, $var:ident) => { - impl From<$ty> for Value { - fn from(value: $ty) -> Self { Self::$var(value) } - } - }; - ($ty:ty, $var:ident, hash) => { - impl From<$ty> for Value { - fn from(value: $ty) -> Self { Self::$var(value) } - } - impl From<$ty> for HashValue { - fn from(value: $ty) -> Self { Self(Value::$var(value)) } - } - }; - ($ty:ty, $var:ident, rc) => { - impl From<$ty> for Value { - fn from(value: $ty) -> Self { Self::$var(Rc::new(value)) } - } - impl From> for Value { - fn from(value: Rc<$ty>) -> Self { Self::$var(value) } - } - }; - ($ty:ty, $var:ident, rcref) => { - impl From<$ty> for Value { - fn from(value: $ty) -> Self { Self::$var(Rc::new(RefCell::new(value))) } - } - impl From> for Value { - fn from(value: RefCell<$ty>) -> Self { Self::$var(Rc::new(value)) } - } - impl From>> for Value { - fn from(value: Rc>) -> Self { Self::$var(value) } - } - }; - ($ty:ty, $var:ident, into) => { - impl From<$ty> for Value { - fn from(value: $ty) -> Self { Self::$var(value.into()) } - } - }; + ($ty:ty, $var:ident) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { Self::$var(value) } + } + }; + ($ty:ty, $var:ident, hash) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { Self::$var(value) } + } + impl From<$ty> for HashValue { + fn from(value: $ty) -> Self { Self(Value::$var(value)) } + } + }; + ($ty:ty, $var:ident, rc) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { Self::$var(Rc::new(value)) } + } + impl From> for Value { + fn from(value: Rc<$ty>) -> Self { Self::$var(value) } + } + }; + ($ty:ty, $var:ident, rcref) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { Self::$var(Rc::new(RefCell::new(value))) } + } + impl From> for Value { + fn from(value: RefCell<$ty>) -> Self { Self::$var(Rc::new(value)) } + } + impl From>> for Value { + fn from(value: Rc>) -> Self { Self::$var(value) } + } + }; + ($ty:ty, $var:ident, into) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { Self::$var(value.into()) } + } + }; } impl_from!(bool, Bool, hash); @@ -185,6 +203,12 @@ impl_from!(Vec, List, rcref); impl_from!(Function, Function, rc); impl_from!(NativeFunc, NativeFunc, rc); impl_from!(Rc, String); +impl_from!(String, String, into); impl_from!(&str, String, into); impl_from!(Box, String, into); -impl_from!(String, String, into); +impl_from!(RefCell, Cell, rc); + +#[inline] +pub fn cell_take(cell: Rc>) -> T { + Rc::unwrap_or_clone(cell).into_inner() +} diff --git a/talc-lang/src/value/ops.rs b/talc-lang/src/value/ops.rs index 8d96ea9..76d5e34 100644 --- a/talc-lang/src/value/ops.rs +++ b/talc-lang/src/value/ops.rs @@ -1,11 +1,11 @@ -use std::{ops::{Neg, Add, Sub, Mul, Div}, cmp::Ordering, cell::RefCell}; +use std::{cell::RefCell, cmp::Ordering, ops::{Add, Div, Mul, Neg, Sub}, rc::Rc}; use num_complex::Complex64; use num_rational::Rational64; -use crate::{symbol::{SYM_INDEX_ERROR, SYM_STOP_ITERATOR, SYM_TYPE_ERROR}, value::range::RangeType, Vm}; +use crate::{symbol::SYM_TYPE_ERROR, value::range::RangeType, Vm}; -use super::{Value, exception::{Result, throw}, range::Range, function::NativeFunc, HashValue}; +use super::{exception::{throw, Result}, function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value}; impl Value { pub fn truthy(&self) -> bool { @@ -19,6 +19,7 @@ impl Value { Value::Complex(c) => !(c.re == 0.0 && c.im == 0.0 || c.is_nan()), Value::String(s) => s.len() > 0, Value::List(l) => l.borrow().len() > 0, + Value::Cell(v) => v.borrow().truthy(), Value::Symbol(_) | Value::Table(_) | Value::Function(_) | Value::NativeFunc(_) => true, @@ -32,7 +33,7 @@ fn ratio_to_f64(r: Rational64) -> f64 { } #[allow(clippy::cast_precision_loss)] -fn promote(a: Value, b: Value) -> (Value, Value) { +pub fn promote(a: Value, b: Value) -> (Value, Value) { use Value as V; match (&a, &b) { (V::Int(x), V::Ratio(..)) => (V::Ratio((*x).into()), b), @@ -113,6 +114,9 @@ impl Div for Value { fn div(self, rhs: Value) -> Self::Output { use Value as V; match promote(self, rhs) { + (_, V::Int(0)) => throw!(*SYM_TYPE_ERROR, "integer division by 0"), + (_, V::Ratio(r)) if *r.numer() == 0 && *r.denom() != 0 + => throw!(*SYM_TYPE_ERROR, "integer division by 0"), (V::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))), (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x / y)), @@ -123,26 +127,26 @@ impl Div for Value { } #[allow(clippy::cast_sign_loss)] -const fn ipow(n: i64, p: u64) -> i64 { +fn ipow(n: i64, p: u64) -> Result { match (n, p) { - (0, 0) => panic!("power 0^0"), - (0, _) => 0, - (_, 0) => 1, + (0, 0) => throw!(*SYM_TYPE_ERROR, "integer 0 raised to power 0"), + (0, _) => Ok(0), + (_, 0) => Ok(1), (n, p) if p > u32::MAX as u64 => { let (lo, hi) = (p as u32, (p >> 32) as u32); let (a, b) = (n.pow(lo), n.pow(hi)); - a * b.pow(0x1_0000).pow(0x1_0000) + Ok(a * b.pow(0x1_0000).pow(0x1_0000)) } - (n, p) => n.pow(p as u32), + (n, p) => Ok(n.pow(p as u32)), } } #[allow(clippy::cast_sign_loss)] -const fn rpow(n: i64, d: i64, p: i64) -> (i64, i64) { - match p { - 0.. => (ipow(n, p as u64), ipow(d, p as u64)), - _ => (ipow(d, (-p) as u64), ipow(n, (-p) as u64)), - } +fn rpow(n: i64, d: i64, p: i64) -> Result<(i64, i64)> { + Ok(match p { + 0.. => (ipow(n, p as u64)?, ipow(d, p as u64)?), + _ => (ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?), + }) } impl Value { @@ -171,11 +175,11 @@ impl Value { pub fn pow(self, rhs: Value) -> Result { use Value as V; if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) { - return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y).into())); + return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y)?.into())); } match promote(self, rhs) { (V::Int(x), V::Int(y)) - => Ok(V::Ratio(rpow(x, 1, y).into())), + => Ok(V::Ratio(rpow(x, 1, y)?.into())), (V::Float(x), V::Float(y)) => Ok(V::Float(x.powf(y))), (V::Ratio(x), V::Ratio(y)) @@ -214,6 +218,7 @@ impl PartialEq for Value { (V::String(a), V::String(b)) => *a == *b, (V::List(a), V::List(b)) => *a.borrow() == *b.borrow(), (V::Symbol(a), V::Symbol(b)) => a == b, + (V::Cell(a), V::Cell(b)) => *a.borrow() == *b.borrow(), (V::Range(a), V::Range(b)) => match (a.ty, b.ty) { (Rty::Open, Rty::Open) => a.start == b.start && a.stop == b.stop, (Rty::Closed, Rty::Closed) => a.start == b.start && a.stop == b.stop, @@ -259,59 +264,6 @@ impl Value { } } - pub fn index(&self, idx: Self) -> Result { - use Value as V; - match (self, idx) { - (V::List(l), V::Int(i)) => { - let l = l.borrow(); - if i >= 0 && (i as usize) < l.len() { - Ok(l[i as usize].clone()) - } else { - throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len()) - } - }, - (V::Range(r), V::Int(i)) => { - if i >= 0 && ( - r.ty == RangeType::Endless - || i < r.stop - || (r.ty == RangeType::Closed && i == r.stop) - ) { - Ok((r.start + i).into()) - } else { - throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}") - } - }, - (V::Table(t), i) => { - let t = t.borrow(); - let i = i.try_into()?; - Ok(t.get(&i).cloned().unwrap_or(Value::Nil)) - }, - (col, idx) => throw!(*SYM_TYPE_ERROR, "cannot index {col:#} with {idx:#}") - } - } - - pub fn store_index(&self, idx: Self, val: Self) -> Result<()> { - use Value as V; - match (self, idx) { - (V::List(l), V::Int(i)) => { - let mut l = l.borrow_mut(); - if i >= 0 && (i as usize) < l.len() { - l[i as usize] = val; - Ok(()) - } else { - throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len()) - } - }, - (V::Table(t), i) => { - let mut t = t.borrow_mut(); - let i = i.try_into()?; - t.insert(i, val); - Ok(()) - }, - (col, idx) => throw!(*SYM_INDEX_ERROR, "cannot index {col:#} with {idx:#}") - } - } - pub fn concat(&self, other: &Self) -> Result { use Value as V; match (self, other) { @@ -334,7 +286,7 @@ impl Value { } } - pub fn append(&self, val: Self) -> Result { + pub fn append(&self, val: Self) -> Result { use Value as V; match self { V::List(list) => { @@ -343,8 +295,8 @@ impl Value { Ok(l.into()) }, lhs => throw!(*SYM_TYPE_ERROR, "cannot append to {lhs:#}"), - } - } + } + } pub fn range(&self, other: &Self, closed: bool) -> Result { if let (Value::Int(start), Value::Int(stop)) = (self, other) { @@ -363,6 +315,25 @@ impl Value { } } + pub fn to_cell(self) -> Self { + Value::Cell(Rc::new(RefCell::new(self))) + } + + pub fn iter_unpack(self) -> Result>>> { + match self { + Self::Nil => Ok(None), + Self::Cell(v) => Ok(Some(v)), + _ => throw!(*SYM_TYPE_ERROR, "expected iterator to return cell or nil") + } + } + + pub fn iter_pack(v: Option) -> Self { + match v { + Some(v) => v.to_cell(), + None => Value::Nil, + } + } + pub fn to_iter_function(self) -> Result { match self { Self::Function(_) | Self::NativeFunc(_) => Ok(self), @@ -370,15 +341,25 @@ impl Value { let range_iter = RefCell::new(range.into_iter()); let f = move |_: &mut Vm, _: Vec| -> Result { if let Some(v) = range_iter.borrow_mut().next() { - Ok(v.into()) + Ok(Value::from(v).to_cell()) } else { - throw!(*SYM_STOP_ITERATOR) + Ok(Value::Nil) } }; - Ok(NativeFunc { - arity: 0, - f: Box::new(f), - }.into()) + Ok(NativeFunc::new(Box::new(f), 0).into()) + }, + Self::String(s) => { + let byte_pos = RefCell::new(0); + let f = move |_: &mut Vm, _: Vec| -> Result { + let pos = *byte_pos.borrow(); + if let Some(v) = &s[pos..].chars().next() { + *byte_pos.borrow_mut() += v.len_utf8(); + Ok(Value::from(v.to_string()).to_cell()) + } else { + Ok(Value::Nil) + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) }, Self::List(list) => { let idx = RefCell::new(0); @@ -386,32 +367,34 @@ impl Value { let i = *idx.borrow(); if let Some(v) = list.borrow().get(i) { *idx.borrow_mut() += 1; - Ok(v.clone()) + Ok(v.clone().to_cell()) } else { - throw!(*SYM_STOP_ITERATOR) + Ok(Value::Nil) } }; - Ok(NativeFunc { - arity: 0, - f: Box::new(f), - }.into()) + Ok(NativeFunc::new(Box::new(f), 0).into()) }, Self::Table(table) => { let keys: Vec = table.borrow().keys().cloned().collect(); let keys = RefCell::new(keys.into_iter()); let f = move |_: &mut Vm, _: Vec| -> Result { if let Some(v) = keys.borrow_mut().next() { - Ok(v.into_inner()) + Ok(v.into_inner().to_cell()) } else { - throw!(*SYM_STOP_ITERATOR) + Ok(Value::Nil) } }; - Ok(NativeFunc { - arity: 0, - f: Box::new(f), - }.into()) + Ok(NativeFunc::new(Box::new(f), 0).into()) }, _ => throw!(*SYM_TYPE_ERROR, "cannot iterate {self:#}"), } } + + pub fn func_attrs(&self) -> Option { + match self { + Value::Function(f) => Some(f.attrs), + Value::NativeFunc(f) => Some(f.attrs), + _ => None, + } + } } diff --git a/talc-lang/src/value/range.rs b/talc-lang/src/value/range.rs index 8597a6a..6974244 100644 --- a/talc-lang/src/value/range.rs +++ b/talc-lang/src/value/range.rs @@ -41,3 +41,4 @@ impl IntoIterator for Range { } } + diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index 1ec7071..92feaa6 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -1,6 +1,6 @@ use std::{cmp::Ordering, collections::HashMap, rc::Rc, sync::{atomic::AtomicBool, Arc}}; -use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{exception::{throw, Exception, Result}, function::Function, Value}}; +use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{cell_take, exception::{throw, Exception, Result}, function::{FuncAttrs, Function, NativeFunc}, Value}}; struct TryFrame { idx: usize, stack_len: usize } @@ -28,7 +28,7 @@ pub struct Vm { call_stack: Vec, stack_max: usize, globals: HashMap, - interrupt: Arc, + interrupt: Arc, } pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result { @@ -61,6 +61,49 @@ pub fn unary_op(o: UnaryOp, a: Value) -> Result { } } +enum CallOutcome { + Call(Vec), + Partial(Value), +} + +fn get_call_outcome(mut args: Vec) -> Result { + let f = &args[0]; + let Some(attrs) = f.func_attrs() else { + throw!(*SYM_TYPE_ERROR, "cannot call non-function {f:#}") + }; + let argc = args.len() - 1; + if attrs.variadic && argc >= attrs.arity { + let vararg = args.split_off(attrs.arity + 1); + args.push(vararg.into()); + Ok(CallOutcome::Call(args)) + } else if argc == attrs.arity { + Ok(CallOutcome::Call(args)) + } else if argc > attrs.arity { + throw!(*SYM_TYPE_ERROR, "too many arguments for function") + } else { + let remaining = attrs.arity - argc; + let f = f.clone(); + let nf = move |vm: &mut Vm, inner_args: Vec| { + let mut ia = inner_args.into_iter(); + ia.next(); + let mut args: Vec = args.clone().into_iter().chain(ia).collect(); + if attrs.variadic { + let Value::List(varargs) = args.pop() + .expect("did not receive vararg") else { + panic!("did not receive vararg") + }; + args.extend(cell_take(varargs)); + } + vm.call_value(f.clone(), args) + }; + let nf = NativeFunc { + attrs: FuncAttrs { arity: remaining, variadic: attrs.variadic }, + func: Box::new(nf), + }; + Ok(CallOutcome::Partial(nf.into())) + } +} + impl Vm { pub fn new(stack_max: usize) -> Self { Self { @@ -68,13 +111,13 @@ impl Vm { call_stack: Vec::with_capacity(16), globals: HashMap::with_capacity(16), stack_max, - interrupt: Arc::new(AtomicBool::new(false)), + interrupt: Arc::new(AtomicBool::new(false)), } } - pub fn get_interrupt(&self) -> Arc { - self.interrupt.clone() - } + pub fn get_interrupt(&self) -> Arc { + self.interrupt.clone() + } pub fn set_global(&mut self, name: Symbol, val: Value) { self.globals.insert(name, val); @@ -88,246 +131,47 @@ impl Vm { self.globals.get(&name) } - #[inline] - fn push(&mut self, v: Value) { - self.stack.push(v); + pub fn globals(&self) -> &HashMap { + &self.globals } - #[inline] - fn pop(&mut self) -> Value { - self.stack.pop().expect("temporary stack underflow") - } - - #[inline] - fn pop_n(&mut self, n: usize) -> Vec { - let res = self.stack.split_off(self.stack.len() - n); - assert!(res.len() == n, "temporary stack underflow"); - res - } - - fn check_interrupt(&mut self) -> Result<()> { - if self.interrupt.fetch_and(false, std::sync::atomic::Ordering::Relaxed) { - throw!(*SYM_INTERRUPTED) - } - Ok(()) - } - - fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result> { - use Instruction as I; - - match instr { - // do nothing - I::Nop => (), - // [] -> [locals[n]] - I::LoadLocal(n) - => self.push(frame.locals[usize::from(n)].clone()), - // [x] -> [], locals[n] = x - I::StoreLocal(n) - => frame.locals[usize::from(n)] = self.pop(), - // [x] -> [], locals.push(x) - I::NewLocal - => frame.locals.push(self.pop()), - // locals.pop_n(n) - I::DropLocal(n) - => frame.locals.truncate(frame.locals.len() - usize::from(n)), - // [] -> [globals[s]] - I::LoadGlobal(s) => { - let sym = unsafe { s.to_symbol_unchecked() }; - let v = match self.globals.get(&sym) { - Some(v) => v.clone(), - None => throw!(*SYM_NAME_ERROR, "undefined global {}", sym.name()), - }; - self.push(v); - }, - // [x] -> [], globals[s] = x - I::StoreGlobal(s) => { - let sym = unsafe { s.to_symbol_unchecked() }; - let v = self.pop(); - self.globals.insert(sym, v); - }, - // [] -> [consts[n]] - I::Const(n) - => self.push(frame.func.chunk.consts[usize::from(n)].clone()), - // [] -> [nil] - I::Nil => self.push(Value::Nil), - // [] -> [b] - I::Bool(b) => self.push(Value::Bool(b)), - // [] -> [s] - I::Symbol(s) => { - let sym = unsafe { Symbol::from_id_unchecked(u32::from(s)) }; - self.push(Value::Symbol(sym)); - }, - // [] -> [n] - I::Int(n) => self.push(Value::Int(i64::from(n))), - // [x] -> [x,x] - I::Dup => self.push(self.stack[self.stack.len() - 1].clone()), - // [x,y] -> [x,y,x,y] - I::DupTwo => { - self.push(self.stack[self.stack.len() - 2].clone()); - self.push(self.stack[self.stack.len() - 2].clone()); - }, - // [a0,a1...an] -> [] - I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); }, - // [x,y] -> [y,x] - I::Swap => { - let len = self.stack.len(); - self.stack.swap(len - 1, len - 2); - }, - // [x,y] -> [y op x] - I::BinaryOp(op) => { - let b = self.pop(); - let a = self.pop(); - self.push(binary_op(op, a, b)?); - }, - // [x] -> [op x] - I::UnaryOp(op) => { - let a = self.pop(); - self.push(unary_op(op, a)?); - }, - // [a0,a1...an] -.> [[a0,a1...an]] - I::NewList(n) => { - let list = self.pop_n(n as usize); - self.push(list.into()) - }, - // [l,a0,a1...an] -.> [l ++ [a0,a1...an]] - 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)); - }, - // [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}] - 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(table.into()) - }, - // [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}] - 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)); - }, - // [ct, idx] -> [ct!idx] - I::Index => { - let idx = self.pop(); - let ct = self.pop(); - self.push(ct.index(idx)?); - }, - // [ct, idx, v] -> [v], ct!idx = v - I::StoreIndex => { - let v = self.pop(); - let idx = self.pop(); - let ct = self.pop(); - ct.store_index(idx, v.clone())?; - self.push(v); - }, - // ip = n - I::Jump(n) => { - self.check_interrupt()?; - frame.ip = usize::from(n) - }, - // [v] ->, [], if v then ip = n - I::JumpTrue(n) => if self.pop().truthy() { - self.check_interrupt()?; - frame.ip = usize::from(n) - }, - // [v] ->, [], if not v then ip = n - I::JumpFalse(n) => if !self.pop().truthy() { - self.check_interrupt()?; - frame.ip = usize::from(n) - }, - // [v] -> [iter(v)] - I::IterBegin => { - let iter = self.pop().to_iter_function()?; - self.push(iter); - }, - // [i] -> if i() succeeds then [i, i()], otherwise [] and ip = n - I::IterNext(n) => { - let v = &self.stack[self.stack.len() - 1]; - self.call_stack.push(std::mem::take(frame)); - let res = self.call_value(v.clone(), vec![v.clone()]); - *frame = self.call_stack.pop().expect("no frame to pop"); - match res { - Ok(res) => self.push(res), - Err(e) => if e.ty == Symbol::get("stop_iterator") { - self.pop(); - frame.ip = usize::from(n) - } else { - return Err(e) - } - } - }, - // try_frames.push(t, stack.len()) - I::BeginTry(t) => { - let tryframe = TryFrame { - idx: usize::from(t), - stack_len: self.stack.len() - }; - frame.try_frames.push(tryframe); - }, - // try_frames.pop() - I::EndTry => { - frame.try_frames.pop().expect("no try to pop"); - }, - // [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); - let func = &args[0]; - if let Value::NativeFunc(nf) = func { - let nf = nf.clone(); - if nf.arity != n { - throw!(*SYM_TYPE_ERROR, "function call with wrong argument count"); - } - - self.call_stack.push(std::mem::take(frame)); - let res = (nf.f)(self, args)?; - *frame = self.call_stack.pop().expect("no frame to pop"); - - self.stack.push(res); - } else if let Value::Function(func) = func { - if func.arity != n { - throw!(*SYM_TYPE_ERROR, "function call with wrong argument count"); - } - - if self.call_stack.len() + 1 >= self.stack_max { - throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow") - } - self.call_stack.push(std::mem::take(frame)); - - let func = func.clone(); - *frame = CallFrame::new(func, args); - } else { - throw!(*SYM_TYPE_ERROR, "attempt to call non-function {func}"); - } - }, - // [v] -> [], return v - I::Return if frame.root => { - return Ok(Some(self.pop())); - }, - // [v] -> [], return v - I::Return => { - *frame = self.call_stack.pop().expect("no root frame"); - }, + pub fn call_value(&mut self, value: Value, args: Vec) -> Result { + self.check_interrupt()?; + match get_call_outcome(args)? { + CallOutcome::Partial(v) => Ok(v), + CallOutcome::Call(args) => match value { + Value::Function(f) => self.run_function(f, args), + Value::NativeFunc(f) => (f.func)(self, args), + _ => unreachable!("already verified by calling get_call_type") + } } + } - Ok(None) + pub fn run_function(&mut self, func: Rc, args: Vec) -> Result { + if func.attrs.arity + 1 != args.len() { + throw!(*SYM_TYPE_ERROR, "function call with wrong argument count"); + } + let init_stack_len = self.stack.len(); + let mut frame = CallFrame::new(func, args); + frame.root = true; + + loop { + let instr = frame.func.chunk.instrs[frame.ip]; + frame.ip += 1; + 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) + } + } + } + } } fn handle_exception(&mut self, frame: &mut CallFrame, exc: Exception) -> Result<()> { @@ -351,49 +195,265 @@ impl Vm { } } - pub fn call_value(&mut self, value: Value, args: Vec) -> Result { - match value { - Value::Function(f) => self.call(f, args), - Value::NativeFunc(f) => { - if f.arity + 1 != args.len() { - throw!(*SYM_TYPE_ERROR, "function call with wrong argument count"); - } - (f.f)(self, args) - }, - _ => throw!(*SYM_TYPE_ERROR, "not a function"), - } - } - - - pub fn call_no_args(&mut self, func: Rc) -> Result { - self.call(func.clone(), vec![func.into()]) - } - - pub fn call(&mut self, func: Rc, args: Vec) -> Result { - if func.arity + 1 != args.len() { - throw!(*SYM_TYPE_ERROR, "function call with wrong argument count"); - } - let init_stack_len = self.stack.len(); - let mut frame = CallFrame::new(func, args); - frame.root = true; - - loop { - let instr = frame.func.chunk.instrs[frame.ip]; - frame.ip += 1; - 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) - } - } - } - } + #[inline] + fn push(&mut self, v: Value) { + self.stack.push(v); } + #[inline] + fn pop(&mut self) -> Value { + self.stack.pop().expect("temporary stack underflow") + } + + #[inline] + fn pop_n(&mut self, n: usize) -> Vec { + let res = self.stack.split_off(self.stack.len() - n); + assert!(res.len() == n, "temporary stack underflow"); + res + } + + fn check_interrupt(&mut self) -> Result<()> { + if self.interrupt.fetch_and(false, std::sync::atomic::Ordering::Relaxed) { + throw!(*SYM_INTERRUPTED) + } + Ok(()) + } + + fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result> { + use Instruction as I; + + match instr { + // do nothing + I::Nop => (), + // [] -> [locals[n]] + I::LoadLocal(n) + => self.push(frame.locals[usize::from(n)].clone()), + // [x] -> [], locals[n] = x + I::StoreLocal(n) + => frame.locals[usize::from(n)] = self.pop(), + // [x] -> [], locals.push(x) + I::NewLocal + => frame.locals.push(self.pop()), + // locals.pop_n(n) + I::DropLocal(n) + => frame.locals.truncate(frame.locals.len() - usize::from(n)), + // [] -> [globals[s]] + I::LoadGlobal(s) => { + let sym = unsafe { s.to_symbol_unchecked() }; + let v = match self.globals.get(&sym) { + Some(v) => v.clone(), + None => throw!(*SYM_NAME_ERROR, "undefined global {}", sym.name()), + }; + self.push(v); + }, + // [x] -> [], globals[s] = x + I::StoreGlobal(s) => { + let sym = unsafe { s.to_symbol_unchecked() }; + let v = self.pop(); + self.globals.insert(sym, v); + }, + // [] -> [consts[n]] + I::Const(n) + => self.push(frame.func.chunk.consts[usize::from(n)].clone()), + // [] -> [nil] + I::Nil => self.push(Value::Nil), + // [] -> [b] + I::Bool(b) => self.push(Value::Bool(b)), + // [] -> [s] + I::Symbol(s) => { + let sym = unsafe { Symbol::from_id_unchecked(u32::from(s)) }; + self.push(Value::Symbol(sym)); + }, + // [] -> [n] + I::Int(n) => self.push(Value::Int(i64::from(n))), + // [x] -> [x,x] + I::Dup => self.push(self.stack[self.stack.len() - 1].clone()), + // [x,y] -> [x,y,x,y] + I::DupTwo => { + self.push(self.stack[self.stack.len() - 2].clone()); + self.push(self.stack[self.stack.len() - 2].clone()); + }, + // [a0,a1...an] -> [] + I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); }, + // [x,y] -> [y,x] + I::Swap => { + let len = self.stack.len(); + self.stack.swap(len - 1, len - 2); + }, + // [x,y] -> [y op x] + I::BinaryOp(op) => { + let b = self.pop(); + let a = self.pop(); + self.push(binary_op(op, a, b)?); + }, + // [x] -> [op x] + I::UnaryOp(op) => { + let a = self.pop(); + self.push(unary_op(op, a)?); + }, + // [a0,a1...an] -.> [[a0,a1...an]] + I::NewList(n) => { + let list = self.pop_n(n as usize); + self.push(list.into()) + }, + // [l,a0,a1...an] -.> [l ++ [a0,a1...an]] + 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)); + }, + // [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}] + 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(table.into()) + }, + // [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}] + 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)); + }, + // [ct, idx] -> [ct!idx] + I::Index => { + let idx = self.pop(); + let ct = self.pop(); + self.push(ct.index(idx)?); + }, + // [ct, idx, v] -> [v], ct!idx = v + I::StoreIndex => { + let v = self.pop(); + let idx = self.pop(); + let ct = self.pop(); + ct.store_index(self, idx, v.clone())?; + self.push(v); + }, + // ip = n + I::Jump(n) => { + self.check_interrupt()?; + frame.ip = usize::from(n) + }, + // [v] ->, [], if v then ip = n + I::JumpTrue(n) => if self.pop().truthy() { + self.check_interrupt()?; + frame.ip = usize::from(n) + }, + // [v] ->, [], if not v then ip = n + I::JumpFalse(n) => if !self.pop().truthy() { + self.check_interrupt()?; + frame.ip = usize::from(n) + }, + // [v] -> [iter(v)] + I::IterBegin => { + let iter = self.pop().to_iter_function()?; + self.push(iter); + }, + // [i,cell(v)] -> [i,v] + // [i,nil] -> [], ip = n + I::IterTest(n) => { + match self.pop() { + Value::Cell(c) => self.push(cell_take(c)), + Value::Nil => { + self.pop(); + frame.ip = usize::from(n) + }, + v => throw!(*SYM_TYPE_ERROR, "iterator returned invalid value {v}") + } + }, + // try_frames.push(t, stack.len()) + I::BeginTry(t) => { + let tryframe = TryFrame { + idx: usize::from(t), + stack_len: self.stack.len() + }; + frame.try_frames.push(tryframe); + }, + // try_frames.pop() + I::EndTry => { + frame.try_frames.pop().expect("no try to pop"); + }, + // [f,a0,a1...an] -> [f(a0,a1...an)] + I::Call(n) => { + 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) => { + self.push(v); + return Ok(None) + } + }; + + if let Value::NativeFunc(nf) = &args[0] { + let nf = nf.clone(); + + self.call_stack.push(std::mem::take(frame)); + let res = (nf.func)(self, args)?; + *frame = self.call_stack.pop().expect("no frame to pop"); + + self.stack.push(res); + } else if let Value::Function(func) = &args[0] { + + if self.call_stack.len() + 1 >= self.stack_max { + throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow") + } + self.call_stack.push(std::mem::take(frame)); + + let func = func.clone(); + *frame = CallFrame::new(func, args); + } 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 + I::Return => { + self.check_interrupt()?; + *frame = self.call_stack.pop().expect("no root frame"); + }, + } + + Ok(None) + } + + +} + +#[macro_export] +macro_rules! vmcall { + ($vm:expr; $func:expr, $($arg:expr),*) => {{ + let f = $func; + $vm.call_value(f.clone(), vec![f, $($arg),*]) + }}; + ($vm:expr; $func:expr) => {{ + let f = $func; + $vm.call_value(f.clone(), vec![f]) + }}; +} + +#[macro_export] +macro_rules! vmcalliter { + ($($input:tt)*) => { + $crate::vmcall!($($input)*).and_then(|v| v.iter_unpack()) + } } diff --git a/talc-macros/src/lib.rs b/talc-macros/src/lib.rs index 46c8bfc..8b27ba6 100644 --- a/talc-macros/src/lib.rs +++ b/talc-macros/src/lib.rs @@ -1,32 +1,52 @@ use proc_macro::TokenStream; -use syn::{LitInt, ItemFn}; +use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt, Token}; use quote::quote; +struct NativeFuncArgs { + arity: LitInt, + variadic: Option, +} + +impl Parse for NativeFuncArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let arity = input.parse()?; + let variadic = input.parse()?; + Ok(Self { arity, variadic }) + } +} + #[proc_macro_attribute] pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream { - let itemfn: ItemFn = syn::parse(annotated_item).unwrap(); - let arity: LitInt = syn::parse(input).unwrap(); + let Ok(itemfn) = syn::parse::(annotated_item.clone()) else { + return annotated_item + }; + let args: NativeFuncArgs = parse_macro_input!(input as NativeFuncArgs); - let visibility = itemfn.vis; - let block = itemfn.block; - let name = itemfn.sig.ident; - let inputs = itemfn.sig.inputs; - let output = itemfn.sig.output; + let visibility = itemfn.vis; + let block = itemfn.block; + let name = itemfn.sig.ident; + let inputs = itemfn.sig.inputs; + let output = itemfn.sig.output; + let arity = args.arity; + let variadic = args.variadic.is_some(); - assert!(itemfn.sig.constness.is_none(), "item must not be const"); - assert!(itemfn.sig.asyncness.is_none(), "item must not be async"); - assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe"); - assert!(itemfn.sig.abi.is_none(), "item must not contain an ABI specifier"); - assert!(itemfn.sig.variadic.is_none(), "item must not be variadic"); + assert!(itemfn.sig.constness.is_none(), "item must not be const"); + assert!(itemfn.sig.asyncness.is_none(), "item must not be async"); + assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe"); + assert!(itemfn.sig.abi.is_none(), "item must not contain an ABI specifier"); + assert!(itemfn.sig.variadic.is_none(), "item must not be variadic"); - let expanded = quote! { - #visibility fn #name() -> ::talc_lang::value::function::NativeFunc { - ::talc_lang::value::function::NativeFunc { - arity: #arity, - f: Box::new(|#inputs| #output #block) - } - } - }; - - TokenStream::from(expanded) + let expanded = quote! { + #visibility fn #name() -> ::talc_lang::value::function::NativeFunc { + ::talc_lang::value::function::NativeFunc { + attrs: ::talc_lang::value::function::FuncAttrs{ + arity: #arity, + variadic: #variadic, + }, + func: Box::new(|#inputs| #output #block) + } + } + }; + + TokenStream::from(expanded) } diff --git a/talc-std/Cargo.toml b/talc-std/Cargo.toml index 81e3fb8..b1914fe 100644 --- a/talc-std/Cargo.toml +++ b/talc-std/Cargo.toml @@ -7,3 +7,8 @@ edition = "2021" talc-lang = { path = "../talc-lang" } talc-macros = { path = "../talc-macros" } lazy_static = "1.4" +rand = { version = "0.8", optional = true } + +[features] +default = ["random"] +random = ["dep:rand"] diff --git a/talc-std/src/collection.rs b/talc-std/src/collection.rs new file mode 100644 index 0000000..a5a6442 --- /dev/null +++ b/talc-std/src/collection.rs @@ -0,0 +1,208 @@ +use std::cmp::Ordering; + +use talc_lang::{exception, symbol::SYM_TYPE_ERROR, throw, value::{exception::Result, function::NativeFunc, Value}, vmcall, Vm}; +use talc_macros::native_func; + +use crate::unpack_args; + + +pub fn load(vm: &mut Vm) { + vm.set_global_name("push", push().into()); + vm.set_global_name("pop", pop().into()); + vm.set_global_name("reverse", reverse().into()); + vm.set_global_name("clear", clear().into()); + vm.set_global_name("sort", sort().into()); + vm.set_global_name("sort_by", sort_by().into()); + vm.set_global_name("sort_key", sort_key().into()); +} + +#[native_func(2)] +pub fn push(_: &mut Vm, args: Vec) -> Result { + let [_, list, item] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "push expected list, found {list:#}") + }; + list.borrow_mut().push(item); + Ok(Value::Nil) +} + +#[native_func(1)] +pub fn pop(_: &mut Vm, args: Vec) -> Result { + let [_, list] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "pop expected list, found {list:#}") + }; + let v = list.borrow_mut().pop(); + v.ok_or_else(|| exception!(*SYM_TYPE_ERROR, "attempt to pop empty list")) +} + +#[native_func(1)] +pub fn reverse(_: &mut Vm, args: Vec) -> Result { + let [_, list] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "reversed expected list, found {list:#}") + }; + list.borrow_mut().reverse(); + Ok(Value::List(list)) +} + +#[native_func(1)] +pub fn clear(_: &mut Vm, args: Vec) -> Result { + let [_, col] = unpack_args!(args); + match &col { + Value::List(list) => list.borrow_mut().clear(), + Value::Table(table) => table.borrow_mut().clear(), + _ => throw!(*SYM_TYPE_ERROR, "clear expected list or table, found {col:#}") + } + Ok(col) +} + +fn call_comparison(vm: &mut Vm, by: Value, l: Value, r: Value) -> Result { + let ord = vmcall!(vm; by, l, r)?; + let Value::Int(ord) = ord else { + throw!(*SYM_TYPE_ERROR, "comparison function should return an integer") + }; + Ok(ord) +} + +fn partition(vals: &mut [Value]) -> (usize, usize) { + let pivot = vals[vals.len() / 2].clone(); + + let mut lt = 0; + let mut eq = 0; + let mut gt = vals.len() - 1; + + while eq <= gt { + let ord = vals[eq].partial_cmp(&pivot); + match ord { + Some(Ordering::Less) => { + vals.swap(eq, lt); + lt += 1; + eq += 1; + }, + Some(Ordering::Greater) => { + vals.swap(eq, gt); + gt -= 1; + }, + Some(Ordering::Equal) | None => { + eq += 1; + }, + } + } + + (lt, gt) +} + +fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, usize)> { + let pivot = vals[vals.len() / 2].clone(); + + let mut lt = 0; + let mut eq = 0; + let mut gt = vals.len() - 1; + + while eq <= gt { + let ord = call_comparison(vm, by.clone(), vals[eq].clone(), pivot.clone())?; + match ord { + ..=-1 => { + vals.swap(eq, lt); + lt += 1; + eq += 1; + }, + 1.. => { + vals.swap(eq, gt); + gt -= 1; + }, + 0 => { + eq += 1; + }, + } + } + + Ok((lt, gt)) +} + +fn insertion(vals: &mut [Value]) { + for i in 0..vals.len() { + let mut j = i; + while j > 0 && vals[j-1] > vals[j] { + vals.swap(j, j-1); + j -= 1; + } + } +} + +fn insertion_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<()> { + for i in 0..vals.len() { + let mut j = i; + while j > 0 { + let ord = call_comparison(vm, by.clone(), vals[j-1].clone(), vals[j].clone())?; + if ord <= 0 { + break; + } + vals.swap(j, j-1); + j -= 1; + } + } + Ok(()) +} + +fn sort_inner(vals: &mut [Value], by: Option<&Value>, vm: &mut Vm) -> Result<()> { + if vals.len() <= 1 { + return Ok(()) + } + if vals.len() <= 8 { + match by { + Some(by) => insertion_by(vals, by, vm)?, + None => insertion(vals), + } + return Ok(()) + } + let (lt, gt) = match by { + Some(by) => partition_by(vals, by, vm)?, + None => partition(vals), + }; + sort_inner(&mut vals[..lt], by, vm)?; + sort_inner(&mut vals[(gt+1)..], by, vm) +} + +#[native_func(1)] +pub fn sort(vm: &mut Vm, args: Vec) -> Result { + let [_, list] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}") + }; + sort_inner(&mut list.borrow_mut(), None, vm)?; + Ok(list.into()) +} + +#[native_func(2)] +pub fn sort_by(vm: &mut Vm, args: Vec) -> Result { + let [_, by, list] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}") + }; + sort_inner(&mut list.borrow_mut(), Some(&by), vm)?; + Ok(list.into()) +} + +#[native_func(2)] +pub fn sort_key(vm: &mut Vm, args: Vec) -> Result { + let [_, key, list] = unpack_args!(args); + let Value::List(list) = list else { + throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}") + }; + let f = move |vm: &mut Vm, args: Vec| { + let [_, a, b] = unpack_args!(args); + let a = vmcall!(vm; key.clone(), a)?; + let b = vmcall!(vm; key.clone(), b)?; + match a.partial_cmp(&b) { + Some(Ordering::Greater) => Ok(Value::Int(1)), + Some(Ordering::Equal) => Ok(Value::Int(0)), + Some(Ordering::Less) => Ok(Value::Int(-1)), + None => throw!(*SYM_TYPE_ERROR, "values returned from sort key were incomparable"), + } + }; + let nf = NativeFunc::new(Box::new(f), 2).into(); + sort_inner(&mut list.borrow_mut(), Some(&nf), vm)?; + Ok(list.into()) +} diff --git a/talc-std/src/exception.rs b/talc-std/src/exception.rs index 1e5874c..d784c98 100644 --- a/talc-std/src/exception.rs +++ b/talc-std/src/exception.rs @@ -1,31 +1,40 @@ use talc_lang::{symbol::SYM_TYPE_ERROR, value::{exception::{throw, Exception, Result}, Value}, Vm}; use talc_macros::native_func; -#[native_func(2)] +use crate::{unpack_args, unpack_varargs}; + +#[native_func(1..)] pub fn throw(_: &mut Vm, args: Vec) -> Result { - let [_, ty, msg] = args.try_into().expect("bypassed arity check"); - let Value::Symbol(ty) = ty else { - throw!(*SYM_TYPE_ERROR, "exception type must be a symbol") - }; - match msg { - Value::String(msg) => Err(Exception::new_with_msg(ty, msg)), - Value::Nil => Err(Exception::new(ty)), - _ => throw!(*SYM_TYPE_ERROR, "exception message must be a string") - } + let ([_, ty], varargs) = unpack_varargs!(args); + let Value::Symbol(ty) = ty else { + throw!(*SYM_TYPE_ERROR, "exception type must be a symbol") + }; + Err(match &*varargs { + [] | [Value::Nil] + => Exception::new(ty), + [Value::Nil, data] + => Exception::new_with_data(ty, data.clone()), + [Value::String(s)] + => Exception::new_with_msg(ty, s.clone()), + [Value::String(s), data] + => Exception::new_with_msg_data(ty, s.clone(), data.clone()), + [_] | [_,_] => throw!(*SYM_TYPE_ERROR, "wrong arguments for throw"), + [_,_,_,..] => throw!(*SYM_TYPE_ERROR, "too many arguments for throw"), + }) } #[native_func(1)] pub fn rethrow(_: &mut Vm, args: Vec) -> Result { - let [_, table] = args.try_into().expect("bypassed arity check"); - if let Value::Table(table) = table { - if let Some(e) = Exception::from_table(&table) { - return Err(e) - } - } - throw!(*SYM_TYPE_ERROR, "argument not a valid exception") + let [_, table] = unpack_args!(args); + if let Value::Table(table) = table { + if let Some(e) = Exception::from_table(&table) { + return Err(e) + } + } + throw!(*SYM_TYPE_ERROR, "argument not a valid exception") } pub fn load(vm: &mut Vm) { - vm.set_global_name("throw", throw().into()); - vm.set_global_name("rethrow", rethrow().into()); + vm.set_global_name("throw", throw().into()); + vm.set_global_name("rethrow", rethrow().into()); } diff --git a/talc-std/src/io.rs b/talc-std/src/io.rs index 7bdec38..a5124c9 100644 --- a/talc-std/src/io.rs +++ b/talc-std/src/io.rs @@ -1,37 +1,70 @@ -use std::io::Write; +use std::{io::Write, time::{SystemTime, UNIX_EPOCH}}; -use talc_lang::{value::{exception::{throw, Result}, Value}, Vm}; +use talc_lang::{value::{exception::{throw, Result}, Value}, vmcall, Vm}; use talc_macros::native_func; -use crate::SYM_IO_ERROR; +use crate::{unpack_args, SYM_IO_ERROR}; #[native_func(1)] pub fn print(_: &mut Vm, args: Vec) -> Result { - if let Err(e) = write!(std::io::stdout(), "{}", args[1]) { - throw!(*SYM_IO_ERROR, "{e}") - } - Ok(Value::Nil) + if let Err(e) = write!(std::io::stdout(), "{}", args[1]) { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) } #[native_func(1)] pub fn println(_: &mut Vm, args: Vec) -> Result { - if let Err(e) = writeln!(std::io::stdout(), "{}", args[1]) { - throw!(*SYM_IO_ERROR, "{e}") - } - Ok(Value::Nil) + if let Err(e) = writeln!(std::io::stdout(), "{}", args[1]) { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) } #[native_func(0)] pub fn readln(_: &mut Vm, _: Vec) -> Result { - let mut buf = String::new(); - if let Err(e) = std::io::stdin().read_line(&mut buf) { - throw!(*SYM_IO_ERROR, "{e}") - } - Ok(buf.into()) + let mut buf = String::new(); + if let Err(e) = std::io::stdin().read_line(&mut buf) { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(buf.into()) +} + +#[native_func(0)] +pub fn time(_: &mut Vm, _: Vec) -> Result { + let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Ok((time.as_secs() as i64).into()) +} + +#[native_func(0)] +pub fn time_ms(_: &mut Vm, _: Vec) -> Result { + let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Ok((time.as_millis() as i64).into()) +} + +#[native_func(0)] +pub fn time_ns(_: &mut Vm, _: Vec) -> Result { + let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards"); + Ok(vec![(time.as_secs() as i64).into(), (time.subsec_nanos() as i64).into()].into()) +} + +#[native_func(1)] +pub fn time_fn(vm: &mut Vm, args: Vec) -> Result { + let [_, func] = unpack_args!(args); + let t0 = SystemTime::now(); + vmcall!(vm; func)?; + let tf = SystemTime::now(); + let time = tf.duration_since(t0).expect("time went backwards"); + Ok((time.as_nanos() as i64).into()) } pub fn load(vm: &mut Vm) { - vm.set_global_name("print", print().into()); - vm.set_global_name("println", println().into()); - vm.set_global_name("readln", readln().into()); + vm.set_global_name("print", print().into()); + vm.set_global_name("println", println().into()); + vm.set_global_name("readln", readln().into()); + + vm.set_global_name("time", time().into()); + vm.set_global_name("time_ms", time_ms().into()); + vm.set_global_name("time_ns", time_ns().into()); + vm.set_global_name("time_fn", time_fn().into()); } diff --git a/talc-std/src/iter.rs b/talc-std/src/iter.rs index 41b4c9e..86e5c16 100644 --- a/talc-std/src/iter.rs +++ b/talc-std/src/iter.rs @@ -1,42 +1,829 @@ -use talc_lang::{symbol::SYM_STOP_ITERATOR, value::{exception::Result, function::NativeFunc, Value}, Vm}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{cell_take, exception::Result, function::NativeFunc, range::RangeType, HashValue, Value}, vmcall, vmcalliter, Vm}; use talc_macros::native_func; +use crate::{unpack_args, unpack_varargs}; + +pub fn load(vm: &mut Vm) { + vm.set_global_name("iter", iter().into()); + vm.set_global_name("pairs", pairs().into()); + vm.set_global_name("once", once().into()); + vm.set_global_name("forever", forever().into()); + vm.set_global_name("do_forever", forever().into()); + + vm.set_global_name("map", map().into()); + vm.set_global_name("scan", scan().into()); + vm.set_global_name("tee", tee().into()); + vm.set_global_name("filter", filter().into()); + vm.set_global_name("filter_map", filter_map().into()); + vm.set_global_name("take", take().into()); + vm.set_global_name("skip", skip().into()); + vm.set_global_name("enumerate", enumerate().into()); + vm.set_global_name("zip", zip().into()); + vm.set_global_name("alternate", alternate().into()); + vm.set_global_name("intersperse", intersperse().into()); + vm.set_global_name("cartprod", cartprod().into()); + vm.set_global_name("cycle", cycle().into()); + vm.set_global_name("chain", chain().into()); + vm.set_global_name("step", step().into()); + vm.set_global_name("rev", rev().into()); + + vm.set_global_name("list", list().into()); + vm.set_global_name("table", table().into()); + vm.set_global_name("len", len().into()); + vm.set_global_name("fold", fold().into()); + vm.set_global_name("foldi", foldi().into()); + vm.set_global_name("sum", sum().into()); + vm.set_global_name("prod", prod().into()); + vm.set_global_name("any", any().into()); + vm.set_global_name("all", all().into()); + vm.set_global_name("last", last().into()); + vm.set_global_name("nth", nth().into()); + vm.set_global_name("contains", contains().into()); + vm.set_global_name("index_of", index_of().into()); + vm.set_global_name("index_if", index_if().into()); + vm.set_global_name("find", find().into()); + vm.set_global_name("count", count().into()); +} + +// +// begin iteration +// + #[native_func(1)] pub fn iter(_: &mut Vm, args: Vec) -> Result { - let [_, v] = args.try_into().expect("bypassed arity check"); - v.to_iter_function() -} + let [_, v] = unpack_args!(args); -#[native_func(2)] -pub fn map(_: &mut Vm, args: Vec) -> Result { - let [_, func, iter] = args.try_into().expect("bypassed arity check"); - let iter = iter.to_iter_function()?; - let f = move |vm: &mut Vm, _| { - let next = vm.call_value(iter.clone(), vec![iter.clone()])?; - vm.call_value(func.clone(), vec![func.clone(), next]) - }; - Ok(NativeFunc { arity: 0, f: Box::new(f) }.into()) + v.to_iter_function() } #[native_func(1)] -pub fn list(vm: &mut Vm, args: Vec) -> Result { - let [_, iter] = args.try_into().expect("bypassed arity check"); - let iter = iter.to_iter_function()?; - let mut result = Vec::new(); - loop { - match vm.call_value(iter.clone(), vec![iter.clone()]) { - Ok(v) => result.push(v), - Err(e) => if e.ty == *SYM_STOP_ITERATOR { - return Ok(result.into()) - } else { - return Err(e) - } - } - }; +pub fn pairs(_: &mut Vm, args: Vec) -> Result { + let [_, table] = unpack_args!(args); + let Value::Table(table) = table else { + throw!(*SYM_TYPE_ERROR, "pairs expected table, found {table:#}") + }; + + let keys: Vec = table.borrow().keys().cloned().collect(); + let keys = RefCell::new(keys.into_iter()); + + let f = move |_: &mut Vm, _: Vec| -> Result { + if let Some(k) = keys.borrow_mut().next() { + let v = table.borrow().get(&k).cloned().unwrap_or(Value::Nil); + Ok(Value::from(vec![k.into_inner(), v]).to_cell()) + } else { + Ok(Value::Nil) + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) } -pub fn load(vm: &mut Vm) { - vm.set_global_name("iter", iter().into()); - vm.set_global_name("map", map().into()); - vm.set_global_name("list", list().into()); +#[native_func(1)] +pub fn once(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + + let v = RefCell::new(Some(val)); + let f = move |_: &mut Vm, _| { + if let Some(v) = v.borrow_mut().take() { + Ok(v.to_cell()) + } else { + Ok(Value::Nil) + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) } + +#[native_func(1)] +pub fn forever(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + + let f = move |_: &mut Vm, _| { + Ok(val.clone().to_cell()) + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(1)] +pub fn do_forever(_: &mut Vm, args: Vec) -> Result { + let [_, func] = unpack_args!(args); + + let f = move |vm: &mut Vm, _| { + Ok(vmcall!(vm; func.clone())?.to_cell()) + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +// +// chain iteration +// + + + +#[native_func(2)] +pub fn map(_: &mut Vm, args: Vec) -> Result { + let [_, map, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let f = move |vm: &mut Vm, _| { + match vmcalliter!(vm; iter.clone())? { + Some(val) => Ok(vmcall!(vm; map.clone(), cell_take(val))?.to_cell()), + None => Ok(Value::Nil), + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2)] +pub fn tee(_: &mut Vm, args: Vec) -> Result { + let [_, tee, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let f = move |vm: &mut Vm, _| { + match vmcalliter!(vm; iter.clone())? { + Some(val) => { + vmcall!(vm; tee.clone(), cell_take(val.clone()))?; + Ok(val.into()) + } + None => Ok(Value::Nil), + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(3)] +pub fn scan(_: &mut Vm, args: Vec) -> Result { + let [_, init, func, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let result = RefCell::new(init); + let f = move |vm: &mut Vm, _| { + match vmcalliter!(vm; iter.clone())? { + Some(val) => { + let val = cell_take(val); + let r = vmcall!(vm; func.clone(), result.take(), val)?; + *result.borrow_mut() = r.clone(); + Ok(r.to_cell()) + }, + None => { + result.take(); + Ok(Value::Nil) + }, + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2)] +pub fn filter(_: &mut Vm, args: Vec) -> Result { + let [_, filter, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let f = move |vm: &mut Vm, _| { + loop { + let next = match vmcalliter!(vm; iter.clone())? { + Some(next) => next, + None => return Ok(Value::Nil), + }; + let res = vmcall!(vm; filter.clone(), cell_take(next.clone()))?; + if res.truthy() { + return Ok(next.into()) + } + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2)] +pub fn filter_map(_: &mut Vm, args: Vec) -> Result { + let [_, fmap, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let f = move |vm: &mut Vm, _| { + loop { + let next = match vmcalliter!(vm; iter.clone())? { + Some(next) => next, + None => return Ok(Value::Nil), + }; + let res = vmcall!(vm; fmap.clone(), cell_take(next))?; + if let Value::Cell(_) = res { + return Ok(res) + } + } + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2)] +pub fn take(_: &mut Vm, args: Vec) -> Result { + let [_, count, iter] = unpack_args!(args); + let Value::Int(count) = count else { + throw!(*SYM_TYPE_ERROR, "take expected integer") + }; + let Ok(count) = count.try_into() else { + throw!(*SYM_TYPE_ERROR, "take expected nonnegative integer") + }; + let iter = iter.to_iter_function()?; + + let taken = RefCell::new(0); + let f = move |vm: &mut Vm, _| { + if *taken.borrow() >= count { + return Ok(Value::Nil) + } + let next = match vmcalliter!(vm; iter.clone())? { + Some(next) => next, + None => { + *taken.borrow_mut() = count; + return Ok(Value::Nil) + } + }; + *taken.borrow_mut() += 1; + Ok(next.into()) + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2)] +pub fn skip(_: &mut Vm, args: Vec) -> Result { + let [_, count, iter] = unpack_args!(args); + let Value::Int(count) = count else { + throw!(*SYM_TYPE_ERROR, "count expected integer") + }; + let Ok(count) = count.try_into() else { + throw!(*SYM_TYPE_ERROR, "count expected nonnegative integer") + }; + let iter = iter.to_iter_function()?; + + let skipped = RefCell::new(false); + let f = move |vm: &mut Vm, _| { + if *skipped.borrow() { + return vmcall!(vm; iter.clone()) + } + *skipped.borrow_mut() = true; + for _ in 0..count { + vmcall!(vm; iter.clone())?; + } + vmcall!(vm; iter.clone()) + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(1)] +pub fn enumerate(_: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let counter = RefCell::new(0); + let f = move |vm: &mut Vm, _| { + let next = match vmcalliter!(vm; iter.clone())? { + Some(next) => cell_take(next), + None => return Ok(Value::Nil), + }; + let mut c = counter.borrow_mut(); + let n = *c; + *c += 1; + Ok(Value::from(vec![n.into(), next]).to_cell()) + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2..)] +pub fn zip(_: &mut Vm, args: Vec) -> Result { + let ([_, i1, i2], rest) = unpack_varargs!(args); + let mut iters = Vec::with_capacity(2 + rest.len()); + for i in [i1, i2].into_iter().chain(rest.into_iter()) { + iters.push(i.to_iter_function()?); + } + + let f = move |vm: &mut Vm, _| { + let mut res = Vec::with_capacity(iters.len()); + for i in iters.iter() { + match vmcalliter!(vm; i.clone())? { + Some(v) => res.push(cell_take(v)), + None => return Ok(Value::Nil), + }; + } + Ok(Value::from(res).to_cell()) + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(2..)] +pub fn alternate(_: &mut Vm, args: Vec) -> Result { + let ([_, i1, i2], rest) = unpack_varargs!(args); + let mut iters = Vec::with_capacity(2 + rest.len()); + for i in [i1, i2].into_iter().chain(rest.into_iter()) { + iters.push(i.to_iter_function()?); + } + + let state = RefCell::new((0, false)); + let f = move |vm: &mut Vm, _| { + let mut s = state.borrow_mut(); + if s.1 { + return Ok(Value::Nil); + } + let n = s.0; + s.0 = (s.0 + 1) % iters.len(); + drop(s); + match vmcalliter!(vm; iters[n].clone())? { + Some(v) => Ok(v.into()), + None => { + state.borrow_mut().1 = true; + Ok(Value::Nil) + } + } + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[derive(Default)] +enum Intersperse { + Init, Waiting, HasNext(Rc>), #[default] End +} + +#[native_func(2)] +pub fn intersperse(_: &mut Vm, args: Vec) -> Result { + let [_, val, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + let val = val.to_cell(); + + let state = RefCell::new(Intersperse::Init); + let f = move |vm: &mut Vm, _| { + match state.take() { + Intersperse::Init => match vmcalliter!(vm; iter.clone())? { + Some(v) => { + *state.borrow_mut() = Intersperse::Waiting; + Ok(v.into()) + }, + None => { + *state.borrow_mut() = Intersperse::End; + Ok(Value::Nil) + }, + }, + Intersperse::Waiting => match vmcalliter!(vm; iter.clone())? { + Some(v) => { + *state.borrow_mut() = Intersperse::HasNext(v); + Ok(val.clone()) + }, + None => { + *state.borrow_mut() = Intersperse::End; + Ok(Value::Nil) + }, + }, + Intersperse::HasNext(v) => { + *state.borrow_mut() = Intersperse::Waiting; + Ok(v.into()) + }, + Intersperse::End => Ok(Value::Nil), + } + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + + +#[native_func(2)] +pub fn chain(_: &mut Vm, args: Vec) -> Result { + let [_, iter1, iter2] = unpack_args!(args); + let iter1 = iter1.to_iter_function()?; + let iter2 = iter2.to_iter_function()?; + + let done_first = RefCell::new(false); + let f = move |vm: &mut Vm, _| { + if *done_first.borrow() { + return vmcall!(vm; iter2.clone()) + } + match vmcalliter!(vm; iter1.clone())? { + Some(v) => Ok(v.into()), + None => { + *done_first.borrow_mut() = true; + vmcall!(vm; iter2.clone()) + } + } + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[derive(Default, Debug)] +struct CartProd { + a_val: Value, + b_data: Vec, + b_idx: usize, + b_done: bool, + done: bool, +} + +#[native_func(2)] +pub fn cartprod(_: &mut Vm, args: Vec) -> Result { + let [_, a, b] = unpack_args!(args); + let a = a.to_iter_function()?; + let b = b.to_iter_function()?; + + let state = RefCell::new(CartProd::default()); + let f = move |vm: &mut Vm, _| { + if state.borrow().done { + return Ok(Value::Nil) + } + + if state.borrow().b_idx >= state.borrow().b_data.len() { + if !state.borrow().b_done { + let v = vmcalliter!(vm; b.clone())?; + let mut s = state.borrow_mut(); + match v { + Some(x) => s.b_data.push(cell_take(x)), + None => { + s.b_done = true; + if s.b_idx == 0 { + s.done = true; + return Ok(Value::Nil) + } + s.b_idx = 0; + } + }; + } else { + if state.borrow().b_idx == 0 { + state.borrow_mut().done = true; + return Ok(Value::Nil) + } + state.borrow_mut().b_idx = 0; + } + } + + let b_res = state.borrow().b_data[state.borrow().b_idx].clone(); + + if state.borrow().b_idx == 0 { + match vmcalliter!(vm; a.clone())? { + Some(v) => state.borrow_mut().a_val = cell_take(v), + None => { + state.borrow_mut().done = true; + return Ok(Value::Nil) + } + } + } + + let a_res = state.borrow().a_val.clone(); + + state.borrow_mut().b_idx += 1; + + Ok(Value::from(vec![a_res, b_res]).to_cell()) + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + + +#[native_func(1)] +pub fn cycle(_: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let record = RefCell::new((Vec::>>::new(), false, 0)); + let f = move |vm: &mut Vm, _| { + if record.borrow().1 { + let mut r = record.borrow_mut(); + if r.0.is_empty() { + return Ok(Value::Nil) + } + let val = r.0[r.2].clone(); + r.2 = (r.2 + 1) % r.0.len(); + return Ok(val.into()) + } + match vmcalliter!(vm; iter.clone())? { + Some(v) => { + record.borrow_mut().0.push(v.clone()); + Ok(v.into()) + }, + None => { + let mut r = record.borrow_mut(); + r.1 = true; + if r.0.is_empty() { + Ok(Value::Nil) + } else { + r.2 = (r.2 + 1) % r.0.len(); + Ok(r.0[0].clone().into()) + } + } + } + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[derive(Clone, Copy, Default)] +enum Step { + First, Going, #[default] End, +} + +#[native_func(2)] +pub fn step(_: &mut Vm, args: Vec) -> Result { + let [_, by, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + let Value::Int(by) = by else { + throw!(*SYM_TYPE_ERROR, "step expected integer") + }; + if by <= 0 { + throw!(*SYM_TYPE_ERROR, "step expected positive integer") + } + + let state = RefCell::new(Step::First); + let f = move |vm: &mut Vm, _| match state.take() { + Step::First => { + match vmcalliter!(vm; iter.clone())? { + Some(x) => { + *state.borrow_mut() = Step::Going; + Ok(x.into()) + }, + None => Ok(Value::Nil), + } + }, + Step::Going => { + for _ in 0..(by-1) { + if vmcall!(vm; iter.clone())? == Value::Nil { + return Ok(Value::Nil) + } + } + let Some(x) = vmcalliter!(vm; iter.clone())? else { + return Ok(Value::Nil) + }; + *state.borrow_mut() = Step::Going; + Ok(x.into()) + }, + Step::End => Ok(Value::Nil), + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(1)] +pub fn rev(_: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let lst = RefCell::new(None); + let f = move |vm: &mut Vm, _| { + if lst.borrow().is_none() { + let mut l = Vec::new(); + while let Some(x) = vmcalliter!(vm; iter.clone())? { + l.push(cell_take(x)) + } + l.reverse(); + *lst.borrow_mut() = Some(l.into_iter()); + } + match lst.borrow_mut().as_mut().unwrap().next() { + Some(v) => Ok(v.to_cell()), + None => Ok(Value::Nil), + } + + }; + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + + +// +// end iteration +// + + + +#[native_func(1)] +pub fn list(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + let mut result = Vec::new(); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + result.push(cell_take(value)); + }; + Ok(result.into()) +} + +#[native_func(1)] +pub fn table(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + let mut result = HashMap::new(); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + let Value::List(l) = cell_take(value) else { + throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list") + }; + let Ok([k, v]): std::result::Result<[Value; 2], Vec> = cell_take(l).try_into() else { + throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list of length 2") + }; + result.insert(k.try_into()?, v); + }; + Ok(result.into()) +} + +#[native_func(1)] +pub fn len(vm: &mut Vm, args: Vec) -> Result { + let [_, value] = unpack_args!(args); + match value { + Value::String(s) => return Ok((s.len() as i64).into()), + Value::List(l) => return Ok((l.borrow().len() as i64).into()), + Value::Table(t) => return Ok((t.borrow().len() as i64).into()), + Value::Range(r) if r.ty != RangeType::Endless + => return Ok((r.len().unwrap() as i64).into()), + _ => (), + } + let iter = value.to_iter_function()?; + let mut len = 0; + while vmcalliter!(vm; iter.clone())?.is_some() { + len += 1; + }; + Ok(len.into()) +} + +#[native_func(2)] +pub fn fold(vm: &mut Vm, args: Vec) -> Result { + let [_, func, iter] = unpack_args!(args); + let iter: Value = iter.to_iter_function()?; + + let mut result = match vmcalliter!(vm; iter.clone())? { + Some(v) => cell_take(v), + None => return Ok(Value::Nil), + }; + while let Some(value) = vmcalliter!(vm; iter.clone())? { + result = vmcall!(vm; func.clone(), result, cell_take(value))?; + } + Ok(result) +} + +#[native_func(3)] +pub fn foldi(vm: &mut Vm, args: Vec) -> Result { + let [_, init, func, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut result = init; + while let Some(value) = vmcalliter!(vm; iter.clone())? { + result = vmcall!(vm; func.clone(), result, cell_take(value))?; + } + Ok(result) +} + +#[native_func(1)] +pub fn sum(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut result = Value::Int(0); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + result = (result + cell_take(value))? + } + Ok(result) +} + +#[native_func(1)] +pub fn prod(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut result = Value::Int(1); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + result = (result * cell_take(value))? + } + Ok(result) +} + +#[native_func(1)] +pub fn any(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + while let Some(value) = vmcalliter!(vm; iter.clone())? { + if RefCell::borrow(&value).truthy() { + return Ok(true.into()) + } + } + Ok(false.into()) +} + +#[native_func(1)] +pub fn all(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + while let Some(value) = vmcalliter!(vm; iter.clone())? { + if !RefCell::borrow(&value).truthy() { + return Ok(false.into()) + } + } + Ok(true.into()) +} + +#[native_func(1)] +pub fn last(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut last = Rc::new(RefCell::new(Value::Nil)); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + last = value; + } + Ok(cell_take(last)) +} + +#[native_func(2)] +pub fn nth(vm: &mut Vm, args: Vec) -> Result { + let [_, n, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + let Value::Int(n) = n else { + throw!(*SYM_TYPE_ERROR, "nth expected integer") + }; + + for _ in 0..n { + if vmcalliter!(vm; iter.clone())?.is_none() { + return Ok(Value::Nil) + } + } + match vmcalliter!(vm; iter)? { + Some(v) => Ok(cell_take(v)), + None => Ok(Value::Nil), + } +} + +#[native_func(2)] +pub fn contains(vm: &mut Vm, args: Vec) -> Result { + let [_, val, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + for _ in 0.. { + match vmcalliter!(vm; iter.clone())? { + None => return Ok(Value::Nil), + Some(v) => if *RefCell::borrow(&v) == val { + return Ok(true.into()) + } + } + } + Ok(false.into()) +} + +#[native_func(2)] +pub fn index_of(vm: &mut Vm, args: Vec) -> Result { + let [_, val, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + for i in 0.. { + match vmcalliter!(vm; iter.clone())? { + None => return Ok(Value::Nil), + Some(v) => if *RefCell::borrow(&v) == val { + return Ok(i.into()) + } + } + } + Ok(Value::Nil) +} + +#[native_func(2)] +pub fn index_if(vm: &mut Vm, args: Vec) -> Result { + let [_, func, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + for i in 0.. { + match vmcalliter!(vm; iter.clone())? { + None => return Ok(Value::Nil), + Some(v) => if vmcall!(vm; func.clone(), cell_take(v))?.truthy() { + return Ok(i.into()) + } + } + } + Ok(Value::Nil) +} + +#[native_func(2)] +pub fn find(vm: &mut Vm, args: Vec) -> Result { + let [_, func, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + loop { + match vmcalliter!(vm; iter.clone())? { + None => return Ok(Value::Nil), + Some(v) => { + let v = cell_take(v); + if vmcall!(vm; func.clone(), v.clone())?.truthy() { + return Ok(v) + } + } + } + } +} + +#[native_func(1)] +pub fn count(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut map = HashMap::new(); + + while let Some(v) = vmcalliter!(vm; iter.clone())? { + let v = cell_take(v); + let hv = v.try_into()?; + map.entry(hv) + .and_modify(|v: &mut Value| *v = (v.clone() + Value::Int(1)).unwrap()) + .or_insert(Value::Int(1)); + } + + Ok(map.into()) +} + diff --git a/talc-std/src/lib.rs b/talc-std/src/lib.rs index 2856dee..3b5e2cd 100644 --- a/talc-std/src/lib.rs +++ b/talc-std/src/lib.rs @@ -1,17 +1,47 @@ use talc_lang::{symbol::{symbol, Symbol}, Vm}; -pub mod num; +pub mod value; pub mod io; pub mod iter; pub mod exception; +pub mod num; +pub mod collection; +#[cfg(feature="random")] +pub mod random; pub fn load_all(vm: &mut Vm) { - num::load(vm); - io::load(vm); - iter::load(vm); - exception::load(vm); + value::load(vm); + exception::load(vm); + iter::load(vm); + collection::load(vm); + num::load(vm); + io::load(vm); + #[cfg(feature="random")] + random::load(vm); } lazy_static::lazy_static! { - pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error); + pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error); } + + +macro_rules! unpack_args { + ($e:expr) => { + ($e).try_into().expect("bypassed arity check") + }; +} + +pub(crate) use unpack_args; + +macro_rules! unpack_varargs { + ($e:expr) => {{ + let mut args = $e; + let Value::List(varargs) = args.pop().expect("bypassed arity check") else { + panic!("bypassed arity check") + }; + let varargs = ::std::rc::Rc::unwrap_or_clone(varargs).into_inner(); + (args.try_into().expect("bypassed arity check"), varargs) + }}; +} + +pub(crate) use unpack_varargs; diff --git a/talc-std/src/num.rs b/talc-std/src/num.rs index 9e6fec5..ed6d386 100644 --- a/talc-std/src/num.rs +++ b/talc-std/src/num.rs @@ -1,5 +1,679 @@ -use talc_lang::Vm; +use std::cmp::Ordering; + +use lazy_static::lazy_static; +use talc_lang::{parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{exception::Result, Complex64, Value}, Vm}; +use talc_macros::native_func; + +use crate::{unpack_args, unpack_varargs}; + +lazy_static! { + static ref SYM_NAN: Symbol = Symbol::get("nan"); + static ref SYM_INFINITE: Symbol = Symbol::get("infinite"); + static ref SYM_ZERO: Symbol = Symbol::get("zero"); + static ref SYM_SUBNORMAL: Symbol = Symbol::get("subnormal"); + static ref SYM_NORMAL: Symbol = Symbol::get("normal"); +} + +#[inline] +fn to_floaty(v: Value) -> Value { + match v { + Value::Int(v) => Value::Float(v as f64), + Value::Ratio(v) => Value::Float(*v.numer() as f64 / *v.denom() as f64), + Value::Float(v) => Value::Float(v), + Value::Complex(v) => Value::Complex(v), + v => v, + } +} + +#[inline] +fn to_complex(v: Value) -> Value { + match v { + Value::Int(v) => Value::Complex((v as f64).into()), + Value::Ratio(v) => Value::Complex((*v.numer() as f64 / *v.denom() as f64).into()), + Value::Float(v) => Value::Complex(v.into()), + Value::Complex(v) => Value::Complex(v), + v => v, + } +} + +// +// load +// pub fn load(vm: &mut Vm) { + vm.set_global_name("e", std::f64::consts::E.into()); + vm.set_global_name("tau", std::f64::consts::TAU.into()); + vm.set_global_name("pi", std::f64::consts::PI.into()); + vm.set_global_name("egamma", 0.5772156649015329.into()); + vm.set_global_name("phi", 1.618033988749895.into()); + + vm.set_global_name("inf", (f64::INFINITY).into()); + vm.set_global_name("NaN", (f64::NAN).into()); + vm.set_global_name("infi", Complex64::new(0.0,f64::INFINITY).into()); + vm.set_global_name("NaNi", Complex64::new(0.0,f64::NAN).into()); + + vm.set_global_name("bin", bin().into()); + vm.set_global_name("sex", sex().into()); + vm.set_global_name("oct", oct().into()); + vm.set_global_name("hex", hex().into()); + vm.set_global_name("to_radix", to_radix().into()); + vm.set_global_name("from_radix", from_radix().into()); + + vm.set_global_name("gcd", gcd().into()); + vm.set_global_name("lcm", lcm().into()); + vm.set_global_name("isqrt", isqrt().into()); + vm.set_global_name("isprime", isprime().into()); + vm.set_global_name("factors", factors().into()); + + vm.set_global_name("min", min().into()); + vm.set_global_name("max", max().into()); + vm.set_global_name("floor", floor().into()); + vm.set_global_name("ceil", ceil().into()); + vm.set_global_name("round", round().into()); + vm.set_global_name("trunc", trunc().into()); + vm.set_global_name("fract", fract().into()); + vm.set_global_name("sign", sign().into()); + + vm.set_global_name("signum", signum().into()); + vm.set_global_name("classify", classify().into()); + vm.set_global_name("isnan", isnan().into()); + vm.set_global_name("isfinite", isfinite().into()); + vm.set_global_name("isinfinite", isinfinite().into()); + + vm.set_global_name("numer", numer().into()); + vm.set_global_name("denom", denom().into()); + + vm.set_global_name("re", re().into()); + vm.set_global_name("im", im().into()); + vm.set_global_name("arg", arg().into()); + vm.set_global_name("abs", abs().into()); + vm.set_global_name("abs_sq", abs_sq().into()); + + vm.set_global_name("sqrt", sqrt().into()); + vm.set_global_name("cbrt", cbrt().into()); + vm.set_global_name("ln", cbrt().into()); + vm.set_global_name("log2", cbrt().into()); + vm.set_global_name("exp", cbrt().into()); + vm.set_global_name("exp2", cbrt().into()); + + vm.set_global_name("sin", sin().into()); + vm.set_global_name("cos", cos().into()); + vm.set_global_name("tan", tan().into()); + vm.set_global_name("asin", asin().into()); + vm.set_global_name("acos", acos().into()); + vm.set_global_name("atan", atan().into()); + vm.set_global_name("sinh", sinh().into()); + vm.set_global_name("cosh", cosh().into()); + vm.set_global_name("tanh", tanh().into()); + vm.set_global_name("asinh", asinh().into()); + vm.set_global_name("acosh", acosh().into()); + vm.set_global_name("atanh", atanh().into()); + vm.set_global_name("atanh", atanh().into()); + vm.set_global_name("atan2", atan2().into()); +} + +// +// base conversions +// + +fn to_radix_inner(n: i64, radix: u32) -> String { + let mut result = vec![]; + let mut begin = 0; + + let mut x; + if n < 0 { + result.push('-' as u32 as u8); + begin = 1; + x = (-n) as u64 + } else { + x = n as u64 + }; + + loop { + let m = x % (radix as u64); + x /= radix as u64; + + result.push(char::from_digit(m as u32, radix).unwrap() as u8); + if x == 0 { + break; + } + } + result[begin..].reverse(); + String::from_utf8(result).expect("string was not valid utf8") +} + +#[native_func(1)] +pub fn bin(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "bin expected integer argument, got {x:#}") + }; + Ok(to_radix_inner(x, 2).into()) +} + +#[native_func(1)] +pub fn sex(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}") + }; + Ok(to_radix_inner(x, 6).into()) +} + +#[native_func(1)] +pub fn oct(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}") + }; + Ok(to_radix_inner(x, 8).into()) +} + +#[native_func(1)] +pub fn hex(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}") + }; + Ok(to_radix_inner(x, 16).into()) +} + +#[native_func(2)] +pub fn to_radix(_: &mut Vm, args: Vec) -> Result { + let [_, x, radix] = unpack_args!(args); + let (Value::Int(x), Value::Int(radix)) = (&x, &radix) else { + throw!(*SYM_TYPE_ERROR, "to_radix expected integer arguments, got {x:#} and {radix:#}") + }; + if *radix < 2 || *radix > 36 { + throw!(*SYM_TYPE_ERROR, "to_radix expected radix in range 0..=36") + } + Ok(to_radix_inner(*x, *radix as u32).into()) +} + +#[native_func(2)] +pub fn from_radix(_: &mut Vm, args: Vec) -> Result { + let [_, s, radix] = unpack_args!(args); + let (Value::String(s), Value::Int(radix)) = (&s, &radix) else { + throw!(*SYM_TYPE_ERROR, "from_radix expected string and integer arguments, got {s:#} and {radix:#}") + }; + if *radix < 2 || *radix > 36 { + throw!(*SYM_TYPE_ERROR, "from_radix expected radix in range 0..=36") + } + match parse_int(s, *radix as u32) { + Ok(v) => Ok(v.into()), + Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"), + } +} + +// +// integer operations +// + +fn isqrt_inner(mut n: i64) -> i64 { + assert!(n >= 0, "isqrt input should be nonnegative"); + if n < 2 { return n } + + let mut c = 0; + let mut d = 1 << 62; + + while d > n { + d >>= 2; + } + + while d != 0 { + if n >= c + d { + n -= c + d; + c = (c >> 1) + d; + } + else { + c >>= 1; + } + d >>= 2; + } + c +} + +#[native_func(1)] +pub fn isqrt(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "isqrt expected integer argument, got {x:#}") + }; + if x < 0 { + throw!(*SYM_TYPE_ERROR, "isqrt: argument must be positive") + } + Ok(isqrt_inner(x).into()) +} + +#[native_func(1)] +pub fn isprime(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "isprime expected integer argument, got {x:#}") + }; + if x < 2 { + return Ok(false.into()) + } + for p in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53] { + if x % p == 0 { + return Ok((x == p).into()) + } + } + if x < 59 { + return Ok(false.into()) + } + let lim = isqrt_inner(x); + let mut i = 54; + while i <= lim { + if x % (i + 1) == 0 { return Ok(false.into()) } + if x % (i + 5) == 0 { return Ok(false.into()) } + i += 6; + } + Ok(true.into()) +} + +fn gcd_inner(a: i64, b: i64) -> i64 { + let (mut a, mut b) = (a.abs(), b.abs()); + if a == 0 { + return b + } + if b == 0 { + return a + } + + let az = a.trailing_zeros(); + a >>= az; + let bz = b.trailing_zeros(); + b >>= bz; + let z = az.min(bz); + + loop { + if a > b { + std::mem::swap(&mut a, &mut b); + } + b -= a; + if b == 0 { + return a << z; + } + b >>= b.trailing_zeros(); + } } + +#[native_func(2..)] +pub fn gcd(_: &mut Vm, args: Vec) -> Result { + let ([_, x, y], rest) = unpack_varargs!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}") + }; + let Value::Int(y) = y else { + throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}") + }; + let mut g = gcd_inner(x, y); + for a in rest { + let Value::Int(a) = a else { + throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {a:#}") + }; + g = gcd_inner(g, a); + } + Ok(g.into()) +} + +#[native_func(2..)] +pub fn lcm(_: &mut Vm, args: Vec) -> Result { + let ([_, x, y], rest) = unpack_varargs!(args); + let Value::Int(x) = x else { + throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}") + }; + let Value::Int(y) = y else { + throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}") + }; + let mut g = gcd_inner(x, y); + let mut prod = y; + for a in rest { + let Value::Int(a) = a else { + throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {a:#}") + }; + prod *= a; + g = gcd_inner(g, a); + } + Ok((x/g * prod).into()) +} + +#[native_func(1)] +pub fn factors(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(mut x) = x else { + throw!(*SYM_TYPE_ERROR, "factords expected integer argument, got {x:#}") + }; + let mut factors = Vec::new(); + if x <= 1 { + return Ok(factors.into()) + } + while x & 1 == 0 { + x >>= 1; + factors.push(Value::Int(2)); + } + while x % 3 == 0 { + x /= 3; + factors.push(Value::Int(3)); + } + //let lim = isqrt_inner(x); + let mut i = 5; + while x > 1 { + while x % i == 0 { + x /= i; + factors.push(Value::Int(i)); + } + i += 2; + while x % i == 0 { + x /= i; + factors.push(Value::Int(i)); + } + i += 4; + } + Ok(factors.into()) +} + + +// +// numeric operations +// + +#[native_func(1..)] +pub fn min(_: &mut Vm, args: Vec) -> Result { + let ([_, mut x], rest) = unpack_varargs!(args); + for val in rest { + if val < x { + x = val; + } + } + Ok(x) +} + +#[native_func(1..)] +pub fn max(_: &mut Vm, args: Vec) -> Result { + let ([_, mut x], rest) = unpack_varargs!(args); + for val in rest { + if val > x { + x = val; + } + } + Ok(x) +} + +#[native_func(1)] +pub fn floor(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x)), + Value::Float(x) => Ok(Value::Float(x.floor())), + Value::Ratio(x) => Ok(Value::Ratio(x.floor())), + x => throw!(*SYM_TYPE_ERROR, "floor expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn ceil(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x)), + Value::Float(x) => Ok(Value::Float(x.ceil())), + Value::Ratio(x) => Ok(Value::Ratio(x.ceil())), + x => throw!(*SYM_TYPE_ERROR, "ceil expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn round(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x)), + Value::Float(x) => Ok(Value::Float(x.round())), + Value::Ratio(x) => Ok(Value::Ratio(x.round())), + x => throw!(*SYM_TYPE_ERROR, "round expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn trunc(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x)), + Value::Float(x) => Ok(Value::Float(x.trunc())), + Value::Ratio(x) => Ok(Value::Ratio(x.trunc())), + x => throw!(*SYM_TYPE_ERROR, "trunc expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn fract(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(_) => Ok(Value::Int(0)), + Value::Float(x) => Ok(Value::Float(x.fract())), + Value::Ratio(x) => Ok(Value::Ratio(x.fract())), + x => throw!(*SYM_TYPE_ERROR, "fract expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn sign(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x.signum())), + Value::Float(x) => Ok(Value::Float(if x == 0.0 { 0.0 } else { x.signum() })), + Value::Ratio(x) => match x.cmp(&0.into()) { + Ordering::Greater => Ok(Value::Ratio(1.into())), + Ordering::Less => Ok(Value::Ratio((-1).into())), + Ordering::Equal => Ok(Value::Ratio(0.into())), + } + x => throw!(*SYM_TYPE_ERROR, "sign expected real argument, got {x:#}"), + } +} + +// +// floating-point operations +// + + +#[native_func(1)] +pub fn signum(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match to_floaty(x) { + Value::Float(x) => Ok(Value::Float(x.signum())), + x => throw!(*SYM_TYPE_ERROR, "signum expected real argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn classify(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let x = to_floaty(x); + let Value::Float(x) = x else { + throw!(*SYM_TYPE_ERROR, "classify expected real argument, got {x:#}") + }; + Ok(match x.classify() { + std::num::FpCategory::Nan => *SYM_NAN, + std::num::FpCategory::Infinite => *SYM_INFINITE, + std::num::FpCategory::Zero => *SYM_ZERO, + std::num::FpCategory::Subnormal => *SYM_SUBNORMAL, + std::num::FpCategory::Normal => *SYM_NORMAL, + }.into()) +} + +#[native_func(1)] +pub fn isnan(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(_) | Value::Ratio(_) => Ok(false.into()), + Value::Float(x) => Ok(x.is_nan().into()), + Value::Complex(z) => Ok(z.is_nan().into()), + v => throw!(*SYM_TYPE_ERROR, "isnan expected numeric argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn isfinite(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(_) | Value::Ratio(_) => Ok(true.into()), + Value::Float(x) => Ok(x.is_finite().into()), + Value::Complex(z) => Ok(z.is_finite().into()), + v => throw!(*SYM_TYPE_ERROR, "isfinite expected numeric argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn isinfinite(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(_) | Value::Ratio(_) => Ok(false.into()), + Value::Float(x) => Ok(x.is_infinite().into()), + Value::Complex(z) => Ok(z.is_infinite().into()), + v => throw!(*SYM_TYPE_ERROR, "isinfinite expected numeric argument, got {v:#}"), + } +} + + +// +// rational operations +// + + +#[native_func(1)] +pub fn numer(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(x) => Ok(Value::Int(x)), + Value::Ratio(x) => Ok(Value::Int(*x.numer())), + v => throw!(*SYM_TYPE_ERROR, "numer expected rational argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn denom(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(_) => Ok(Value::Int(1)), + Value::Ratio(x) => Ok(Value::Int(*x.denom())), + v => throw!(*SYM_TYPE_ERROR, "denom expected rational argument, got {v:#}"), + } +} + +// +// complex operations +// + + + +#[native_func(1)] +pub fn re(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match v { + Value::Int(x) => Ok(Value::Int(x)), + Value::Ratio(x) => Ok(Value::Ratio(x)), + Value::Float(x) => Ok(Value::Float(x)), + Value::Complex(z) => Ok(Value::Float(z.re)), + v => throw!(*SYM_TYPE_ERROR, "re expected numeric argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn im(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match to_complex(v) { + Value::Int(_) => Ok(Value::Int(0)), + Value::Ratio(_) => Ok(Value::Ratio(0.into())), + Value::Float(_) => Ok(Value::Float(0.0)), + Value::Complex(z) => Ok(Value::Float(z.im)), + v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn arg(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match to_complex(v) { + Value::Complex(z) => Ok(Value::Float(z.arg())), + v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"), + } +} + +#[native_func(1)] +pub fn abs(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x.abs())), + Value::Ratio(x) => Ok(Value::Ratio(if x < 0.into() { -x } else { x })), + Value::Float(x) => Ok(Value::Float(x.abs())), + Value::Complex(x) => Ok(Value::Float(x.norm())), + x => throw!(*SYM_TYPE_ERROR, "abs expected numeric argument, got {x:#}"), + } +} + +#[native_func(1)] +pub fn abs_sq(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + match x { + Value::Int(x) => Ok(Value::Int(x * x)), + Value::Ratio(x) => Ok(Value::Ratio(x * x)), + Value::Float(x) => Ok(Value::Float(x * x)), + Value::Complex(x) => Ok(Value::Float(x.norm_sqr())), + x => throw!(*SYM_TYPE_ERROR, "abs_sq expected numeric argument, got {x:#}"), + } +} + + + +// +// continuous operations +// + + +macro_rules! float_func { + ($name:ident) => { + #[native_func(1)] + pub fn $name(_: &mut Vm, args: Vec) -> Result { + let [_, v] = unpack_args!(args); + match to_floaty(v) { + Value::Float(x) => Ok(Value::Float(x.$name())), + Value::Complex(z) => Ok(Value::Complex(z.$name())), + v => throw!(*SYM_TYPE_ERROR, + "{} expected numeric argument, got {v:#}", stringify!($name)), + } + } + }; +} + +float_func!(sqrt); +float_func!(cbrt); +float_func!(ln); +float_func!(log2); +float_func!(exp); +float_func!(exp2); + +float_func!(sin); +float_func!(cos); +float_func!(tan); +float_func!(asin); +float_func!(acos); +float_func!(atan); +float_func!(sinh); +float_func!(cosh); +float_func!(tanh); +float_func!(asinh); +float_func!(acosh); +float_func!(atanh); + +#[native_func(2)] +pub fn atan2(_: &mut Vm, args: Vec) -> Result { + let [_, y, x] = unpack_args!(args); + match (to_floaty(y), to_floaty(x)) { + (Value::Float(y), Value::Float(x)) + => Ok(Value::Float(x.atan2(y))), + (y,x) => throw!(*SYM_TYPE_ERROR, + "atan2 expected real arguments, got {y:#} and {x:#}"), + } +} + diff --git a/talc-std/src/random.rs b/talc-std/src/random.rs new file mode 100644 index 0000000..c8e1610 --- /dev/null +++ b/talc-std/src/random.rs @@ -0,0 +1,50 @@ +use rand::Rng; +use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{cell_take, exception::Result, range::RangeType, Value}, vmcalliter, Vm}; +use talc_macros::native_func; + +use crate::unpack_args; + +pub fn load(vm: &mut Vm) { + vm.set_global_name("rand", rand().into()); + vm.set_global_name("rand_in", rand_in().into()); +} + +#[native_func(0)] +pub fn rand(_: &mut Vm, _: Vec) -> Result { + Ok(Value::Float(rand::thread_rng().gen())) +} + +#[native_func(1)] +pub fn rand_in(vm: &mut Vm, args: Vec) -> Result { + let [_, col] = unpack_args!(args); + match col { + Value::List(l) => { + let l = l.borrow(); + let i = rand::thread_rng().gen_range(0..l.len()); + Ok(l[i].clone()) + }, + Value::Table(t) => { + let t = t.borrow(); + let i = rand::thread_rng().gen_range(0..t.len()); + let key = t.keys().nth(i).unwrap(); + Ok(key.clone().into_inner()) + }, + Value::Range(r) => match r.ty { + RangeType::Open => Ok(Value::Int( + rand::thread_rng().gen_range(r.start..r.stop))), + RangeType::Closed => Ok(Value::Int( + rand::thread_rng().gen_range(r.start..=r.stop))), + RangeType::Endless => throw!(*SYM_TYPE_ERROR, "cannot select random element of endless range"), + }, + col => { + let iter = col.to_iter_function()?; + let mut values = Vec::new(); + while let Some(v) = vmcalliter!(vm; iter.clone())? { + values.push(v); + } + let i = rand::thread_rng().gen_range(0..values.len()); + let v = values.swap_remove(i); + Ok(cell_take(v)) + } + } +} diff --git a/talc-std/src/value.rs b/talc-std/src/value.rs new file mode 100644 index 0000000..ee5edf9 --- /dev/null +++ b/talc-std/src/value.rs @@ -0,0 +1,170 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use talc_lang::{exception, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, value::{exception::Result, HashValue, Value}, Vm}; +use talc_macros::native_func; + +use crate::unpack_args; + +pub fn load(vm: &mut Vm) { + vm.set_global_name("type", type_().into()); + vm.set_global_name("is", is().into()); + vm.set_global_name("as", as_().into()); + vm.set_global_name("copy", copy().into()); + + vm.set_global_name("str", str_().into()); + vm.set_global_name("repr", repr().into()); + + vm.set_global_name("cell", cell().into()); + vm.set_global_name("uncell", uncell().into()); + vm.set_global_name("cell_replace", cell_replace().into()); + vm.set_global_name("cell_take", cell_replace().into()); +} + +// +// types +// + + +#[native_func(1)] +pub fn type_(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + Ok(val.get_type().into()) +} + +#[native_func(2)] +pub fn is(_: &mut Vm, args: Vec) -> Result { + let [_, val, ty] = unpack_args!(args); + let Value::Symbol(ty) = ty else { + throw!(*SYM_TYPE_ERROR, "type expected symbol, got {ty:#}") + }; + Ok((val.get_type() == ty).into()) +} + +#[native_func(2)] +pub fn as_(_: &mut Vm, args: Vec) -> Result { + let [_, val, ty] = unpack_args!(args); + let Value::Symbol(ty) = ty else { + throw!(*SYM_TYPE_ERROR, "type expected symbol, got {ty:#}") + }; + if val.get_type() == ty { + return Ok(val) + } + match (val, ty.name().as_ref()) { + (_, "nil") => Ok(Value::Nil), + (v, "string") => Ok(Value::String(v.to_string().into())), + (v, "bool") => Ok(Value::Bool(v.truthy())), + + (Value::Symbol(s), "int") => Ok(Value::Int(s.id() as i64)), + + (Value::Int(x), "ratio") => Ok(Value::Ratio(x.into())), + (Value::Int(x), "float") => Ok(Value::Float(x as f64)), + (Value::Int(x), "complex") => Ok(Value::Complex((x as f64).into())), + (Value::Ratio(x), "float") => Ok(Value::Float(*x.numer() as f64 / *x.denom() as f64)), + (Value::Ratio(x), "complex") => Ok(Value::Complex((*x.numer() as f64 / *x.denom() as f64).into())), + (Value::Float(x), "complex") => Ok(Value::Complex(x.into())), + + (Value::String(s), "int") + => parse_int(&s, 10) + .map(|v| v.into()) + .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as integer")), + + (Value::String(s), "float") + => parse_float(&s) + .map(|v| v.into()) + .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as float")), + + (v, t) => throw!(*SYM_TYPE_ERROR, + "cannot convert value of type {} to type {t}", v.get_type().name()) + } +} + +pub fn copy_inner(value: Value) -> Result { + match value { + Value::Nil | Value::Bool(_) | Value::Symbol(_) + | Value::Int(_) | Value::Ratio(_) | Value::Float(_) + | Value::Complex(_) | Value::Range(_) | Value::String(_) + => Ok(value), + Value::Cell(c) => { + let c = copy_inner(talc_lang::value::cell_take(c))?; + Ok(RefCell::new(c).into()) + }, + Value::List(l) => { + let l = talc_lang::value::cell_take(l); + let v: Result> = l.into_iter() + .map(copy_inner) + .collect(); + Ok(v?.into()) + }, + Value::Table(t) => { + let t = talc_lang::value::cell_take(t); + let v: Result> = t.into_iter() + .map(|(k, v)| copy_inner(v).map(|v| (k, v))) + .collect(); + Ok(v?.into()) + }, + _ => throw!(*SYM_TYPE_ERROR, + "cannot copy value of type {}", value.get_type().name()) + } +} + +#[native_func(1)] +pub fn copy(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + copy_inner(val) +} + +// +// strings +// + + +#[native_func(1)] +pub fn str_(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + Ok(val.to_string().into()) +} + +#[native_func(1)] +pub fn repr(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + Ok(format!("{val:#}").into()) +} + +// +// cells +// + + +#[native_func(1)] +pub fn cell(_: &mut Vm, args: Vec) -> Result { + let [_, value] = unpack_args!(args); + Ok(RefCell::new(value).into()) +} + +#[native_func(1)] +pub fn uncell(_: &mut Vm, args: Vec) -> Result { + let [_, cell] = unpack_args!(args); + let Value::Cell(cell) = cell else { + throw!(*SYM_TYPE_ERROR, "value is not a cell") + }; + Ok(Rc::unwrap_or_clone(cell).into_inner()) +} + +#[native_func(2)] +pub fn cell_replace(_: &mut Vm, args: Vec) -> Result { + let [_, cell, value] = unpack_args!(args); + let Value::Cell(cell) = cell else { + throw!(*SYM_TYPE_ERROR, "value is not a cell") + }; + Ok(cell.replace(value)) +} + +#[native_func(1)] +pub fn cell_take(_: &mut Vm, args: Vec) -> Result { + let [_, cell] = unpack_args!(args); + let Value::Cell(cell) = cell else { + throw!(*SYM_TYPE_ERROR, "value is not a cell") + }; + Ok(cell.replace(Value::Nil)) +} +