removed variadics, prepared for closures

This commit is contained in:
trimill 2024-10-31 12:36:53 -04:00
parent e4b4c981d2
commit 754fbf6c2c
19 changed files with 538 additions and 231 deletions

View file

@ -88,38 +88,54 @@ impl TryFrom<Arg24> 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 {}",

View file

@ -12,6 +12,7 @@ use crate::value::Value;
pub struct Local {
name: Rc<LStr>,
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<Local>) {
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,9 +236,17 @@ 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 {
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) {
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,
});
}

View file

@ -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);

View file

@ -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<Chunk>,
pub state: Box<[CellValue]>,
}
impl Function {
pub fn new(chunk: Rc<Chunk>, arity: usize) -> Self {
Self { chunk, attrs: FuncAttrs { arity, variadic: false } }
pub fn new(chunk: Rc<Chunk>, 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<Chunk>, arity: usize) -> Self {
Self { chunk, attrs: FuncAttrs { arity, variadic: true } }
pub fn from_parts(chunk: Rc<Chunk>, 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<Function>, 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() {

View file

@ -20,6 +20,7 @@ pub mod index;
type RcList = Rc<RefCell<Vec<Value>>>;
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
pub type CellValue = Rc<RefCell<Value>>;
#[derive(Clone, Debug, Default)]
pub enum Value {

View file

@ -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<Value> 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<Value> for Value {
}
#[allow(clippy::cast_sign_loss)]
#[inline]
fn ipow(n: i64, p: u64) -> Result<i64> {
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<i64> {
}
#[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<Self> {
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<Self> {
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:#}")
}
}

View file

@ -72,17 +72,13 @@ enum CallOutcome {
Partial(Value),
}
fn get_call_outcome(mut args: Vec<Value>) -> Result<CallOutcome> {
fn get_call_outcome(args: Vec<Value>) -> Result<CallOutcome> {
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<Value>) -> Result<CallOutcome> {
let nf = move |vm: &mut Vm, inner_args: Vec<Value>| {
let mut ia = inner_args.into_iter();
ia.next();
let mut args: Vec<Value> = 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<Value> = 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 {

View file

@ -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<Token![..]>,
}
impl Parse for NativeFuncArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
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)
}

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
let [_, x, y] = unpack_args!(args);
Ok(vec![x, y].into())
}
#[native_func(1)]
pub fn fst(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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())
}

View file

@ -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<Value>) -> Result<Value> {
let ([_, ty], varargs) = unpack_varargs!(args);
let Value::Symbol(ty) = ty else {
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
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(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"),
})
Err(exc)
}
#[native_func(1)]
@ -30,6 +32,7 @@ pub fn rethrow(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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")
}

View file

@ -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<Value>) -> Result<Value> {
} 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<Value>) -> Result<Value> {
} 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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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());
},

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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)

View file

@ -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<Value>) -> Result<Value> {
pub fn take(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
pub fn skip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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,14 +358,38 @@ pub fn rev(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
//
#[native_func(2..)]
#[native_func(2)]
pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Result<Vec<_>>>()?;
let f = move |vm: &mut Vm, _| {
let mut res = Vec::with_capacity(iters.len());
for i in &iters {
@ -372,13 +404,42 @@ pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2..)]
#[native_func(2)]
pub fn alternate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Result<Vec<_>>>()?;
let state = RefCell::new((0, false));
let f = move |vm: &mut Vm, _| {
@ -556,12 +617,14 @@ pub fn table(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>> = 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<Value>) -> Result<Value> {
Ok(map.into())
}
#[native_func(1)]
pub fn mean(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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)
}
}

View file

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

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
Ok(true.into())
}
#[native_func(2..)]
#[native_func(2)]
pub fn gcd(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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 Value::Int(a) = a else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {a:#}")
};
prod *= a;
g = gcd_inner(g, a);
let g = gcd_inner(x, y);
if g == 0 {
Ok(Value::from(0))
} else {
Value::from(x/g) * Value::from(y)
}
Ok((x/g * prod).into())
}
#[native_func(1)]
pub fn lcmn(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
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, "lcmn: cannot take lcm with {a:#}")
};
let g = gcd_inner(l, a);
if g == 0 { return Ok(Value::from(0)) };
l = (l/g).wrapping_mul(a);
}
Ok(Value::from(l))
}
#[native_func(1)]
@ -401,27 +429,25 @@ pub fn factors(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
// numeric operations
//
#[native_func(1..)]
#[native_func(2)]
pub fn min(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val < x {
x = val;
}
}
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<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val > x {
x = val;
}
}
let [_, x, y] = unpack_args!(args);
if y > x {
Ok(y)
} else {
Ok(x)
}
}
#[native_func(1)]
pub fn floor(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
@ -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);

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
},
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<Value>) -> Result<Value> {
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);

View file

@ -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<Cow<'a, Regex>> {
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::<ValueRegex>()
.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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Vec<Value>>().into())
@ -142,7 +142,7 @@ pub fn captures_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Vec<Value>>().into())
@ -171,10 +171,10 @@ pub fn replace_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value> = re.split(s)

View file

@ -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<Value>) -> Result<Value> {
};
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
let bytes: Vec<u8> = 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::<Result<Vec<u8>>>()?;
Ok(LString::from(bytes).into())
}
@ -185,7 +185,7 @@ pub fn _format(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
}
},
_ => throw!(*SYM_TYPE_ERROR, "invalid format code")
_ => throw!(*SYM_FORMAT_ERROR, "invalid format code")
}
} else {
res.push_byte(b);

View file

@ -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<Value>) -> Result<Value> {
(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<Value>) -> Result<Value> {
(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<Value>) -> Result<Value> {
pub fn uncell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
pub fn cell_replace(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
pub fn cell_take(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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))
}