diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs index adeafdf..2465db6 100644 --- a/talc-lang/src/chunk.rs +++ b/talc-lang/src/chunk.rs @@ -88,38 +88,54 @@ impl TryFrom for Symbol { #[derive(Clone, Copy, Debug, Default)] pub enum Instruction { #[default] - Nop, + Nop, // do nothing - LoadLocal(Arg24), - StoreLocal(Arg24), - NewLocal, - DropLocal(Arg24), + LoadLocal(Arg24), // push nth local onto stack + StoreLocal(Arg24), // pop stack into nth local + NewLocal, // pop stack into a new local + DropLocal(Arg24), // remove last n locals - LoadGlobal(Arg24), StoreGlobal(Arg24), + LoadGlobal(Arg24), // load global by id + StoreGlobal(Arg24), // store global by id - Const(Arg24), - Int(Arg24), - Symbol(Arg24), - Bool(bool), - Nil, + CloseOver(Arg24), // load nth local and convert to cell, write back a copy + Closure(Arg24), // load constant function and fill state from stack + LoadUpvalue(Arg24), // load a cell from closure state to new local + StoreUpvalue(Arg24), // load a cell from closure state to new local + LoadClosedLocal(Arg24), // load through cell in nth local + StoreClosedLocal(Arg24), // store through cell in nth local - Dup, DupTwo, Drop(Arg24), Swap, + Const(Arg24), // push nth constant + Int(Arg24), // push integer + Symbol(Arg24), // push symbol + Bool(bool), // push boolean + Nil, // push nil + + Dup, + DupTwo, + Drop(Arg24), + Swap, UnaryOp(UnaryOp), BinaryOp(BinaryOp), - NewList(u8), GrowList(u8), - NewTable(u8), GrowTable(u8), + NewList(u8), + GrowList(u8), + NewTable(u8), + GrowTable(u8), - Index, StoreIndex, + Index, + StoreIndex, Jump(Arg24), JumpTrue(Arg24), JumpFalse(Arg24), - IterBegin, IterTest(Arg24), + IterBegin, + IterTest(Arg24), - BeginTry(Arg24), EndTry, + BeginTry(Arg24), + EndTry, Call(u8), Return, @@ -137,6 +153,12 @@ impl std::fmt::Display for Instruction { Symbol::try_from(s).expect("symbol does not exist").name()), Self::StoreGlobal(s) => write!(f, "storeglobal {}", Symbol::try_from(s).expect("symbol does not exist").name()), + Self::CloseOver(n) => write!(f, "closeover {}", usize::from(n)), + Self::Closure(c) => write!(f, "closure {}", usize::from(c)), + Self::LoadUpvalue(n) => write!(f, "load_upval {}", usize::from(n)), + Self::StoreUpvalue(n) => write!(f, "store_upval {}", usize::from(n)), + Self::LoadClosedLocal(n) => write!(f, "load_closed {}", usize::from(n)), + Self::StoreClosedLocal(n) => write!(f, "store_closed {}", usize::from(n)), Self::Const(c) => write!(f, "const {}", usize::from(c)), Self::Int(i) => write!(f, "int {}", i64::from(i)), Self::Symbol(s) => write!(f, "symbol {}", diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index 13458c5..623b0f1 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -12,6 +12,7 @@ use crate::value::Value; pub struct Local { name: Rc, scope: usize, + closed: bool, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -46,12 +47,13 @@ impl<'a> Default for Compiler<'a> { let locals = vec![Local { name: lstr!("self").into(), scope: 0, + closed: false, }]; Self { mode: CompilerMode::Function, parent: None, chunk: Chunk::new(), - attrs: FuncAttrs { arity: 0, variadic: false }, + attrs: FuncAttrs::default(), scope: 0, locals, globals: Vec::new(), @@ -88,6 +90,7 @@ impl<'a> Compiler<'a> { new.locals.push(Local { name: (*arg).into(), scope: 0, + closed: false, }); } @@ -96,13 +99,15 @@ impl<'a> Compiler<'a> { pub fn finish(mut self) -> Function { self.emit(I::Return); - Function { chunk: Rc::new(self.chunk), attrs: self.attrs } + // TODO closure + Function::from_parts(Rc::new(self.chunk), self.attrs, Vec::new().into()) } pub fn finish_repl(mut self) -> (Function, Vec) { self.emit(I::Return); ( - Function { chunk: Rc::new(self.chunk), attrs: self.attrs }, + // TODO closure + Function::from_parts(Rc::new(self.chunk), self.attrs, Vec::new().into()), self.globals ) } @@ -128,6 +133,7 @@ impl<'a> Compiler<'a> { && matches!(instrs.get(instrs.len() - 2), Some(I::Dup)) && matches!(instrs.last(), Some( I::NewLocal | I::StoreLocal(_) | I::StoreGlobal(_) + | I::StoreClosedLocal(_) )) { // can't panic: checked that instrs.len() >= 2 @@ -144,7 +150,7 @@ impl<'a> Compiler<'a> { Some( I::Dup | I::Const(_) | I::Int(_) | I::Nil | I::Bool(_) | I::Symbol(_) - | I::LoadLocal(_) + | I::LoadLocal(_) | I::LoadClosedLocal(_) ) ); if poppable { @@ -221,12 +227,8 @@ impl<'a> Compiler<'a> { fn load_var(&mut self, name: &LStr) { match (self.resolve_local(name), self.resolve_global(name)) { - (Some(n), None) => { - self.emit(I::LoadLocal(Arg24::from_usize(n))); - }, - (Some(n), Some(m)) if n >= m => { - self.emit(I::LoadLocal(Arg24::from_usize(n))); - }, + (Some(n), None) => self.load_local(n), + (Some(n), Some(m)) if n >= m => self.load_local(n), _ => { let sym = Symbol::get(name); self.emit(I::LoadGlobal(Arg24::from_symbol(sym))); @@ -234,10 +236,18 @@ impl<'a> Compiler<'a> { } } + fn load_local(&mut self, n: usize) { + if self.locals[n].closed { + self.emit(I::LoadClosedLocal(Arg24::from_usize(n))); + } else { + self.emit(I::LoadLocal(Arg24::from_usize(n))); + } + } + fn declare_local(&mut self, name: &LStr) -> usize { if let Some(i) = self.resolve_local(name) { - if self.locals[i].scope == self.scope { - self.emit(I::StoreLocal(Arg24::from_usize(i))); + if self.locals[i].scope == self.scope && !self.locals[i].closed { + self.emit(I::StoreLocal(Arg24::from_usize(i))); return i; } } @@ -245,6 +255,7 @@ impl<'a> Compiler<'a> { self.locals.push(Local { name: name.into(), scope: self.scope, + closed: false, }); let i = self.locals.len() - 1; @@ -253,7 +264,11 @@ impl<'a> Compiler<'a> { } fn store_local(&mut self, i: usize) { - self.emit(I::StoreLocal(Arg24::from_usize(i))); + if self.locals[i].closed { + self.emit(I::StoreLocal(Arg24::from_usize(i))); + } else { + self.emit(I::StoreClosedLocal(Arg24::from_usize(i))); + } } fn store_global(&mut self, name: &LStr) { @@ -267,6 +282,7 @@ impl<'a> Compiler<'a> { self.globals.push(Local { name: name.into(), scope: self.scope, + closed: false, }); } diff --git a/talc-lang/src/symbol.rs b/talc-lang/src/symbol.rs index c96ec07..0c21de3 100644 --- a/talc-lang/src/symbol.rs +++ b/talc-lang/src/symbol.rs @@ -31,6 +31,7 @@ lazy_static! { pub static ref SYM_DATA: Symbol = symbol!(data); pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error); + pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error); pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error); pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error); pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error); diff --git a/talc-lang/src/value/function.rs b/talc-lang/src/value/function.rs index ca38366..68c65de 100644 --- a/talc-lang/src/value/function.rs +++ b/talc-lang/src/value/function.rs @@ -1,27 +1,30 @@ -use std::rc::Rc; +use std::{cell::RefCell, rc::Rc}; use crate::{chunk::Chunk, Vm, exception::Result}; -use super::Value; +use super::{Value, CellValue}; #[derive(Clone, Copy, Debug, Default)] pub struct FuncAttrs { pub arity: usize, - pub variadic: bool, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Function { pub attrs: FuncAttrs, pub chunk: Rc, + pub state: Box<[CellValue]>, } impl Function { - pub fn new(chunk: Rc, arity: usize) -> Self { - Self { chunk, attrs: FuncAttrs { arity, variadic: false } } + pub fn new(chunk: Rc, attrs: FuncAttrs, closes: usize) -> Self { + let v = Rc::new(RefCell::new(Value::Nil)); + let state = std::iter::repeat(v).take(closes).collect(); + Self::from_parts(chunk, attrs, state) } - pub fn new_variadic(chunk: Rc, arity: usize) -> Self { - Self { chunk, attrs: FuncAttrs { arity, variadic: true } } + + pub fn from_parts(chunk: Rc, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self { + Self { chunk, attrs, state } } } @@ -34,10 +37,7 @@ pub struct NativeFunc { 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 } } + Self { func, attrs: FuncAttrs { arity } } } } @@ -50,10 +50,9 @@ impl std::fmt::Debug for NativeFunc { } pub fn disasm_recursive(f: &Rc, w: &mut impl std::io::Write) -> std::io::Result<()> { - writeln!(w, "{} ({}{})", + writeln!(w, "{} ({})", Value::Function(f.clone()), - f.attrs.arity, - if f.attrs.variadic { ".." } else { "" })?; + f.attrs.arity)?; if !f.chunk.consts.is_empty() { writeln!(w, "constants")?; for (i, c) in f.chunk.consts.iter().enumerate() { diff --git a/talc-lang/src/value/mod.rs b/talc-lang/src/value/mod.rs index fe2020e..b567890 100644 --- a/talc-lang/src/value/mod.rs +++ b/talc-lang/src/value/mod.rs @@ -20,6 +20,7 @@ pub mod index; type RcList = Rc>>; type RcTable = Rc>>; +pub type CellValue = Rc>; #[derive(Clone, Debug, Default)] pub enum Value { diff --git a/talc-lang/src/value/ops.rs b/talc-lang/src/value/ops.rs index 4830cf0..aad92db 100644 --- a/talc-lang/src/value/ops.rs +++ b/talc-lang/src/value/ops.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, cmp::Ordering, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Shl, Shr, Sub}, rc::Rc}; -use num_complex::Complex64; +use num_complex::{Complex64, ComplexFloat}; use num_rational::Rational64; -use crate::{exception::{throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR}, value::range::RangeType, Vm}; +use crate::{exception::{throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::range::RangeType, Vm}; use super::{function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value}; @@ -120,10 +120,10 @@ 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(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer division by 0"), (V::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))), + (V::Ratio(_), V::Ratio(r)) if *r.numer() == 0 && *r.denom() != 0 + => throw!(*SYM_VALUE_ERROR, "rational division by 0"), (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)), (V::Float(x), V::Float(y)) => Ok(V::Float(x / y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x / y)), @@ -133,9 +133,10 @@ impl Div for Value { } #[allow(clippy::cast_sign_loss)] +#[inline] fn ipow(n: i64, p: u64) -> Result { match (n, p) { - (0, 0) => throw!(*SYM_TYPE_ERROR, "integer 0 raised to power 0"), + (0, 0) => throw!(*SYM_VALUE_ERROR, "integer 0 raised to power 0"), (0, _) => Ok(0), (_, 0) => Ok(1), (n, p) if p > u32::MAX as u64 => { @@ -148,6 +149,7 @@ fn ipow(n: i64, p: u64) -> Result { } #[allow(clippy::cast_sign_loss)] +#[inline] 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)?), @@ -159,10 +161,20 @@ impl Value { pub fn modulo(self, rhs: Value) -> Result { use Value as V; match promote(self, rhs) { + (V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer modulo by 0"), (V::Int(x), V::Int(y)) => Ok(V::Int(x.rem_euclid(y))), - (V::Ratio(_x), V::Ratio(_y)) => todo!("ratio modulo"), + (V::Ratio(_), V::Ratio(y)) if *y.numer() == 0 && *y.denom() != 0 + => throw!(*SYM_VALUE_ERROR, "rational modulo by 0"), + (V::Ratio(x), V::Ratio(y)) => { + let n = (x / y).floor(); + Ok(Value::Ratio(x - n * y)) + } (V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(y))), - (V::Complex(_x), V::Complex(_y)) => todo!("complex modulo"), + (V::Complex(x), V::Complex(y)) => { + let n = x / y; + let n = Complex64::new(n.re().floor(), n.im().floor()); + Ok(Value::Complex(x - n * y)) + } (l, r) => throw!(*SYM_TYPE_ERROR, "cannot modulo {l:#} and {r:#}") } } @@ -170,10 +182,16 @@ impl Value { pub fn int_div(self, rhs: Value) -> Result { use Value as V; match promote(self, rhs) { + (V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer division by 0"), (V::Int(x), V::Int(y)) => Ok(V::Int(x.div_euclid(y))), - (V::Ratio(_x), V::Ratio(_y)) => todo!("ratio integer division"), + (V::Ratio(_), V::Ratio(r)) if *r.numer() == 0 && *r.denom() != 0 + => throw!(*SYM_VALUE_ERROR, "rational division by 0"), + (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio((x / y).floor())), (V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(y))), - (V::Complex(_x), V::Complex(_y)) => todo!("complex integer division"), + (V::Complex(x), V::Complex(y)) => { + let n = x / y; + Ok(V::from(Complex64::new(n.re().floor(), n.im().floor()))) + } (l, r) => throw!(*SYM_TYPE_ERROR, "cannot integer divide {l:#} and {r:#}") } } diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index 72ff537..b6f9d2a 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -72,17 +72,13 @@ enum CallOutcome { Partial(Value), } -fn get_call_outcome(mut args: Vec) -> Result { +fn get_call_outcome(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 { + if argc == attrs.arity { Ok(CallOutcome::Call(args)) } else if argc > attrs.arity { throw!(*SYM_TYPE_ERROR, "too many arguments for function") @@ -92,19 +88,11 @@ fn get_call_outcome(mut args: Vec) -> Result { 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") - }; - let varargs = Rc::unwrap_or_clone(varargs).take(); - args.extend(varargs); - } + let args: Vec = args.clone().into_iter().chain(ia).collect(); vm.call_value(f.clone(), args) }; let nf = NativeFunc { - attrs: FuncAttrs { arity: remaining, variadic: attrs.variadic }, + attrs: FuncAttrs { arity: remaining }, func: Box::new(nf), }; Ok(CallOutcome::Partial(nf.into())) @@ -260,6 +248,49 @@ impl Vm { let v = self.pop(); self.globals.insert(sym, v); }, + I::CloseOver(n) => { + let n = usize::from(n); + let v = std::mem::replace(&mut frame.locals[n], Value::Nil); + let v = v.to_cell(); + frame.locals[n] = v.clone(); + self.push(v); + }, + I::Closure(n) => { + let f = frame.func.chunk.consts[usize::from(n)].clone(); + let Value::Function(f) = f else { + panic!("attempt to build closure from non-closure constant") + }; + let mut f = f.as_ref().clone(); + let mut args = Vec::with_capacity(f.state.len()); + for _ in 0..f.state.len() { + let Value::Cell(c) = self.pop() else { + panic!("attempt to build closure from non-cell local"); + }; + args.push(c); + } + f.state = args.into_boxed_slice(); + self.push(f.into()); + }, + I::LoadUpvalue(n) => { + let v = frame.func.state[usize::from(n)].clone(); + self.push(v.borrow().clone()); + }, + I::StoreUpvalue(n) => { + let v = frame.func.state[usize::from(n)].clone(); + *v.borrow_mut() = self.pop(); + }, + I::LoadClosedLocal(n) => { + let Value::Cell(c) = &frame.locals[usize::from(n)] else { + panic!("attempt to load from closed non-cell local"); + }; + self.push(c.borrow().clone()); + }, + I::StoreClosedLocal(n) => { + let Value::Cell(c) = &frame.locals[usize::from(n)] else { + panic!("attempt to store to closed non-cell local"); + }; + *c.borrow_mut() = self.pop(); + }, // [] -> [consts[n]] I::Const(n) => self.push(frame.func.chunk.consts[usize::from(n)].clone()), @@ -432,7 +463,7 @@ impl Vm { // before propagating exceptions let res = res?; - self.stack.push(res); + self.push(res); } else if let Value::Function(func) = &args[0] { if self.call_stack.len() + 1 >= self.stack_max { diff --git a/talc-macros/src/lib.rs b/talc-macros/src/lib.rs index 8b27ba6..7e1a1f6 100644 --- a/talc-macros/src/lib.rs +++ b/talc-macros/src/lib.rs @@ -1,17 +1,15 @@ use proc_macro::TokenStream; -use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt, Token}; +use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt}; 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 }) + Ok(Self { arity }) } } @@ -28,7 +26,6 @@ pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStre 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"); @@ -41,7 +38,6 @@ pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStre ::talc_lang::value::function::NativeFunc { attrs: ::talc_lang::value::function::FuncAttrs{ arity: #arity, - variadic: #variadic, }, func: Box::new(|#inputs| #output #block) } diff --git a/talc-std/src/collection.rs b/talc-std/src/collection.rs index 034d404..6716df9 100644 --- a/talc-std/src/collection.rs +++ b/talc-std/src/collection.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use talc_lang::{exception::{exception, Result}, symbol::SYM_TYPE_ERROR, throw, value::{function::NativeFunc, Value}, vmcall, Vm}; +use talc_lang::{exception::{exception, Result}, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{function::NativeFunc, Value}, vmcall, Vm}; use talc_macros::native_func; use crate::unpack_args; @@ -14,6 +14,9 @@ pub fn load(vm: &mut Vm) { 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()); + vm.set_global_name("pair", pair().into()); + vm.set_global_name("fst", fst().into()); + vm.set_global_name("snd", snd().into()); } #[native_func(2)] @@ -33,7 +36,7 @@ pub fn pop(_: &mut Vm, args: Vec) -> Result { 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")) + v.ok_or_else(|| exception!(*SYM_VALUE_ERROR, "attempt to pop empty list")) } #[native_func(1)] @@ -199,10 +202,42 @@ pub fn sort_key(vm: &mut Vm, args: Vec) -> Result { 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"), + None => throw!(*SYM_VALUE_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()) } + +#[native_func(2)] +pub fn pair(_: &mut Vm, args: Vec) -> Result { + let [_, x, y] = unpack_args!(args); + Ok(vec![x, y].into()) +} + +#[native_func(1)] +pub fn fst(_: &mut Vm, args: Vec) -> Result { + let [_, l] = unpack_args!(args); + let Value::List(l) = l else { + throw!(*SYM_TYPE_ERROR, "fst: expected list") + }; + let l = l.borrow(); + let [x, _] = l.as_slice() else { + throw!(*SYM_VALUE_ERROR, "fst: list must have length 2") + }; + Ok(x.clone()) +} + +#[native_func(1)] +pub fn snd(_: &mut Vm, args: Vec) -> Result { + let [_, l] = unpack_args!(args); + let Value::List(l) = l else { + throw!(*SYM_TYPE_ERROR, "snd: expected list") + }; + let l = l.borrow(); + let [_, y] = l.as_slice() else { + throw!(*SYM_VALUE_ERROR, "snd: list must have length 2") + }; + Ok(y.clone()) +} diff --git a/talc-std/src/exception.rs b/talc-std/src/exception.rs index edec195..270987c 100644 --- a/talc-std/src/exception.rs +++ b/talc-std/src/exception.rs @@ -1,26 +1,28 @@ -use talc_lang::{exception::{throw, Exception, Result}, symbol::SYM_TYPE_ERROR, value::Value, Vm}; +use talc_lang::{exception::{throw, Exception, Result}, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::Value, Vm}; use talc_macros::native_func; -use crate::{unpack_args, unpack_varargs}; +use crate::unpack_args; -#[native_func(1..)] +#[native_func(1)] pub fn throw(_: &mut Vm, args: Vec) -> Result { - 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"), - }) + let [_, arg] = unpack_args!(args); + let exc = match arg { + Value::Symbol(ty) => Exception::new(ty), + Value::List(l) => match l.borrow().as_slice() { + [Value::Symbol(ty)] | [Value::Symbol(ty), Value::Nil] + => Exception::new(*ty), + [Value::Symbol(ty), Value::Nil, data] + => Exception::new_with_data(*ty, data.clone()), + [Value::Symbol(ty), Value::String(s)] + => Exception::new_with_msg(*ty, s.clone()), + [Value::Symbol(ty), Value::String(s), data] + => Exception::new_with_msg_data(*ty, s.clone(), data.clone()), + [] | [_] | [_,_] | [_,_,_] => throw!(*SYM_TYPE_ERROR, "wrong argument for throw"), + [_,_,_,_,..] => throw!(*SYM_VALUE_ERROR, "too many elements in list argument for throw"), + } + _ => throw!(*SYM_TYPE_ERROR, "throw expected symbol or list"), + }; + Err(exc) } #[native_func(1)] @@ -30,6 +32,7 @@ pub fn rethrow(_: &mut Vm, args: Vec) -> Result { if let Some(e) = Exception::from_table(&table) { return Err(e) } + throw!(*SYM_VALUE_ERROR, "argument not a valid exception") } throw!(*SYM_TYPE_ERROR, "argument not a valid exception") } diff --git a/talc-std/src/file.rs b/talc-std/src/file.rs index a26a5e9..9b5968a 100644 --- a/talc-std/src/file.rs +++ b/talc-std/src/file.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, collections::HashMap, fs::{File, OpenOptions}, io::{BufRead, BufReader, BufWriter, ErrorKind, IntoInnerError, Read, Write}, net::{TcpListener, TcpStream, ToSocketAddrs}, os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, process::{Child, Command, Stdio}, time::Duration}; -use talc_lang::{exception::Result, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, Vm}; +use talc_lang::{exception::Result, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, Vm}; use talc_macros::native_func; use lazy_static::lazy_static; @@ -218,7 +218,7 @@ pub fn open(_: &mut Vm, args: Vec) -> Result { } else { match OPEN_OPT_MAP.get(&s) { Some(f) => f(&mut oo, true), - None => throw!(*SYM_TYPE_ERROR, "invalid option for open") + None => throw!(*SYM_VALUE_ERROR, "invalid option for open") }; } }, @@ -231,7 +231,7 @@ pub fn open(_: &mut Vm, args: Vec) -> Result { } else { match OPEN_OPT_MAP.get(s) { Some(f) => f(&mut oo, true), - None => throw!(*SYM_TYPE_ERROR, "invalid option for open") + None => throw!(*SYM_VALUE_ERROR, "invalid option for open") }; } }, @@ -256,7 +256,7 @@ pub fn read(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "read expected integer, got {nbytes:#}") }; let Ok(nbytes) = usize::try_from(nbytes) else { - throw!(*SYM_TYPE_ERROR, "number of bytes to read must be nonnegative") + throw!(*SYM_VALUE_ERROR, "number of bytes to read must be nonnegative") }; let Some(file): Option<&ValueFile> = file.downcast_native() else { throw!(*SYM_TYPE_ERROR, "read expected file, got {file:#}") @@ -310,7 +310,7 @@ pub fn read_until(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "read_until expected string, got {end:#}") }; if end.is_empty() { - throw!(*SYM_TYPE_ERROR, "read_until: end string must not be empty") + throw!(*SYM_VALUE_ERROR, "read_until: end string must not be empty") } let mut file = file.0.borrow_mut(); if let BufFile::Buffered { r, .. } = &mut *file { @@ -339,7 +339,7 @@ pub fn read_line(_: &mut Vm, args: Vec) -> Result { Err(e) => throw!(*SYM_IO_ERROR, "{e}") } } else { - throw!(*SYM_TYPE_ERROR, "read_line: file must be buffered") + throw!(*SYM_VALUE_ERROR, "read_line: file must be buffered") } } @@ -398,7 +398,7 @@ pub fn tcp_connect(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}") }; let Ok(addr) = addr.to_str() else { - throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8") }; match TcpStream::connect(addr) { Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) { @@ -416,12 +416,12 @@ pub fn tcp_connect_timeout(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected string, got {addr:#}") }; let Ok(addr) = addr.to_str() else { - throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8") }; let timeout = match timeout { Value::Int(n) if n >= 0 => Duration::from_secs(n as u64), Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => Duration::from_secs_f64(n), - Value::Int(_) | Value::Float(_) => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout: invalid timeout"), + Value::Int(_) | Value::Float(_) => throw!(*SYM_VALUE_ERROR, "tcp_connect_timeout: invalid timeout"), _ => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected int or float, got {timeout:#}") }; let mut addrs = match addr.to_socket_addrs() { @@ -464,7 +464,7 @@ pub fn tcp_listen(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}") }; let Ok(addr) = addr.to_str() else { - throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8") }; match TcpListener::bind(addr) { Ok(listener) => { @@ -508,7 +508,7 @@ fn spawn_opt_stdio( let stdio = unsafe { Stdio::from_raw_fd(fd) }; func(proc, stdio) }, - _ => throw!(*SYM_TYPE_ERROR, "{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}") + _ => throw!(*SYM_VALUE_ERROR, "{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}") }; Ok(()) } @@ -535,7 +535,7 @@ fn spawn_opt_delenv(fname: &str, proc: &mut Command, delenv: &Value) -> Result<( }; proc.env_remove(e.to_os_str()); }, - _ => throw!(*SYM_TYPE_ERROR, + _ => throw!(*SYM_VALUE_ERROR, "{fname} delenv option expected :all or list of strings, got {delenv:#}") } Ok(()) @@ -547,7 +547,7 @@ fn spawn_opt_env(fname: &str, proc: &mut Command, env: &Value) -> Result<()> { Value::Table(t) => for (k, v) in t.borrow().iter() { let Value::String(k) = k.inner() else { throw!(*SYM_TYPE_ERROR, - "{fname} efromnv option expected table with string keys, got {env:#}") + "{fname} env option expected table with string keys, got {env:#}") }; proc.env(k.to_os_str(), v.str().to_os_str()); }, diff --git a/talc-std/src/io.rs b/talc-std/src/io.rs index 2d89ead..791fbd3 100644 --- a/talc-std/src/io.rs +++ b/talc-std/src/io.rs @@ -1,6 +1,6 @@ use std::{io::{BufRead, Write}, os::unix::ffi::OsStrExt, time::{SystemTime, UNIX_EPOCH}}; -use talc_lang::{exception::{throw, Result}, lstring::LString, symbol::SYM_TYPE_ERROR, value::Value, vmcall, Vm}; +use talc_lang::{exception::{throw, Result}, lstring::LString, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::Value, vmcall, Vm}; use talc_macros::native_func; use crate::{unpack_args, SYM_IO_ERROR}; @@ -70,7 +70,7 @@ pub fn env(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "env expected string, got {key:#}") }; if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { - throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + throw!(*SYM_VALUE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") } let val = std::env::var_os(key.to_os_str()); match val { @@ -86,11 +86,11 @@ pub fn setenv(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "setenv expected string, got {key:#}") }; if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { - throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + throw!(*SYM_VALUE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") } let val = val.str(); if val.as_bytes().contains(&0) { - throw!(*SYM_TYPE_ERROR, "environment variable value must not contain a null byte") + throw!(*SYM_VALUE_ERROR, "environment variable value must not contain a null byte") } std::env::set_var(key.to_os_str(), val.to_os_str()); Ok(Value::Nil) @@ -103,7 +103,7 @@ pub fn delenv(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "delenv expected string, got {key:#}") }; if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { - throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + throw!(*SYM_VALUE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") } std::env::remove_var(key.to_os_str()); Ok(Value::Nil) diff --git a/talc-std/src/iter.rs b/talc-std/src/iter.rs index e90b330..3ac685b 100644 --- a/talc-std/src/iter.rs +++ b/talc-std/src/iter.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use talc_lang::{symbol::SYM_TYPE_ERROR, exception::Result, throw, value::{function::NativeFunc, range::RangeType, HashValue, Value}, vmcall, vmcalliter, Vm}; +use talc_lang::{exception::Result, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{function::NativeFunc, ops::RatioExt, range::RangeType, HashValue, Value}, vmcall, vmcalliter, Vm}; use talc_macros::native_func; -use crate::{unpack_args, unpack_varargs}; +use crate::unpack_args; pub fn load(vm: &mut Vm) { // begin @@ -26,7 +26,9 @@ pub fn load(vm: &mut Vm) { // join vm.set_global_name("zip", zip().into()); + vm.set_global_name("zipn", zipn().into()); vm.set_global_name("alternate", alternate().into()); + vm.set_global_name("alternaten", alternaten().into()); vm.set_global_name("intersperse", intersperse().into()); vm.set_global_name("cartprod", cartprod().into()); vm.set_global_name("chain", chain().into()); @@ -48,6 +50,12 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("index_if", index_if().into()); vm.set_global_name("find", find().into()); vm.set_global_name("count", count().into()); + vm.set_global_name("mean", mean().into()); + vm.set_global_name("variance", variance().into()); + vm.set_global_name("stdev", stdev().into()); + vm.set_global_name("pvariance", pvariance().into()); + vm.set_global_name("pstdev", pstdev().into()); + vm.set_global_name("median", median().into()); } // @@ -181,10 +189,10 @@ pub fn filter(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_TYPE_ERROR, "take expected integer, got {count:#}") }; let Ok(count) = count.try_into() else { - throw!(*SYM_TYPE_ERROR, "take expected nonnegative integer") + throw!(*SYM_VALUE_ERROR, "take expected nonnegative integer, got {count:#}") }; let iter = iter.to_iter_function()?; @@ -207,10 +215,10 @@ pub fn take(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_TYPE_ERROR, "count expected integer, got {count:#}") }; let Ok(count) = count.try_into() else { - throw!(*SYM_TYPE_ERROR, "count expected nonnegative integer") + throw!(*SYM_VALUE_ERROR, "count expected nonnegative integer, got {count:#}") }; let iter = iter.to_iter_function()?; @@ -292,10 +300,10 @@ 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") + throw!(*SYM_TYPE_ERROR, "step expected integer, got {by:#}") }; if by <= 0 { - throw!(*SYM_TYPE_ERROR, "step expected positive integer") + throw!(*SYM_VALUE_ERROR, "step expected positive integer, got {by:#}") } let state = RefCell::new(Step::First); @@ -350,13 +358,37 @@ pub fn rev(_: &mut Vm, args: Vec) -> Result { // -#[native_func(2..)] +#[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 [_, i1, i2] = unpack_args!(args); + let i1 = i1.to_iter_function()?; + let i2 = i2.to_iter_function()?; + + let f = move |vm: &mut Vm, _| { + let mut res = Vec::with_capacity(2); + match vmcalliter!(vm; i1.clone())? { + Some(v) => res.push(v), + None => return Ok(Value::iter_pack(None)), + }; + match vmcalliter!(vm; i2.clone())? { + Some(v) => res.push(v), + None => return Ok(Value::iter_pack(None)), + }; + Ok(Value::from(res)) + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(1)] +pub fn zipn(_: &mut Vm, args: Vec) -> Result { + let [_, args] = unpack_args!(args); + let Value::List(args) = args else { + throw!(*SYM_TYPE_ERROR, "zipn expected list, got {args:#}") + }; + let iters = args.borrow().iter() + .map(|i| i.clone().to_iter_function()) + .collect::>>()?; let f = move |vm: &mut Vm, _| { let mut res = Vec::with_capacity(iters.len()); @@ -372,13 +404,42 @@ pub fn zip(_: &mut Vm, args: Vec) -> Result { Ok(NativeFunc::new(Box::new(f), 0).into()) } -#[native_func(2..)] +#[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 [_, i1, i2] = unpack_args!(args); + let i1 = i1.to_iter_function()?; + let i2 = i2.to_iter_function()?; + + let state = RefCell::new((false, false)); + let f = move |vm: &mut Vm, _| { + let mut s = state.borrow_mut(); + if s.1 { + return Ok(Value::iter_pack(None)); + } + let n = s.0; + s.0 = !s.0; + drop(s); + let iter = if n { i1.clone() } else { i2.clone() }; + if let Some(v) = vmcalliter!(vm; iter)? { + Ok(v) + } else { + state.borrow_mut().1 = true; + Ok(Value::iter_pack(None)) + } + }; + + Ok(NativeFunc::new(Box::new(f), 0).into()) +} + +#[native_func(1)] +pub fn alternaten(_: &mut Vm, args: Vec) -> Result { + let [_, args] = unpack_args!(args); + let Value::List(args) = args else { + throw!(*SYM_TYPE_ERROR, "alternaten expected list, got {args:#}") + }; + let iters = args.borrow().iter() + .map(|i| i.clone().to_iter_function()) + .collect::>>()?; let state = RefCell::new((0, false)); let f = move |vm: &mut Vm, _| { @@ -556,12 +617,14 @@ pub fn table(vm: &mut Vm, args: Vec) -> Result { let mut result = HashMap::new(); while let Some(value) = vmcalliter!(vm; iter.clone())? { let Value::List(l) = value else { - throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list") + throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list, got {value:#}") }; - let l = Rc::unwrap_or_clone(l).take(); - let Ok([k, v]): std::result::Result<[Value; 2], Vec> = l.try_into() else { - throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list of length 2") + let mut l = Rc::unwrap_or_clone(l).take(); + if l.len() != 2 { + throw!(*SYM_VALUE_ERROR, "table: iterator yielded list of length {} (expected 2)", l.len()) }; + let v = l.pop().unwrap(); + let k = l.pop().unwrap(); result.insert(k.try_into()?, v); }; Ok(result.into()) @@ -775,3 +838,110 @@ pub fn count(vm: &mut Vm, args: Vec) -> Result { Ok(map.into()) } +#[native_func(1)] +pub fn mean(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut sum = Value::Float(0.0); + let mut count = Value::Int(0); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + sum = (sum + value)?; + count = (count + Value::from(1))?; + } + sum / count +} + +fn variance_inner(vm: &mut Vm, iter: Value, pop: bool) -> Result { + let mut m = Value::Float(0.0); + let mut s = Value::Float(0.0); + let mut k = 1; + while let Some(value) = vmcalliter!(vm; iter.clone())? { + let old_m = m.clone(); + m = (m.clone() + ((value.clone() - m.clone())?/Value::Int(k))?)?; + s = (s + ((value.clone() - m.clone())?*(value - old_m)?)?)?; + k += 1; + } + s / Value::Int(k - if pop { 1 } else { 2 }) +} + +#[native_func(1)] +pub fn variance(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + variance_inner(vm, iter, false) +} + +#[native_func(1)] +pub fn stdev(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let v = variance_inner(vm, iter, false)?; + Ok(match v { + Value::Int(n) => Value::Float((n as f64).sqrt()), + Value::Float(f) => Value::Float(f.sqrt()), + Value::Ratio(r) => Value::Float(r.to_f64().sqrt()), + Value::Complex(c) => Value::Complex(c.sqrt()), + v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}") + }) +} + +#[native_func(1)] +pub fn pvariance(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + variance_inner(vm, iter, true) +} + +#[native_func(1)] +pub fn pstdev(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let v = variance_inner(vm, iter, true)?; + Ok(match v { + Value::Int(n) => Value::Float((n as f64).sqrt()), + Value::Float(f) => Value::Float(f.sqrt()), + Value::Ratio(r) => Value::Float(r.to_f64().sqrt()), + Value::Complex(c) => Value::Complex(c.sqrt()), + v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}") + }) +} + +#[derive(PartialEq, PartialOrd)] +struct OrdValue(Value); +impl std::cmp::Eq for OrdValue {} +impl std::cmp::Ord for OrdValue { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Less) + } +} + +#[native_func(1)] +pub fn median(vm: &mut Vm, args: Vec) -> Result { + let [_, iter] = unpack_args!(args); + let iter = iter.to_iter_function()?; + + let mut vals = Vec::new(); + while let Some(value) = vmcalliter!(vm; iter.clone())? { + vals.push(OrdValue(value)); + } + let count = vals.len(); + if count == 0 { + Ok(Value::Nil) + } else if count % 2 == 0 { + let (_, _, hi) = vals.select_nth_unstable(count/2 - 1); + let (_, _, _) = hi.select_nth_unstable(0); + let m2 = vals.swap_remove(count/2); + let m1 = vals.swap_remove(count/2 - 1); + (m1.0 + m2.0)? / Value::Int(2) + } else { + let (_, _, _) = vals.select_nth_unstable(count/2); + let m = vals.swap_remove(count/2); + m.0 / Value::Int(1) + } +} + diff --git a/talc-std/src/lib.rs b/talc-std/src/lib.rs index 03af428..0893a9e 100644 --- a/talc-std/src/lib.rs +++ b/talc-std/src/lib.rs @@ -28,6 +28,7 @@ pub fn load_all(vm: &mut Vm) { lazy_static::lazy_static! { pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error); + pub static ref SYM_FORMAT_ERROR: Symbol = symbol!(format_error); } @@ -38,16 +39,3 @@ macro_rules! unpack_args { } 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 2ac96d9..dbff4e9 100644 --- a/talc-std/src/num.rs +++ b/talc-std/src/num.rs @@ -1,10 +1,10 @@ use std::cmp::Ordering; use lazy_static::lazy_static; -use talc_lang::{exception::Result, lstring::LString, parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{ops::RatioExt, Complex64, Value}, Vm}; +use talc_lang::{exception::Result, lstring::LString, parse_int, symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{ops::RatioExt, Complex64, Value}, vmcalliter, Vm}; use talc_macros::native_func; -use crate::{unpack_args, unpack_varargs}; +use crate::unpack_args; lazy_static! { static ref SYM_NAN: Symbol = Symbol::get("nan"); @@ -61,7 +61,9 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("from_radix", from_radix().into()); vm.set_global_name("gcd", gcd().into()); + vm.set_global_name("gcdn", gcdn().into()); vm.set_global_name("lcm", lcm().into()); + vm.set_global_name("lcmn", lcmn().into()); vm.set_global_name("isqrt", isqrt().into()); vm.set_global_name("isprime", isprime().into()); vm.set_global_name("factors", factors().into()); @@ -96,6 +98,7 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("cbrt", cbrt().into()); vm.set_global_name("ln", ln().into()); vm.set_global_name("log2", log2().into()); + vm.set_global_name("log10", log10().into()); vm.set_global_name("exp", exp().into()); vm.set_global_name("exp2", exp2().into()); @@ -191,7 +194,7 @@ pub fn to_radix(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_VALUE_ERROR, "to_radix expected radix in range 0..=36") } Ok(to_radix_inner(*x, *radix as u32, false).into()) } @@ -203,7 +206,7 @@ pub fn to_radix_upper(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "to_radix_upper expected integer arguments, got {x:#} and {radix:#}") }; if *radix < 2 || *radix > 36 { - throw!(*SYM_TYPE_ERROR, "to_radix_upper expected radix in range 0..=36") + throw!(*SYM_VALUE_ERROR, "to_radix_upper expected radix in range 0..=36") } Ok(to_radix_inner(*x, *radix as u32, true).into()) } @@ -215,11 +218,11 @@ pub fn from_radix(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_VALUE_ERROR, "from_radix expected radix in range 0..=36") } match parse_int(s.as_ref(), *radix as u32) { Ok(v) => Ok(v.into()), - Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"), + Err(_) => throw!(*SYM_VALUE_ERROR, "string was not a valid integer in given radix"), } } @@ -286,7 +289,7 @@ pub fn isqrt(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "isqrt expected integer argument, got {x:#}") }; if x < 0 { - throw!(*SYM_TYPE_ERROR, "isqrt: argument must be positive") + throw!(*SYM_VALUE_ERROR, "isqrt: argument must be positive") } Ok(isqrt_inner(x).into()) } @@ -319,44 +322,69 @@ pub fn isprime(_: &mut Vm, args: Vec) -> Result { Ok(true.into()) } -#[native_func(2..)] +#[native_func(2)] pub fn gcd(_: &mut Vm, args: Vec) -> Result { - let ([_, x, y], rest) = unpack_varargs!(args); + let [_, x, y] = unpack_args!(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:#}") + throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {y:#}") }; - let mut g = gcd_inner(x, y); - for a in rest { + Ok(gcd_inner(x, y).into()) +} + +#[native_func(1)] +pub fn gcdn(vm: &mut Vm, args: Vec) -> Result { + let [_, args] = unpack_args!(args); + let args = args.to_iter_function()?; + + let mut g = 0; + + while let Some(a) = vmcalliter!(vm; args.clone())? { let Value::Int(a) = a else { - throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {a:#}") + throw!(*SYM_TYPE_ERROR, "gcdn: cannot take gcd with {a:#}") }; g = gcd_inner(g, a); } + Ok(g.into()) } -#[native_func(2..)] +#[native_func(2)] pub fn lcm(_: &mut Vm, args: Vec) -> Result { - let ([_, x, y], rest) = unpack_varargs!(args); + let [_, x, y] = unpack_args!(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:#}") + throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {y:#}") }; - let mut g = gcd_inner(x, y); - let mut prod = y; - for a in rest { + let g = gcd_inner(x, y); + if g == 0 { + Ok(Value::from(0)) + } else { + Value::from(x/g) * Value::from(y) + } +} + +#[native_func(1)] +pub fn lcmn(vm: &mut Vm, args: Vec) -> Result { + let [_, args] = unpack_args!(args); + let args = args.to_iter_function()?; + + let mut l = 1; + + while let Some(a) = vmcalliter!(vm; args.clone())? { let Value::Int(a) = a else { - throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {a:#}") + throw!(*SYM_TYPE_ERROR, "lcmn: cannot take lcm with {a:#}") }; - prod *= a; - g = gcd_inner(g, a); + let g = gcd_inner(l, a); + if g == 0 { return Ok(Value::from(0)) }; + l = (l/g).wrapping_mul(a); } - Ok((x/g * prod).into()) + + Ok(Value::from(l)) } #[native_func(1)] @@ -401,26 +429,24 @@ pub fn factors(_: &mut Vm, args: Vec) -> Result { // numeric operations // -#[native_func(1..)] +#[native_func(2)] 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) + let [_, x, y] = unpack_args!(args); + if y < x { + Ok(y) + } else { + Ok(x) + } } -#[native_func(1..)] +#[native_func(2)] 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) + let [_, x, y] = unpack_args!(args); + if y > x { + Ok(y) + } else { + Ok(x) + } } #[native_func(1)] @@ -690,6 +716,7 @@ float_func!(sqrt); float_func!(cbrt); float_func!(ln); float_func!(log2); +float_func!(log10); float_func!(exp); float_func!(exp2); diff --git a/talc-std/src/random.rs b/talc-std/src/random.rs index ae5a4db..b3ccae0 100644 --- a/talc-std/src/random.rs +++ b/talc-std/src/random.rs @@ -1,5 +1,5 @@ use rand::{seq::SliceRandom, Rng}; -use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{range::RangeType, Value}, vmcalliter, Vm, exception::Result}; +use talc_lang::{exception::Result, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{range::RangeType, Value}, vmcalliter, Vm}; use talc_macros::native_func; use crate::unpack_args; @@ -22,14 +22,14 @@ pub fn rand_in(vm: &mut Vm, args: Vec) -> Result { Value::List(l) => { let l = l.borrow(); let Some(v) = l.choose(&mut rand::thread_rng()) else { - throw!(*SYM_TYPE_ERROR, "rand_in: empty list") + throw!(*SYM_VALUE_ERROR, "rand_in: empty list") }; Ok(v.clone()) }, Value::Table(t) => { let t = t.borrow(); if t.is_empty() { - throw!(*SYM_TYPE_ERROR, "rand_in: empty table") + throw!(*SYM_VALUE_ERROR, "rand_in: empty table") }; let i = rand::thread_rng().gen_range(0..t.len()); let key = t.keys().nth(i).unwrap(); @@ -37,14 +37,14 @@ pub fn rand_in(vm: &mut Vm, args: Vec) -> Result { }, Value::Range(r) => { if r.is_empty() { - throw!(*SYM_TYPE_ERROR, "rand_in: empty range") + throw!(*SYM_VALUE_ERROR, "rand_in: empty range") } 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, "rand_in: endless range"), + RangeType::Endless => throw!(*SYM_VALUE_ERROR, "rand_in: endless range"), } } col => { @@ -54,7 +54,7 @@ pub fn rand_in(vm: &mut Vm, args: Vec) -> Result { values.push(v); } if values.is_empty() { - throw!(*SYM_TYPE_ERROR, "rand_in: empty iterator") + throw!(*SYM_VALUE_ERROR, "rand_in: empty iterator") } let i = rand::thread_rng().gen_range(0..values.len()); let v = values.swap_remove(i); diff --git a/talc-std/src/regex.rs b/talc-std/src/regex.rs index 58af8a6..84b068e 100644 --- a/talc-std/src/regex.rs +++ b/talc-std/src/regex.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use talc_lang::{exception::{exception, Result}, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{NativeValue, Value}, Vm}; +use talc_lang::{exception::{exception, Result}, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{NativeValue, Value}, Vm}; use talc_macros::native_func; use regex::{Captures, Match, Regex}; use lazy_static::lazy_static; @@ -73,17 +73,17 @@ fn regex_from<'a>(v: &'a Value, name: &str) -> Result> { match v { Value::String(s) => { let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "regex must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "regex must be valid UTF-8") }; Regex::new(s) .map(Cow::Owned) - .map_err(|e| exception!(*SYM_TYPE_ERROR, "invalid regex: {e}")) + .map_err(|e| exception!(*SYM_VALUE_ERROR, "invalid regex: {e}")) }, Value::Native(n) if n.get_type() == *SYM_STD_REGEX => { n.as_any().downcast_ref::() .map(|vr| Cow::Borrowed(&vr.0)) .ok_or_else(|| exception!( - *SYM_TYPE_ERROR, "BEES {name} expected string or regex, got {v:#}")) + *SYM_TYPE_ERROR, "{name} expected string or regex, got {v:#}")) }, _ => throw!(*SYM_TYPE_ERROR, "{name} expected string or regex, got {v:#}") } @@ -103,7 +103,7 @@ pub fn matches(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "matches expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let re = regex_from(&re, "matches")?; Ok(re.is_match(s).into()) @@ -116,7 +116,7 @@ pub fn match_once(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "match_once expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let re = regex_from(&re, "match_once")?; Ok(re.find(s).map_or(Value::Nil, match_to_value)) @@ -129,7 +129,7 @@ pub fn _match(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "match expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let re = regex_from(&re, "match")?; Ok(re.find_iter(s).map(match_to_value).collect::>().into()) @@ -142,7 +142,7 @@ pub fn captures_once(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "captures_once expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let re = regex_from(&re, "captures_once")?; Ok(re.captures(s).map_or(Value::Nil, captures_to_value)) @@ -155,7 +155,7 @@ pub fn captures(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "captures expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let re = regex_from(&re, "captures")?; Ok(re.captures_iter(s).map(captures_to_value).collect::>().into()) @@ -171,10 +171,10 @@ pub fn replace_once(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "replace_once expected string or function, got {rep:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let Ok(rep) = rep.to_str() else { - throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "replacement string must be valid UTF-8") }; let re = regex_from(&re, "replace_once")?; Ok(LString::from(re.replace(s, rep)).into()) @@ -190,10 +190,10 @@ pub fn replace(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "replace expected string or function, got {rep:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8") }; let Ok(rep) = rep.to_str() else { - throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "replacement string must be valid UTF-8") }; let re = regex_from(&re, "replace")?; Ok(LString::from(re.replace_all(s, rep)).into()) @@ -206,7 +206,7 @@ pub fn split_once(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "split_once expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "string to split must be valid UTF-8") }; let re = regex_from(&re, "split_once")?; let mut parts = re.splitn(s, 2); @@ -224,7 +224,7 @@ pub fn split(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "split expected string, got {s:#}") }; let Ok(s) = s.to_str() else { - throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8") + throw!(*SYM_VALUE_ERROR, "string to split must be valid UTF-8") }; let re = regex_from(&re, "split")?; let parts: Vec = re.split(s) diff --git a/talc-std/src/string.rs b/talc-std/src/string.rs index 5e056c8..a06d122 100644 --- a/talc-std/src/string.rs +++ b/talc-std/src/string.rs @@ -1,7 +1,7 @@ -use talc_lang::{exception::Result, lformat, lstring::LString, symbol::SYM_TYPE_ERROR, throw, value::Value, Vm}; +use talc_lang::{exception::Result, lformat, lstring::LString, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::Value, Vm}; use talc_macros::native_func; -use crate::unpack_args; +use crate::{unpack_args, SYM_FORMAT_ERROR}; pub fn load(vm: &mut Vm) { vm.set_global_name("ord", ord().into()); @@ -28,10 +28,10 @@ pub fn ord(_: &mut Vm, args: Vec) -> Result { }; let mut chars = s.chars(); let Some(c) = chars.next() else { - throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1") + throw!(*SYM_VALUE_ERROR, "argument to ord must have length 1") }; if chars.next().is_some() { - throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1") + throw!(*SYM_VALUE_ERROR, "argument to ord must have length 1") }; Ok(Value::Int(c as u32 as i64)) } @@ -43,10 +43,10 @@ pub fn chr(_: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "chr expected integer argument, got {i:#}") }; let Ok(i) = u32::try_from(i) else { - throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint") + throw!(*SYM_VALUE_ERROR, "argument to chr is not a valid codepoint") }; let Some(c) = char::from_u32(i) else { - throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint") + throw!(*SYM_VALUE_ERROR, "argument to chr is not a valid codepoint") }; Ok(Value::String(LString::from(c).into())) } @@ -164,7 +164,7 @@ pub fn str_of_bytes(_: &mut Vm, args: Vec) -> Result { let bytes: Vec = b.borrow().iter() .map(|v| match v { Value::Int(i) if (0..=255).contains(i) => Ok(*i as u8), - _ => throw!(*SYM_TYPE_ERROR, "str_of_bytes expected list of integers in 0..=255"), + _ => throw!(*SYM_VALUE_ERROR, "str_of_bytes expected list of integers in 0..=255"), }).collect::>>()?; Ok(LString::from(bytes).into()) } @@ -185,7 +185,7 @@ pub fn _format(_: &mut Vm, args: Vec) -> Result { Some(b @ (b'#' | b'?')) => { let fargs = fargs.borrow(); let Some(a) = fargs.get(faidx) else { - throw!(*SYM_TYPE_ERROR, "not enough args for format string") + throw!(*SYM_FORMAT_ERROR, "not enough args for format string") }; faidx += 1; if b == b'?' { @@ -195,7 +195,7 @@ pub fn _format(_: &mut Vm, args: Vec) -> Result { } }, - _ => throw!(*SYM_TYPE_ERROR, "invalid format code") + _ => throw!(*SYM_FORMAT_ERROR, "invalid format code") } } else { res.push_byte(b); diff --git a/talc-std/src/value.rs b/talc-std/src/value.rs index b96e424..eaddb3c 100644 --- a/talc-std/src/value.rs +++ b/talc-std/src/value.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use talc_lang::{exception::{exception, Result}, lformat, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, value::{ops::RatioExt, HashValue, Rational64, Value}, Vm}; +use talc_lang::{exception::{exception, Result}, lformat, parse_float, parse_int, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{ops::RatioExt, HashValue, Rational64, Value}, Vm}; use talc_macros::native_func; use crate::unpack_args; @@ -65,7 +65,7 @@ pub fn as_(_: &mut Vm, args: Vec) -> Result { (Value::Float(x), b"int") => Ok(Value::Int(x as i64)), (Value::Float(x), b"ratio") => { let r = Rational64::approximate_float(x) - .ok_or_else(|| exception!(*SYM_TYPE_ERROR, "float {x:?} could not be converted to ratio"))?; + .ok_or_else(|| exception!(*SYM_VALUE_ERROR, "float {x:?} could not be converted to ratio"))?; Ok(Value::Ratio(r)) } (Value::Float(x), b"complex") => Ok(Value::Complex(x.into())), @@ -73,12 +73,12 @@ pub fn as_(_: &mut Vm, args: Vec) -> Result { (Value::String(s), b"int") => parse_int(s.as_ref(), 10) .map(i64::into) - .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as integer")), + .map_err(|_| exception!(*SYM_VALUE_ERROR, "could not parse {s:#} as integer")), (Value::String(s), b"float") => parse_float(s.as_ref()) .map(f64::into) - .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as float")), + .map_err(|_| exception!(*SYM_VALUE_ERROR, "could not parse {s:#} as float")), (v, _) => throw!(*SYM_TYPE_ERROR, "cannot convert value of type {} to type {}", v.get_type().name(), ty.name()) @@ -158,7 +158,7 @@ pub fn cell(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_TYPE_ERROR, "uncell: value is not a cell") }; Ok(Rc::unwrap_or_clone(cell).into_inner()) } @@ -167,7 +167,7 @@ pub fn uncell(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_TYPE_ERROR, "cell_replace: value is not a cell") }; Ok(cell.replace(value)) } @@ -176,7 +176,7 @@ pub fn cell_replace(_: &mut Vm, args: Vec) -> Result { 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") + throw!(*SYM_TYPE_ERROR, "cell_take: value is not a cell") }; Ok(cell.replace(Value::Nil)) }