diff --git a/Cargo.lock b/Cargo.lock index 01d170f..8bed4f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,11 +162,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -177,9 +177,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" @@ -232,6 +232,31 @@ dependencies = [ "libc", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -250,12 +275,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-bigint", "num-integer", "num-traits", ] @@ -434,9 +471,7 @@ name = "talc-lang" version = "0.2.1" dependencies = [ "lazy_static", - "num-complex", - "num-rational", - "num-traits", + "num", "unicode-ident", ] @@ -453,6 +488,7 @@ name = "talc-std" version = "0.2.1" dependencies = [ "lazy_static", + "num-bigint", "rand", "regex", "talc-lang", diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs index 16f1234..9378e48 100644 --- a/talc-bin/src/main.rs +++ b/talc-bin/src/main.rs @@ -4,7 +4,9 @@ use talc_lang::{ compiler::compile, lstring::LString, optimize::optimize, - parser, serial, + parser, + prelude::*, + serial, symbol::Symbol, value::{function::disasm_recursive, Value}, vm::Vm, @@ -112,7 +114,7 @@ fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode { ExitCode::FAILURE } Ok(Value::Bool(false)) => ExitCode::FAILURE, - Ok(Value::Int(n)) => ExitCode::from(n as u8), + Ok(Value::Int(n)) => ExitCode::from(n.to_u64().unwrap_or(1) as u8), _ => ExitCode::SUCCESS, } } diff --git a/talc-lang/Cargo.toml b/talc-lang/Cargo.toml index 2ddd4c8..51006c3 100644 --- a/talc-lang/Cargo.toml +++ b/talc-lang/Cargo.toml @@ -4,8 +4,6 @@ version = "0.2.1" edition = "2021" [dependencies] -num-complex = "0.4" -num-rational = { version = "0.4", default-features = false, features = [] } -num-traits = "0.2" +num = { version = "0.4", features = [] } lazy_static = "1.5" unicode-ident = "1.0" diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index a2516e3..e579d62 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use crate::chunk::{Arg24, Catch, Chunk, Instruction as I}; use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind}; use crate::parser::Pos; +use crate::prelude::*; use crate::symbol::{Symbol, SYM_REPL, SYM_SELF}; use crate::throw; use crate::value::function::{FuncAttrs, Function}; @@ -27,9 +28,9 @@ impl fmt::Display for CompileError { type Result = std::result::Result; macro_rules! throw { - ($pos:expr, $($t:tt)*) => { - return Err(CompileError { pos: $pos, msg: format!($($t)*) }) - }; + ($pos:expr, $($t:tt)*) => { + return Err(CompileError { pos: $pos, msg: format!($($t)*) }) + }; } enum ResolveOutcome { @@ -766,21 +767,28 @@ impl<'a> Compiler<'a> { match val { Value::Nil => { self.emit(I::Nil); + return } Value::Bool(b) => { self.emit(I::Bool(*b)); - } - Value::Int(i) if (-0x80_0000..=0x7f_ffff).contains(i) => { - self.emit(I::Int(Arg24::from_i64(*i))); + return } Value::Symbol(s) => { self.emit(I::Symbol(Arg24::from_symbol(*s))); + return } - _ => { - let n = self.add_const(val.clone()); - self.emit(I::Const(Arg24::from_usize(n))); + Value::Int(i) => { + if let Some(v) = i.to_i32() { + if (-0x80_0000..=0x7f_ffff).contains(&v) { + self.emit(I::Int(Arg24::from_i32(v))); + return + } + } } + _ => (), } + let n = self.add_const(val.clone()); + self.emit(I::Const(Arg24::from_usize(n))); } fn expr_assign(&mut self, o: Option, lv: &LValue, a: &Expr) -> Result<()> { diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs index 0d044f6..e044fbf 100644 --- a/talc-lang/src/lib.rs +++ b/talc-lang/src/lib.rs @@ -8,9 +8,19 @@ pub mod chunk; pub mod compiler; pub mod exception; pub mod lstring; +pub mod number; pub mod optimize; pub mod parser; pub mod serial; pub mod symbol; pub mod value; pub mod vm; + +pub mod prelude { + pub use crate::number::RatioExt; + pub use num::complex::ComplexFloat; + pub use num::integer::{Integer, Roots}; + pub use num::traits::{ + ConstOne, ConstZero, Euclid, FromPrimitive, Num, One, Signed, ToPrimitive, Zero, + }; +} diff --git a/talc-lang/src/lstring.rs b/talc-lang/src/lstring.rs index 10c4088..493b96b 100644 --- a/talc-lang/src/lstring.rs +++ b/talc-lang/src/lstring.rs @@ -546,13 +546,6 @@ impl io::Write for LString { } } -//impl fmt::Write for LString { -// fn write_str(&mut self, s: &str) -> fmt::Result { -// self.extend(s.as_bytes()); -// Ok(()) -// } -//} - // // methods // diff --git a/talc-lang/src/number/int.rs b/talc-lang/src/number/int.rs new file mode 100644 index 0000000..b958045 --- /dev/null +++ b/talc-lang/src/number/int.rs @@ -0,0 +1,986 @@ +use core::{f64, panic}; +use std::{borrow::Cow, cmp::Ordering, fmt, hash::Hash, mem::ManuallyDrop, ops, rc::Rc}; + +use num::{ + bigint::{ParseBigIntError, Sign}, + integer::Roots, + traits::CheckedEuclid, + BigInt, Integer, Num, +}; + +use crate::prelude::*; + +pub enum IntType<'a> { + Int(i64), + Big(&'a BigInt), +} + +#[inline] +const fn is_int_small(n: i64) -> bool { + let b = n >> 62; + b == 0 || b == -1 +} + +union IntInner { + int: i64, + big: ManuallyDrop>, +} + +impl IntInner { + #[inline] + fn from_int(n: i64) -> Self { + if is_int_small(n) { + unsafe { Self::from_int_unchecked(n) } + } else { + Self::from_big_direct(BigInt::from(n)) + } + } + + #[inline] + const unsafe fn from_int_unchecked(n: i64) -> Self { + Self { int: (n << 1) | 1 } + } + + #[inline] + fn from_big(n: BigInt) -> Self { + if let Some(n) = n.to_i64() { + if is_int_small(n) { + return unsafe { Self::from_int_unchecked(n) } + } + } + Self::from_big_direct(n) + } + + #[inline] + fn from_big_ref(n: &BigInt) -> Self { + if let Some(n) = n.to_i64() { + if is_int_small(n) { + return unsafe { Self::from_int_unchecked(n) } + } + } + Self::from_big_direct(n.clone()) + } + + #[inline] + fn from_big_direct(n: BigInt) -> Self { + Self { + big: ManuallyDrop::new(Rc::new(n)), + } + } + + #[inline] + fn from_big_rc(n: Rc) -> Self { + Self { + big: ManuallyDrop::new(n), + } + } + + #[inline] + const fn is_big(&self) -> bool { + unsafe { self.int & 1 == 0 } + } + + #[inline] + fn expand(&self) -> IntType { + if self.is_big() { + IntType::Big(unsafe { self.big.as_ref() }) + } else { + IntType::Int(unsafe { self.int >> 1 }) + } + } + + #[inline] + fn as_int(&self) -> Option { + if let IntType::Int(n) = self.expand() { + Some(n) + } else { + None + } + } + + #[inline] + fn to_big(&self) -> Cow { + match self.expand() { + IntType::Big(n) => Cow::Borrowed(n), + IntType::Int(n) => Cow::Owned(BigInt::from(n)), + } + } +} + +impl Clone for IntInner { + fn clone(&self) -> Self { + if let Some(n) = self.as_int() { + unsafe { Self::from_int_unchecked(n) } + } else { + Self::from_big_rc(ManuallyDrop::into_inner(unsafe { self.big.clone() })) + } + } +} + +impl Drop for IntInner { + fn drop(&mut self) { + if self.is_big() { + unsafe { ManuallyDrop::drop(&mut self.big) } + } + } +} + +// +// Int implementation +// + +#[derive(Clone)] +pub struct Int(IntInner); + +impl Int { + /// panics if n is not small enough + pub const fn from_small(n: i64) -> Self { + assert!(is_int_small(n), "argument to from_small was too large"); + Self(unsafe { IntInner::from_int_unchecked(n) }) + } +} + +impl Default for Int { + #[inline] + fn default() -> Self { + Self(unsafe { IntInner::from_int_unchecked(0) }) + } +} + +impl From for Int { + #[inline] + fn from(value: i8) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: u8) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: i16) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: u16) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: i32) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: u32) -> Self { + Self::from_small(value as i64) + } +} + +impl From for Int { + #[inline] + fn from(value: i64) -> Self { + Self(IntInner::from_int(value)) + } +} + +impl From for Int { + #[inline] + fn from(value: u64) -> Self { + if let Ok(v) = value.try_into() { + Self(IntInner::from_int(v)) + } else { + Self(IntInner::from_big_direct(BigInt::from(value))) + } + } +} + +impl From for Int { + #[inline] + fn from(value: i128) -> Self { + if let Ok(v) = value.try_into() { + Self(IntInner::from_int(v)) + } else { + Self(IntInner::from_big_direct(BigInt::from(value))) + } + } +} + +impl From for Int { + #[inline] + fn from(value: u128) -> Self { + if let Ok(v) = value.try_into() { + Self(IntInner::from_int(v)) + } else { + Self(IntInner::from_big_direct(BigInt::from(value))) + } + } +} + +impl From for Int { + #[inline] + fn from(value: isize) -> Self { + if let Ok(v) = value.try_into() { + Self(IntInner::from_int(v)) + } else { + Self(IntInner::from_big_direct(BigInt::from(value))) + } + } +} + +impl From for Int { + #[inline] + fn from(value: usize) -> Self { + if let Ok(v) = value.try_into() { + Self(IntInner::from_int(v)) + } else { + Self(IntInner::from_big_direct(BigInt::from(value))) + } + } +} + +impl From for Int { + fn from(value: BigInt) -> Self { + Self(IntInner::from_big(value)) + } +} + +impl From<&BigInt> for Int { + fn from(value: &BigInt) -> Self { + Self(IntInner::from_big_ref(value)) + } +} + +macro_rules! impl_binop { + ($($trait:ident)::+,$($atrait:ident)::+,$fname:ident,$afname:ident,$op:tt,$checked:ident) => { + impl $($trait)::+ for &Int { + type Output = Int; + + fn $fname(self, rhs: Self) -> Self::Output { + match (self.expand(), rhs.expand()) { + (IntType::Int(a), IntType::Int(b)) => match a.$checked(b) { + Some(n) => Int::from(n), + None => Int::from(BigInt::from(a) $op b), + } + (IntType::Int(a), IntType::Big(b)) => Int::from(a $op b), + (IntType::Big(a), IntType::Int(b)) => Int::from(a $op b), + (IntType::Big(a), IntType::Big(b)) => Int::from(a $op b), + } + } + } + + impl $($trait)::+ for Int { + type Output = Int; + + fn $fname(self, rhs: Int) -> Self::Output { + &self $op &rhs + } + } + + impl $($trait)::+ for &Int { + type Output = Int; + + fn $fname(self, rhs: i64) -> Self::Output { + self $op &Int::from(rhs) + } + } + + impl $($atrait)::+ for Int { + fn $afname(&mut self, rhs: Int) { + *self = &*self $op &rhs; + } + } + + impl $($atrait)::+<&Int> for Int { + fn $afname(&mut self, rhs: &Int) { + *self = &*self $op rhs; + } + } + + impl $($atrait)::+ for Int { + fn $afname(&mut self, rhs: i64) { + *self = &*self $op rhs; + } + } + }; +} + +impl_binop!(ops::Add, ops::AddAssign, add, add_assign, +, checked_add); +impl_binop!(ops::Sub, ops::SubAssign, sub, sub_assign, -, checked_sub); +impl_binop!(ops::Mul, ops::MulAssign, mul, mul_assign, *, checked_mul); +impl_binop!(ops::Div, ops::DivAssign, div, div_assign, /, checked_div); +impl_binop!(ops::Rem, ops::RemAssign, rem, rem_assign, %, checked_rem); + +macro_rules! impl_logicop { + ($($trait:ident)::+,$($atrait:ident)::+,$fname:ident,$afname:ident,$op:tt) => { + impl $($trait)::+ for &Int { + type Output = Int; + + fn $fname(self, rhs: Self) -> Self::Output { + match (self.expand(), rhs.expand()) { + (IntType::Int(a), IntType::Int(b)) => Int::from(a $op b), + (IntType::Int(a), IntType::Big(b)) => Int::from(BigInt::from(a) $op b), + (IntType::Big(a), IntType::Int(b)) => Int::from(a $op BigInt::from(b)), + (IntType::Big(a), IntType::Big(b)) => Int::from(a $op b), + } + } + } + + impl $($trait)::+ for Int { + type Output = Int; + + fn $fname(self, rhs: Int) -> Self::Output { + &self $op &rhs + } + } + + impl $($trait)::+ for &Int { + type Output = Int; + + fn $fname(self, rhs: i64) -> Self::Output { + self $op &Int::from(rhs) + } + } + + impl $($atrait)::+<&Int> for Int { + fn $afname(&mut self, rhs: &Int) { + *self = &*self $op rhs; + } + } + + impl $($atrait)::+ for Int { + fn $afname(&mut self, rhs: i64) { + *self = &*self $op rhs; + } + } + }; +} + +impl_logicop!(ops::BitAnd, ops::BitAndAssign, bitand, bitand_assign, &); +impl_logicop!(ops::BitOr, ops::BitOrAssign, bitor, bitor_assign, |); +impl_logicop!(ops::BitXor, ops::BitXorAssign, bitxor, bitxor_assign, ^); + +impl ops::Shl for &Int { + type Output = Int; + + fn shl(self, rhs: Self) -> Self::Output { + if rhs.is_negative() { + panic!("attempt to shift left with negative") + } + let Some(rhs) = rhs.to_u32() else { + panic!("shift left overflow") + }; + Int::from(self.to_big().as_ref() << rhs) + } +} + +impl ops::Shl for Int { + type Output = Int; + + fn shl(self, rhs: Int) -> Self::Output { + &self << &rhs + } +} + +impl ops::Shl for &Int { + type Output = Int; + + fn shl(self, rhs: i64) -> Self::Output { + self << &Int::from(rhs) + } +} + +impl ops::ShlAssign<&Int> for Int { + fn shl_assign(&mut self, rhs: &Int) { + *self = &*self << rhs; + } +} + +impl ops::ShlAssign for Int { + fn shl_assign(&mut self, rhs: i64) { + *self = &*self << &Int::from(rhs); + } +} + +impl ops::Shr for &Int { + type Output = Int; + fn shr(self, rhs: Self) -> Self::Output { + if rhs.is_negative() { + panic!("attempt to shift right with negative") + } + let Some(rhs) = rhs.to_u32() else { + return Int::ZERO + }; + + Int::from(self.to_big().as_ref() >> rhs) + } +} + +impl ops::Shr for Int { + type Output = Int; + + fn shr(self, rhs: Int) -> Self::Output { + &self >> &rhs + } +} + +impl ops::Shr for &Int { + type Output = Int; + + fn shr(self, rhs: i64) -> Self::Output { + self >> &Int::from(rhs) + } +} + +impl ops::ShrAssign<&Int> for Int { + fn shr_assign(&mut self, rhs: &Int) { + *self = &*self >> rhs; + } +} + +impl ops::ShrAssign for Int { + fn shr_assign(&mut self, rhs: i64) { + *self = &*self >> &Int::from(rhs); + } +} + +impl ops::Neg for &Int { + type Output = Int; + + fn neg(self) -> Int { + match self.expand() { + IntType::Int(n) => Int::from(-n), + IntType::Big(n) => Int::from(-n), + } + } +} + +impl ops::Neg for Int { + type Output = Int; + + fn neg(self) -> Int { + -&self + } +} + +impl ops::Not for &Int { + type Output = Int; + + fn not(self) -> Int { + match self.expand() { + IntType::Int(n) => Int::from(!n), + IntType::Big(n) => Int::from(!n), + } + } +} + +impl ops::Not for Int { + type Output = Int; + + fn not(self) -> Int { + !&self + } +} + +impl PartialEq for Int { + fn eq(&self, other: &Self) -> bool { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + a.eq(&b) + } else { + let a = self.0.to_big(); + let b = other.0.to_big(); + a.as_ref().eq(b.as_ref()) + } + } +} + +impl Eq for Int {} + +impl PartialOrd for Int { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Int { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + a.cmp(&b) + } else { + let a = self.0.to_big(); + let b = other.0.to_big(); + a.as_ref().cmp(b.as_ref()) + } + } +} + +impl ConstZero for Int { + const ZERO: Self = Self::from_small(0); +} + +impl ConstOne for Int { + const ONE: Self = Self::from_small(1); +} + +impl Zero for Int { + #[inline] + fn zero() -> Self { + Self::ZERO + } + + fn is_zero(&self) -> bool { + match self.expand() { + IntType::Int(n) => n == 0, + IntType::Big(n) => n.is_zero(), + } + } +} + +impl One for Int { + #[inline] + fn one() -> Self { + Self::ONE + } +} + +impl Num for Int { + type FromStrRadixErr = ParseBigIntError; + + fn from_str_radix(str: &str, radix: u32) -> Result { + match i64::from_str_radix(str, radix) { + Ok(v) => Ok(Self::from(v)), + Err(_) => Ok(Self::from(BigInt::from_str_radix(str, radix)?)), + } + } +} + +impl Integer for Int { + fn div_floor(&self, other: &Self) -> Self { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + return Integer::div_floor(&a, &b).into() + } + let (a, b) = (self.0.to_big(), other.0.to_big()); + a.div_floor(&b).into() + } + + fn mod_floor(&self, other: &Self) -> Self { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + return a.mod_floor(&b).into() + } + let (a, b) = (self.0.to_big(), other.0.to_big()); + a.mod_floor(&b).into() + } + + fn gcd(&self, other: &Self) -> Self { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + return a.gcd(&b).into() + } + let (a, b) = (self.0.to_big(), other.0.to_big()); + a.gcd(&b).into() + } + + fn lcm(&self, other: &Self) -> Self { + let (a, b) = (self.0.to_big(), other.0.to_big()); + a.lcm(&b).into() + } + + fn is_multiple_of(&self, other: &Self) -> bool { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + return a.is_multiple_of(&b) + } + let (a, b) = (self.0.to_big(), other.0.to_big()); + a.is_multiple_of(&b) + } + + fn is_even(&self) -> bool { + match self.expand() { + IntType::Int(n) => n.is_even(), + IntType::Big(n) => n.is_even(), + } + } + + fn is_odd(&self) -> bool { + match self.expand() { + IntType::Int(n) => n.is_odd(), + IntType::Big(n) => n.is_odd(), + } + } + + fn div_rem(&self, other: &Self) -> (Self, Self) { + if let (Some(a), Some(b)) = (self.0.as_int(), other.0.as_int()) { + let (q, r) = a.div_rem(&b); + return (q.into(), r.into()) + } + let (a, b) = (self.0.to_big(), other.0.to_big()); + let (q, r) = a.div_rem(&b); + (q.into(), r.into()) + } +} + +impl Signed for Int { + fn abs(&self) -> Self { + match self.expand() { + IntType::Int(n) => Int::from(n.abs()), + IntType::Big(n) => Int::from(n.abs()), + } + } + + fn abs_sub(&self, other: &Self) -> Self { + (self - other).abs() + } + + fn signum(&self) -> Self { + match self.expand() { + IntType::Int(n) => Int::from(n.signum()), + IntType::Big(n) => Int::from(n.signum()), + } + } + + fn is_positive(&self) -> bool { + match self.expand() { + IntType::Int(n) => n > 0, + IntType::Big(n) => n.is_positive(), + } + } + + fn is_negative(&self) -> bool { + match self.expand() { + IntType::Int(n) => n < 0, + IntType::Big(n) => n.is_negative(), + } + } +} + +impl Roots for Int { + fn nth_root(&self, n: u32) -> Self { + match self.expand() { + IntType::Int(i) => Int::from(i.nth_root(n)), + IntType::Big(b) => Int::from(b.nth_root(n)), + } + } +} + +impl Euclid for Int { + fn div_euclid(&self, v: &Self) -> Self { + if let (Some(a), Some(b)) = (self.0.as_int(), v.0.as_int()) { + if let Some(v) = CheckedEuclid::checked_div_euclid(&a, &b) { + return Int::from(v) + } + } + let (a, b) = (self.0.to_big(), v.0.to_big()); + a.div_euclid(&b).into() + } + + fn rem_euclid(&self, v: &Self) -> Self { + if let (Some(a), Some(b)) = (self.0.as_int(), v.0.as_int()) { + if let Some(v) = CheckedEuclid::checked_rem_euclid(&a, &b) { + return Int::from(v) + } + } + let (a, b) = (self.0.to_big(), v.0.to_big()); + a.rem_euclid(&b).into() + } +} + +impl Int { + pub fn to_str_radix(&self, radix: u32) -> String { + self.to_big().to_str_radix(radix) + } + + pub fn to_str_radix_case(&self, radix: u32, upper: bool) -> String { + let mut s = self.to_big().to_str_radix(radix); + if upper { + s.make_ascii_uppercase(); + } + s + } +} + +impl fmt::Debug for Int { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt::Display::fmt(&self, f) + } +} + +impl fmt::Display for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.to_big().as_ref(), f) + } +} + +impl fmt::Binary for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Binary::fmt(self.to_big().as_ref(), f) + } +} + +impl fmt::Octal for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Octal::fmt(self.to_big().as_ref(), f) + } +} + +impl fmt::LowerHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(self.to_big().as_ref(), f) + } +} + +impl fmt::UpperHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(self.to_big().as_ref(), f) + } +} + +impl Hash for Int { + fn hash(&self, state: &mut H) { + match self.expand() { + IntType::Int(n) => n.hash(state), + IntType::Big(b) => match b.to_i64() { + Some(n) => n.hash(state), + None => b.hash(state), + }, + } + } +} + +impl FromPrimitive for Int { + fn from_i64(n: i64) -> Option { + Some(Self::from(n)) + } + + fn from_u64(n: u64) -> Option { + Some(Self::from(n)) + } + + fn from_isize(n: isize) -> Option { + Some(Self::from(n)) + } + + fn from_i128(n: i128) -> Option { + Some(Self::from(n)) + } + + fn from_usize(n: usize) -> Option { + Some(Self::from(n)) + } + + fn from_u128(n: u128) -> Option { + Some(Self::from(n)) + } + + fn from_f32(n: f32) -> Option { + Some(Int::from(BigInt::from_f32(n)?)) + } + + fn from_f64(n: f64) -> Option { + Some(Int::from(BigInt::from_f64(n)?)) + } +} + +impl ToPrimitive for Int { + fn to_i64(&self) -> Option { + match self.expand() { + IntType::Int(n) => Some(n), + IntType::Big(b) => b.to_i64(), + } + } + + fn to_u64(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_u64(), + IntType::Big(b) => b.to_u64(), + } + } + + fn to_isize(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_isize(), + IntType::Big(b) => b.to_isize(), + } + } + + fn to_i128(&self) -> Option { + match self.expand() { + IntType::Int(n) => Some(n as i128), + IntType::Big(b) => b.to_i128(), + } + } + + fn to_usize(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_usize(), + IntType::Big(b) => b.to_usize(), + } + } + + fn to_u128(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_u128(), + IntType::Big(b) => b.to_u128(), + } + } + + fn to_f32(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_f32(), + IntType::Big(b) => b.to_f32(), + } + } + + fn to_f64(&self) -> Option { + match self.expand() { + IntType::Int(n) => n.to_f64(), + IntType::Big(b) => b.to_f64(), + } + } +} + +impl Int { + #[inline] + pub fn to_f64_uc(&self) -> f64 { + self.to_f64().unwrap_or(f64::NAN) + } + + #[inline] + pub fn expand(&self) -> IntType { + self.0.expand() + } + + /// exp must be nonnegative, both cannot be zero. + pub fn ipow(&self, exp: &Int) -> Option { + if self.is_zero() && exp.is_zero() || exp.is_negative() { + return None + } + if exp.is_zero() { + return Some(Int::ONE) + } + if exp.is_one() { + return Some(self.clone()) + } + if self.is_zero() || self.is_one() { + return Some(self.clone()) + } + if self == &Int::from_small(-1) { + return if exp.is_even() { + Some(Int::ONE) + } else { + Some(self.clone()) + } + } + if let Some(e) = exp.to_u32() { + if let Some(i) = self.0.as_int() { + if let Some(v) = i.checked_pow(e) { + return Some(Int::from(v)) + } + } + Some(Self::from(self.0.to_big().pow(e))) + } else { + None + } + } + + pub fn modpow(&self, exp: &Int, modulus: &Int) -> Int { + self.to_big() + .modpow(&exp.to_big(), &modulus.to_big()) + .into() + } + + pub fn modinv(&self, modulus: &Int) -> Option { + self.to_big().modinv(&modulus.to_big()).map(|v| v.into()) + } + + pub fn isignum(&self) -> i64 { + match self.expand() { + IntType::Int(i) => i.signum(), + IntType::Big(b) => match b.sign() { + Sign::Minus => -1, + Sign::NoSign => 0, + Sign::Plus => 1, + }, + } + } + + #[inline] + pub fn to_big(&self) -> Cow { + self.0.to_big() + } + + pub fn trailing_zeros(&self) -> Option { + match self.expand() { + IntType::Int(0) => None, + IntType::Int(n) => Some(n.trailing_zeros() as u64), + IntType::Big(n) => n.trailing_zeros(), + } + } + + #[inline] + pub fn eq_i64(&self, n: i64) -> bool { + self.to_i64().is_some_and(|v| v == n) + } + + #[inline] + pub fn ne_i64(&self, n: i64) -> bool { + !self.eq_i64(n) + } + + #[inline] + pub fn cmp_i64(&self, n: i64) -> Ordering { + match self.to_i64() { + Some(v) => v.cmp(&n), + None => { + if self.is_negative() { + Ordering::Less + } else { + Ordering::Greater + } + } + } + } + + #[inline] + pub fn lt_i64(&self, n: i64) -> bool { + self.cmp_i64(n).is_lt() + } + + #[inline] + pub fn gt_i64(&self, n: i64) -> bool { + self.cmp_i64(n).is_gt() + } + + #[inline] + pub fn le_i64(&self, n: i64) -> bool { + self.cmp_i64(n).is_le() + } + + #[inline] + pub fn ge_i64(&self, n: i64) -> bool { + self.cmp_i64(n).is_ge() + } +} + +impl From for BigInt { + #[inline] + fn from(value: Int) -> Self { + value.0.to_big().into_owned() + } +} + +impl From<&Int> for BigInt { + #[inline] + fn from(value: &Int) -> Self { + value.0.to_big().into_owned() + } +} diff --git a/talc-lang/src/number/mod.rs b/talc-lang/src/number/mod.rs new file mode 100644 index 0000000..2b7fce1 --- /dev/null +++ b/talc-lang/src/number/mod.rs @@ -0,0 +1,10 @@ +mod int; +pub use int::{Int, IntType}; + +mod range; +pub use range::Range; + +mod ratio; +pub use ratio::{Ratio, RatioExt}; + +pub use num::complex::Complex64 as Complex; diff --git a/talc-lang/src/number/range.rs b/talc-lang/src/number/range.rs new file mode 100644 index 0000000..47f2958 --- /dev/null +++ b/talc-lang/src/number/range.rs @@ -0,0 +1,217 @@ +use std::fmt; + +use num::BigInt; + +use crate::prelude::*; + +use super::Int; + +fn to_slice_index(n: &BigInt, len: usize) -> usize { + if n.is_negative() { + let n = n + len; + if n.is_negative() { + return 0 + } + n.to_usize().unwrap_or(usize::MAX).min(len) + } else { + n.to_usize().unwrap_or(usize::MAX).min(len) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Range { + pub start: Option, + pub end: Option, + pub inclusive: bool, +} + +impl Range { + #[expect(clippy::self_named_constructors)] + pub fn range(start: I, end: J) -> Self + where + I: Into, + J: Into, + { + Self { + start: Some(start.into()), + end: Some(end.into()), + inclusive: false, + } + } + + pub fn range_incl(start: I, end: J) -> Self + where + I: Into, + J: Into, + { + Self { + start: Some(start.into()), + end: Some(end.into()), + inclusive: true, + } + } + + pub fn range_from(start: I) -> Self + where + I: Into, + { + Self { + start: Some(start.into()), + end: None, + inclusive: false, + } + } + + pub fn range_to(end: I) -> Self + where + I: Into, + { + Self { + start: None, + end: Some(end.into()), + inclusive: false, + } + } + + pub fn range_to_incl(end: I) -> Self + where + I: Into, + { + Self { + start: None, + end: Some(end.into()), + inclusive: true, + } + } + + pub const fn range_all() -> Self { + Self { + start: None, + end: None, + inclusive: false, + } + } + + pub fn len(&self) -> Option { + let (Some(s), Some(e)) = (&self.start, &self.end) else { + return None + }; + if e < s { + return Some(Int::ZERO) + } + Some((e - s + if self.inclusive { 1i64 } else { 0i64 }).into()) + } + + pub fn is_empty(&self) -> bool { + let (Some(s), Some(e)) = (&self.start, &self.end) else { + return false + }; + if e < s { + return true + } + if e == s && !self.inclusive { + return true + } + false + } + + pub fn try_iterate(&self) -> Option> { + let Some(s) = &self.start else { return None }; + Some(RangeIterator { + cur: s.into(), + end: self.end.as_ref().map(|e| e.into()), + inclusive: self.inclusive, + done: false, + }) + } + + pub fn index_slice<'a, T>(&self, l: &'a [T]) -> &'a [T] { + let len = l.len(); + let s = self + .start + .as_ref() + .map(|s| to_slice_index(s, len)) + .unwrap_or(0); + let e = self + .end + .as_ref() + .map(|e| to_slice_index(e, len)) + .unwrap_or(len); + let e = if self.inclusive { + e.saturating_add(1).min(len) + } else { + e + }; + if e <= s { + return &[] + } + &l[s..e] + } + + pub fn replace_range(&self, l: &mut Vec, rep: &[T]) { + let len = l.len(); + let s = self + .start + .as_ref() + .map(|s| to_slice_index(s, len)) + .unwrap_or(0); + let e = self + .end + .as_ref() + .map(|e| to_slice_index(e, len)) + .unwrap_or(len); + let e = if self.inclusive { + e.saturating_add(1).min(len) + } else { + e + }; + + if e < s { + l.splice(e..s, rep.iter().cloned()); + } else { + l.splice(s..e, rep.iter().cloned()); + } + } +} + +struct RangeIterator { + cur: Int, + end: Option, + inclusive: bool, + done: bool, +} + +impl Iterator for RangeIterator { + type Item = Int; + + fn next(&mut self) -> Option { + if self.done { + return None + } + if let Some(end) = &self.end { + if &self.cur > end || (&self.cur == end && !self.inclusive) { + self.done = true; + return None + } + } + let mut val = &self.cur + 1; + std::mem::swap(&mut val, &mut self.cur); + Some(val) + } +} + +impl fmt::Display for Range { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.start { + Some(s) => write!(f, "{s}..")?, + None => write!(f, "*..")?, + } + if self.inclusive { + write!(f, "=")?; + } + match &self.end { + Some(e) => write!(f, "{e}"), + None => write!(f, "*"), + } + } +} diff --git a/talc-lang/src/number/ratio.rs b/talc-lang/src/number/ratio.rs new file mode 100644 index 0000000..e826189 --- /dev/null +++ b/talc-lang/src/number/ratio.rs @@ -0,0 +1,73 @@ +use num::Rational64; + +use super::Int; +use crate::prelude::*; + +pub type Ratio = num::BigRational; + +impl From for Ratio { + fn from(value: Int) -> Self { + Ratio::from_integer(value.into()) + } +} + +impl From<&Int> for Ratio { + fn from(value: &Int) -> Self { + Ratio::from_integer(value.to_owned().into()) + } +} + +pub trait RatioExt: Sized { + fn div_euclid(&self, n: &Self) -> Self; + fn rem_euclid(&self, n: &Self) -> Self; + fn to_f64_uc(&self) -> f64; + fn approximate_float(x: f64) -> Option; +} + +impl RatioExt for Ratio { + #[inline] + fn div_euclid(&self, n: &Self) -> Ratio { + let (n_abs, n_sgn) = (n.abs(), n.signum()); + (self / n_abs).floor() * n_sgn + } + + #[inline] + fn rem_euclid(&self, n: &Self) -> Ratio { + ToPrimitive::to_f64(self); + let n_abs = n.abs(); + self - (self / &n_abs).floor() * &n_abs + } + + #[inline] + fn to_f64_uc(&self) -> f64 { + if let Some(f) = self.to_f64() { + f + } else if self.is_negative() { + f64::NEG_INFINITY + } else { + f64::INFINITY + } + } + + fn approximate_float(x: f64) -> Option { + match Rational64::approximate_float(x) { + Some(r) => { + let r = Ratio::new((*r.numer()).into(), (*r.denom()).into()); + Some(r) + } + None => { + let mut y = x; + if y.abs() < 1.0 { + y = 1.0 / y; + } + let i = Int::from_f64(y)?; + let r = Ratio::from_integer(i.into()); + if x.abs() < 1.0 { + Some(r.recip()) + } else { + Some(r) + } + } + } + } +} diff --git a/talc-lang/src/parser/ast.rs b/talc-lang/src/parser/ast.rs index f25ce49..6a8e639 100644 --- a/talc-lang/src/parser/ast.rs +++ b/talc-lang/src/parser/ast.rs @@ -35,7 +35,9 @@ pub enum UnaryOp { Neg, Not, BitNot, - RangeEndless, + RangeFrom, + RangeTo, + RangeToIncl, } #[derive(Debug)] diff --git a/talc-lang/src/parser/lexer.rs b/talc-lang/src/parser/lexer.rs index 0d2010a..092c483 100644 --- a/talc-lang/src/parser/lexer.rs +++ b/talc-lang/src/parser/lexer.rs @@ -26,6 +26,9 @@ pub enum TokenKind { AmperEqual, Star, StarEqual, + StarDotDot, + StarDotDotStar, + StarDotDotEqual, Plus, PlusPlus, PlusPlusEqual, @@ -115,6 +118,9 @@ impl TokenKind { K::AmperEqual => "'&='", K::Star => "'*'", K::StarEqual => "'*='", + K::StarDotDot => "'*..'", + K::StarDotDotStar => "'*..*'", + K::StarDotDotEqual => "'*..='", K::Plus => "'+'", K::PlusPlus => "'++'", K::PlusPlusEqual => "'++='", @@ -489,6 +495,14 @@ impl<'s> Lexer<'s> { }, '*' => match self.and_peek()? { '=' => self.and_emit(K::StarEqual), + '.' => match self.and_peek()? { + '.' => match self.and_peek()? { + '*' => self.and_emit(K::StarDotDotStar), + '=' => self.and_emit(K::StarDotDotEqual), + _ => self.emit(K::StarDotDot), + }, + _ => self.unexpected(), + }, _ => self.emit(K::Star), }, '/' => match self.and_peek()? { diff --git a/talc-lang/src/parser/parser.rs b/talc-lang/src/parser/parser.rs index 708ac81..fd30bdb 100644 --- a/talc-lang/src/parser/parser.rs +++ b/talc-lang/src/parser/parser.rs @@ -10,31 +10,31 @@ use super::{ parse_float, parse_int_literal, parse_str_escapes, Lexer, ParserError, Span, SpanParserError, Token, TokenKind, }; -use num_complex::Complex64; +use num::complex::Complex64; use ExprKind as E; use TokenKind as T; type Result = std::result::Result; macro_rules! expect { - ($self:expr, $($t:tt)*) => {{ - let t = $self.next()?; - match t.kind { - $($t)* => t, - e => return Err(ParserError { span: t.span, msg: expect_inner!(e, $($t)*) }) - } - }}; + ($self:expr, $($t:tt)*) => {{ + let t = $self.next()?; + match t.kind { + $($t)* => t, + e => return Err(ParserError { span: t.span, msg: expect_inner!(e, $($t)*) }) + } + }}; } macro_rules! expect_inner { - ($e:expr, $($tok:path)|*) => { - { - let mut s = format!("unexpected token {}, expected ", $e.name()) - + $($tok.name() + ", " +)* ""; - s.truncate(s.len() - 2); - s - } - }; + ($e:expr, $($tok:path)|*) => { + { + let mut s = format!("unexpected token {}, expected ", $e.name()) + + $($tok.name() + ", " +)* ""; + s.truncate(s.len() - 2); + s + } + }; } macro_rules! try_next { @@ -48,9 +48,9 @@ macro_rules! try_next { } macro_rules! throw { - ($span:expr, $($t:tt)*) => { - return Err(ParserError { span: $span, msg: format!($($t)*) }) - }; + ($span:expr, $($t:tt)*) => { + return Err(ParserError { span: $span, msg: format!($($t)*) }) + }; } impl TokenKind { @@ -108,13 +108,15 @@ impl TokenKind { T::Minus => Some(UnaryOp::Neg), T::Not => Some(UnaryOp::Not), T::Tilde => Some(UnaryOp::BitNot), + T::StarDotDot => Some(UnaryOp::RangeTo), + T::StarDotDotEqual => Some(UnaryOp::RangeToIncl), _ => None, } } fn postfix_unary_op(self) -> Option { match self { - T::DotDotStar => Some(UnaryOp::RangeEndless), + T::DotDotStar => Some(UnaryOp::RangeFrom), _ => None, } } @@ -124,7 +126,7 @@ impl UnaryOp { fn precedence(self) -> u8 { match self { UnaryOp::Not => 0, - UnaryOp::RangeEndless => 40, + UnaryOp::RangeFrom | UnaryOp::RangeTo | UnaryOp::RangeToIncl => 40, UnaryOp::Neg | UnaryOp::BitNot => 110, } } @@ -159,25 +161,26 @@ fn b(t: T) -> Box { } impl TokenKind { + #[rustfmt::skip] fn expr_first(self) -> bool { - matches!( - self, - T::Return - | T::Continue - | T::Break | T::Var - | T::Global | T::Fn - | T::Not | T::Backslash - | T::Amper | T::Minus - | T::Tilde | T::Identifier - | T::LParen | T::LBrack - | T::LBrace | T::Dollar - | T::Do | T::If - | T::While | T::For - | T::Try | T::Integer - | T::Float | T::Imaginary - | T::String | T::Symbol - | T::True | T::False - | T::Nil + matches!(self, + | T::Return | T::Continue + | T::Break | T::Var + | T::Global | T::Fn + | T::Not | T::StarDotDot + | T::StarDotDotStar + | T::StarDotDotEqual + | T::Backslash | T::Amper + | T::Minus | T::Tilde + | T::Identifier + | T::LParen | T::LBrack + | T::LBrace | T::Dollar + | T::Do | T::If | T::While + | T::For | T::Try + | T::Integer | T::Float + | T::Imaginary | T::String + | T::Symbol | T::True + | T::False | T::Nil ) } } @@ -398,6 +401,7 @@ impl<'s> Parser<'s> { T::True => Ok(E::Literal(Value::Bool(true)).span(tok.span)), T::False => Ok(E::Literal(Value::Bool(false)).span(tok.span)), T::Nil => Ok(E::Literal(Value::Nil).span(tok.span)), + T::StarDotDotStar => Ok(E::Literal(Value::range_all()).span(tok.span)), t => throw!( tok.span, "unexpected token {}, expected expression", @@ -503,7 +507,7 @@ impl<'s> Parser<'s> { .. }) => { let args = self.parse_ident_list()?; - expect!(self, T::Dot); + expect!(self, T::Arrow); let body = self.parse_lambda()?; let body_span = body.span; Ok(E::Lambda(args, b(body)).span(span + body_span)) diff --git a/talc-lang/src/serial/mod.rs b/talc-lang/src/serial/mod.rs index d987959..6255875 100644 --- a/talc-lang/src/serial/mod.rs +++ b/talc-lang/src/serial/mod.rs @@ -1,11 +1,15 @@ use core::fmt; use std::io::{self, Write}; +use num::{bigint::Sign, BigInt}; + use crate::{ chunk::{Chunk, Instruction, TryTable}, lstring::LStr, + number::Int, + prelude::*, symbol::Symbol, - value::{function::Function, range::RangeType, Value}, + value::{function::Function, Value}, }; const MAGIC: [u8; 8] = *b"\x7fTALC\0\0\0"; @@ -109,12 +113,12 @@ impl<'w, W: Write> ProgramWriter<'w, W> { } Value::Int(n) => { self.write_u8(3)?; - self.write_i64(*n) + self.write_int(n.clone()) } Value::Ratio(r) => { self.write_u8(4)?; - self.write_i64(*r.numer())?; - self.write_i64(*r.denom()) + self.write_int(Int::from(r.numer().clone()))?; + self.write_int(Int::from(r.denom().clone())) } Value::Float(f) => { self.write_u8(5)?; @@ -127,13 +131,19 @@ impl<'w, W: Write> ProgramWriter<'w, W> { } Value::Range(r) => { self.write_u8(7)?; - match r.ty { - RangeType::Open => self.write_u8(0)?, - RangeType::Closed => self.write_u8(1)?, - RangeType::Endless => self.write_u8(2)?, + if let Some(s) = &r.start { + self.write_u8(1)?; + self.write_int(s.into())?; + } else { + self.write_u8(0)?; } - self.write_i64(r.start)?; - self.write_i64(r.stop) + if let Some(e) = &r.end { + self.write_u8(1)?; + self.write_int(e.into())?; + } else { + self.write_u8(0)?; + } + self.write_bool(r.inclusive) } Value::String(s) => { self.write_u8(8)?; @@ -194,11 +204,36 @@ impl<'w, W: Write> ProgramWriter<'w, W> { Ok(()) } + fn write_int(&mut self, n: Int) -> Result<()> { + if let Some(i) = n.to_i64() { + self.write_u8(0x80)?; + self.write_i64(i) + } else { + let big = BigInt::from(n); + let (sign, digits) = big.to_u64_digits(); + match sign { + Sign::Minus => self.write_u8(0xff)?, + Sign::NoSign => self.write_u8(0x00)?, + Sign::Plus => self.write_u8(0x01)?, + } + self.write_u32(digits.len() as u32)?; + for digit in digits { + self.write_u64(digit)?; + } + Ok(()) + } + } + fn write_i64(&mut self, n: i64) -> Result<()> { self.w.write_all(&n.to_le_bytes())?; Ok(()) } + fn write_u64(&mut self, n: u64) -> Result<()> { + self.w.write_all(&n.to_le_bytes())?; + Ok(()) + } + fn write_u32(&mut self, n: u32) -> Result<()> { self.w.write_all(&n.to_le_bytes())?; Ok(()) diff --git a/talc-lang/src/value/index.rs b/talc-lang/src/value/index.rs index 98184ff..9d970e2 100644 --- a/talc-lang/src/value/index.rs +++ b/talc-lang/src/value/index.rs @@ -1,12 +1,15 @@ use crate::{ exception::{throw, Result}, - symbol::{symbol, SYM_END_ITERATION, SYM_INDEX_ERROR, SYM_TYPE_ERROR}, + lstring::LStr, + number::Int, + prelude::*, + symbol::{symbol, SYM_END_ITERATION, SYM_INDEX_ERROR, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::function::NativeFunc, vm::Vm, vmcalliter, }; -use super::{range::RangeType, Value}; +use super::Value; impl Value { pub fn index(&self, idx: Self) -> Result { @@ -14,8 +17,15 @@ impl Value { match (self, idx) { (V::List(l), V::Int(i)) => { let l = l.borrow(); - if i >= 0 && (i as usize) < l.len() { - Ok(l[i as usize].clone()) + let Some(i) = i.to_usize() else { + throw!( + *SYM_INDEX_ERROR, + "index {i} out of bounds for list of length {}", + l.len() + ) + }; + if i < l.len() { + Ok(l[i].clone()) } else { throw!( *SYM_INDEX_ERROR, @@ -25,14 +35,20 @@ impl Value { } } (V::Range(r), V::Int(i)) => { - if i >= 0 - && (r.ty == RangeType::Endless - || i < r.stop || (r.ty == RangeType::Closed && i == r.stop)) - { - Ok((r.start + i).into()) - } else { - throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}") + if !i.is_negative() { + if let Some(s) = &r.start { + let n = &Int::from(s) + &i; + if let Some(e) = &r.end { + let e = Int::from(e); + if n < e || (n == e && r.inclusive) { + return Ok(n.into()) + } + } else { + return Ok(n.into()) + } + } } + throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}") } (V::Table(t), i) if i.hashable() => { let t = t.borrow(); @@ -40,58 +56,13 @@ impl Value { Ok(t.get(&i).cloned().unwrap_or(Value::Nil)) } (V::String(s), V::Range(r)) => { - let slen = s.len(); - match r.ty { - RangeType::Open => { - if r.start < 0 || r.start > slen as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for string of length {}", - r.stop, - slen - ) - } - if r.stop < 0 || r.stop > slen as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for string of length {}", - r.stop, - slen - ) - } - Ok(s[r.start as usize..r.stop as usize].into()) - } - RangeType::Closed => { - if r.start < 0 || r.start > slen as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for string of length {}", - r.stop, - slen - ) - } - if r.stop < 0 || r.stop >= slen as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for string of length {}", - r.stop, - slen - ) - } - Ok(s[r.start as usize..=r.stop as usize].into()) - } - RangeType::Endless => { - if r.start < 0 || r.start > slen as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for string of length {}", - r.stop, - slen - ) - } - Ok(s[r.start as usize..].into()) - } - } + let ns = r.index_slice(s.as_bytes()); + Ok(LStr::from_bytes(ns).into()) + } + (V::List(l), V::Range(r)) => { + let l = l.borrow(); + let nl = r.index_slice(l.as_slice()); + Ok(nl.to_vec().into()) } (col, idx) => { if let Ok(ii) = idx.clone().to_iter_function() { @@ -113,8 +84,15 @@ impl Value { match (self, idx) { (V::List(l), V::Int(i)) => { let mut l = l.borrow_mut(); - if i >= 0 && (i as usize) < l.len() { - l[i as usize] = val; + let Some(i) = i.to_usize() else { + throw!( + *SYM_INDEX_ERROR, + "index {i} out of bounds for list of length {}", + l.len() + ) + }; + if i < l.len() { + l[i] = val; Ok(()) } else { throw!( @@ -135,69 +113,27 @@ impl Value { Ok(()) } (V::List(t), V::Range(r)) => { - let iter = val.to_iter_function()?; - let mut vals = Vec::new(); - while let Some(v) = vmcalliter!(vm; iter.clone())? { - vals.push(v); + if let Value::List(l) = val { + let mut tm = t.borrow_mut(); + if let Ok(l) = l.try_borrow() { + r.replace_range(&mut tm, l.as_slice()); + Ok(()) + } else { + throw!( + *SYM_VALUE_ERROR, + "cannot replace part of a list with itself" + ) + } + } else { + let iter = val.to_iter_function()?; + let mut vals = Vec::new(); + while let Some(v) = vmcalliter!(vm; iter.clone())? { + vals.push(v); + } + let mut tm = t.borrow_mut(); + r.replace_range(&mut tm, &vals); + Ok(()) } - let mut tm = t.borrow_mut(); - match r.ty { - RangeType::Open => { - if r.start < 0 || r.start > tm.len() as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for list of length {}", - r.stop, - tm.len() - ) - } - if r.stop < 0 || r.stop > tm.len() as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for list of length {}", - r.stop, - tm.len() - ) - } - let end = tm.split_off(r.stop as usize); - tm.truncate(r.start as usize); - tm.extend(vals.into_iter().chain(end)); - } - RangeType::Closed => { - if r.start < 0 || r.start > tm.len() as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for list of length {}", - r.stop, - tm.len() - ) - } - if r.stop < 0 || r.stop >= tm.len() as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for list of length {}", - r.stop, - tm.len() - ) - } - let end = tm.split_off(r.stop as usize + 1); - tm.truncate(r.start as usize); - tm.extend(vals.into_iter().chain(end)); - } - RangeType::Endless => { - if r.start < 0 || r.start > tm.len() as i64 { - throw!( - *SYM_INDEX_ERROR, - "index {} out of bounds for list of length {}", - r.stop, - tm.len() - ) - } - tm.truncate(r.start as usize); - tm.extend(vals); - } - } - Ok(()) } (col, idx) => { if let Ok(ii) = idx.clone().to_iter_function() { diff --git a/talc-lang/src/value/mod.rs b/talc-lang/src/value/mod.rs index 33a1b4c..30b4917 100644 --- a/talc-lang/src/value/mod.rs +++ b/talc-lang/src/value/mod.rs @@ -3,23 +3,19 @@ use std::borrow::Cow; use std::io; use std::{cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, rc::Rc}; -pub use num_complex::Complex64; -use num_complex::ComplexFloat; -pub use num_rational::Rational64; +use num::{complex::Complex64, BigInt}; use crate::exception::{throw, Exception}; use crate::lstring::{LStr, LString}; +use crate::number::{Int, Range, Ratio}; +use crate::prelude::*; use crate::symbol::{Symbol, SYM_HASH_ERROR}; -use self::{ - function::{Function, NativeFunc}, - range::{Range, RangeType}, -}; +use self::function::{Function, NativeFunc}; pub mod function; pub mod index; pub mod ops; -pub mod range; type RcList = Rc>>; type RcTable = Rc>>; @@ -31,11 +27,11 @@ pub enum Value { Nil, Bool(bool), Symbol(Symbol), - Range(Range), + Range(Rc), - Int(i64), + Int(Int), Float(f64), - Ratio(Rational64), + Ratio(Rc), Complex(Complex64), Cell(Rc>), @@ -113,11 +109,7 @@ impl Value { } Ok(()) } - Self::Range(r) => match r.ty { - RangeType::Open => write!(w, "{}..{}", r.start, r.stop), - RangeType::Closed => write!(w, "{}..={}", r.start, r.stop), - RangeType::Endless => write!(w, "{}..*", r.start), - }, + Self::Range(r) => write!(w, "{r}"), Self::Int(i) => write!(w, "{i}"), Self::Float(x) => write!(w, "{x:?}"), Self::Ratio(r) => write!(w, "{}/{}", r.numer(), r.denom()), @@ -401,6 +393,23 @@ macro_rules! impl_from { } } }; + ($ty:ty, $var:ident, rchash) => { + impl From<$ty> for Value { + fn from(value: $ty) -> Self { + Self::$var(Rc::new(value)) + } + } + impl From> for Value { + fn from(value: Rc<$ty>) -> Self { + Self::$var(value) + } + } + impl From<$ty> for HashValue { + fn from(value: $ty) -> Self { + Self(Value::$var(Rc::new(value))) + } + } + }; ($ty:ty, $var:ident, into) => { impl From<$ty> for Value { fn from(value: $ty) -> Self { @@ -412,10 +421,12 @@ macro_rules! impl_from { impl_from!(bool, Bool, hash); impl_from!(Symbol, Symbol, hash); -impl_from!(Range, Range); -impl_from!(i64, Int, hash); +impl_from!(Range, Range, rc); +impl_from!(Int, Int, hash); +impl_from!(i64, Int, into); +impl_from!(BigInt, Int, into); impl_from!(f64, Float); -impl_from!(Rational64, Ratio, hash); +impl_from!(Ratio, Ratio, rchash); impl_from!(Complex64, Complex); impl_from!(HashMap, Table, rcref); impl_from!(Vec, List, rcref); @@ -439,3 +450,15 @@ impl From for Value { Self::Native(Rc::new(value)) } } + +impl From for Value { + fn from(value: String) -> Self { + Self::String(LStr::from_str(&value).into()) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self::String(LStr::from_str(value).into()) + } +} diff --git a/talc-lang/src/value/ops.rs b/talc-lang/src/value/ops.rs index 421eea2..631ba48 100644 --- a/talc-lang/src/value/ops.rs +++ b/talc-lang/src/value/ops.rs @@ -5,43 +5,31 @@ use std::{ rc::Rc, }; -use num_complex::{Complex64, ComplexFloat}; -use num_rational::Rational64; -use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Signed, Zero}; +use num::complex::Complex64; use crate::{ exception::{throw, Result}, lstring::LString, + number::{Int, Range, Ratio}, + prelude::*, symbol::{symbol, SYM_END_ITERATION, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, - value::range::RangeType, vm::Vm, }; use super::{ function::{FuncAttrs, NativeFunc}, - range::Range, HashValue, Value, }; -pub trait RatioExt { - fn to_f64(&self) -> f64; -} - -impl RatioExt for Rational64 { - fn to_f64(&self) -> f64 { - num_traits::ToPrimitive::to_f64(self).unwrap_or(f64::NAN) - } -} - impl Value { pub fn truthy(&self) -> bool { match self { Value::Nil => false, Value::Bool(b) => *b, - Value::Range(r) => matches!(r.len(), None | Some(1..)), - Value::Int(n) => *n != 0, + Value::Range(r) => !r.is_empty(), + Value::Int(n) => !n.is_zero(), Value::Float(x) => *x != 0.0 && !x.is_nan(), - Value::Ratio(r) => !(*r.numer() == 0 && *r.denom() != 0), + Value::Ratio(r) => !r.is_zero(), Value::Complex(c) => !(c.re == 0.0 && c.im == 0.0 || c.is_nan()), Value::String(s) => !s.is_empty(), Value::List(l) => l.borrow().len() > 0, @@ -59,17 +47,17 @@ impl Value { pub fn promote(a: Value, b: Value) -> (Value, Value) { use Value as V; match (&a, &b) { - (V::Int(x), V::Ratio(..)) => (V::Ratio((*x).into()), b), - (V::Int(x), V::Float(..)) => (V::Float(*x as f64), b), - (V::Int(x), V::Complex(..)) => (V::Complex((*x as f64).into()), b), - (V::Ratio(x), V::Float(..)) => (V::Float(x.to_f64()), b), - (V::Ratio(x), V::Complex(..)) => (V::Complex(x.to_f64().into()), b), + (V::Int(x), V::Ratio(..)) => (V::Ratio(Ratio::from(x).into()), b), + (V::Int(x), V::Float(..)) => (V::Float(x.to_f64_uc()), b), + (V::Int(x), V::Complex(..)) => (V::Complex(x.to_f64_uc().into()), b), + (V::Ratio(x), V::Float(..)) => (V::Float(x.to_f64_uc()), b), + (V::Ratio(x), V::Complex(..)) => (V::Complex(x.to_f64_uc().into()), b), (V::Float(x), V::Complex(..)) => (V::Complex(x.into()), b), - (V::Ratio(..), V::Int(y)) => (a, V::Ratio((*y).into())), - (V::Float(..), V::Int(y)) => (a, V::Float(*y as f64)), - (V::Complex(..), V::Int(y)) => (a, V::Complex((*y as f64).into())), - (V::Float(..), V::Ratio(y)) => (a, V::Float(y.to_f64())), - (V::Complex(..), V::Ratio(y)) => (a, V::Complex(y.to_f64().into())), + (V::Ratio(..), V::Int(y)) => (a, V::Ratio(Ratio::from(y).into())), + (V::Float(..), V::Int(y)) => (a, V::Float(y.to_f64_uc())), + (V::Complex(..), V::Int(y)) => (a, V::Complex(y.to_f64_uc().into())), + (V::Float(..), V::Ratio(y)) => (a, V::Float(y.to_f64_uc())), + (V::Complex(..), V::Ratio(y)) => (a, V::Complex(y.to_f64_uc().into())), (V::Complex(..), V::Float(y)) => (a, V::Complex((*y).into())), _ => (a, b), } @@ -84,20 +72,8 @@ impl Neg for Value { fn neg(self) -> Self::Output { use Value as V; match self { - V::Int(x) => { - if let Some(x) = x.checked_neg() { - Ok(V::Int(x)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when negating {self}") - } - } - V::Ratio(x) => { - if let Some(x) = Rational64::ZERO.checked_sub(&x) { - Ok(V::Ratio(x)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when negating {self}") - } - } + V::Int(x) => Ok(V::Int(-x)), + V::Ratio(x) => Ok(V::Ratio((-x.as_ref()).into())), V::Float(x) => Ok(V::Float(-x)), V::Complex(x) => Ok(V::Complex(-x)), a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}"), @@ -109,26 +85,8 @@ impl Value { pub fn abs(self) -> Result { use Value as V; match self { - V::Int(x) => { - if let Some(x) = x.checked_abs() { - Ok(V::Int(x)) - } else { - throw!( - *SYM_VALUE_ERROR, - "overflow when finding absolute value of {self}" - ) - } - } - V::Ratio(x) => { - if let Some((x, _)) = ratio_checked_absign(&x) { - Ok(V::Ratio(x)) - } else { - throw!( - *SYM_VALUE_ERROR, - "overflow when finding absolute value of {self}" - ) - } - } + V::Int(x) => Ok(V::Int(x.abs())), + V::Ratio(x) => Ok(V::Ratio(x.abs().into())), V::Float(x) => Ok(V::Float(x.abs())), V::Complex(x) => Ok(V::Float(x.norm())), a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}"), @@ -141,37 +99,29 @@ impl Value { ///////////////////////// macro_rules! impl_value_arith { - ($trait:ident, $name:ident, $checked:ident, $op:tt, $verb:literal) => { + ($trait:ident, $name:ident, $op:tt, $verb:literal) => { - impl $trait for Value { - type Output = Result; - fn $name(self, rhs: Value) -> Self::Output { - use Value as V; - let (a, b) = promote(self, rhs); - match (&a, &b) { - (V::Int(x), V::Int(y)) => if let Some(v) = x.$checked(y) { - Ok(V::Int(v)) - } else { - throw!(*SYM_VALUE_ERROR, concat!("overflow when ", $verb, "ing {} and {}"), a, b) - }, - (V::Ratio(x), V::Ratio(y)) => if let Some(v) = x.$checked(y) { - Ok(V::Ratio(v)) - } else { - throw!(*SYM_VALUE_ERROR, concat!("overflow when ", $verb, "ing {} and {}"), a, b) - }, - (V::Float(x), V::Float(y)) => Ok(V::Float(x $op y)), - (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x $op y)), - (l, r) => throw!(*SYM_TYPE_ERROR, concat!("cannot ", $verb, " {:#} and {:#}"), l, r) - } - } - } + impl $trait for Value { + type Output = Result; + fn $name(self, rhs: Value) -> Self::Output { + use Value as V; + let (a, b) = promote(self, rhs); + match (&a, &b) { + (V::Int(x), V::Int(y)) => Ok(V::Int(x $op y)), + (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio((x.as_ref() $op y.as_ref()).into())), + (V::Float(x), V::Float(y)) => Ok(V::Float(x $op y)), + (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x $op y)), + (l, r) => throw!(*SYM_TYPE_ERROR, concat!("cannot ", $verb, " {:#} and {:#}"), l, r) + } + } + } - }; + }; } -impl_value_arith!(Add, add, checked_add, +, "add"); -impl_value_arith!(Sub, sub, checked_sub, -, "subtract"); -impl_value_arith!(Mul, mul, checked_mul, *, "multiply"); +impl_value_arith!(Add, add, +, "add"); +impl_value_arith!(Sub, sub, -, "subtract"); +impl_value_arith!(Mul, mul, *, "multiply"); impl Div for Value { type Output = Result; @@ -179,18 +129,16 @@ impl Div for Value { use Value as V; let (a, b) = promote(self, rhs); match (&a, &b) { - (V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer division by 0"), - (V::Int(x), V::Int(y)) => Ok(Value::Ratio((*x, *y).into())), + (V::Int(_), V::Int(y)) if y.is_zero() => { + throw!(*SYM_VALUE_ERROR, "integer division by 0") + } + (V::Int(x), V::Int(y)) => { + Ok(Value::from(Ratio::new(x.clone().into(), y.clone().into()))) + } (V::Ratio(_), V::Ratio(r)) if r.is_zero() => { throw!(*SYM_VALUE_ERROR, "rational division by 0") } - (V::Ratio(x), V::Ratio(y)) => { - if let Some(v) = x.checked_div(y) { - Ok(V::Ratio(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when dividing {a} and {b}") - } - } + (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio((x.as_ref() / y.as_ref()).into())), (V::Float(x), V::Float(y)) => Ok(V::Float(x / y)), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x / y)), (l, r) => throw!(*SYM_TYPE_ERROR, "cannot divide {l:#} and {r:#}"), @@ -202,54 +150,20 @@ impl Div for Value { // modulo and integer division // /////////////////////////////////// -#[inline] -fn ratio_checked_absign(r: &Rational64) -> Option<(Rational64, Rational64)> { - let a = if r.is_negative() { - Rational64::ZERO.checked_sub(r)? - } else { - *r - }; - Some((a, r.signum())) -} - -#[inline] -fn ratio_checked_div_euclid(r1: &Rational64, r2: &Rational64) -> Option { - let (r2_abs, r2_sgn) = ratio_checked_absign(r2)?; - Some(r1.checked_div(&r2_abs)?.floor() * r2_sgn) -} - -#[inline] -fn ratio_checked_rem_euclid(r1: &Rational64, r2: &Rational64) -> Option { - let q = ratio_checked_div_euclid(r1, r2)?; - r1.checked_sub(&r2.checked_mul(&q)?) -} - impl Value { pub fn modulo(self, rhs: Value) -> Result { use Value as V; let (a, b) = promote(self, rhs); match (&a, &b) { - (V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer modulo by 0"), - (V::Int(x), V::Int(y)) => { - if let Some(v) = x.checked_rem_euclid(*y) { - Ok(V::Int(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when calculating {a} modulo {b}") - } + (V::Int(_), V::Int(y)) if y.is_zero() => { + throw!(*SYM_VALUE_ERROR, "integer modulo by 0") } - + (V::Int(x), V::Int(y)) => Ok(V::Int(x.rem_euclid(y))), (V::Ratio(_), V::Ratio(y)) if y.is_zero() => { throw!(*SYM_VALUE_ERROR, "rational modulo by 0") } - (V::Ratio(x), V::Ratio(y)) => { - if let Some(v) = ratio_checked_rem_euclid(x, y) { - Ok(V::Ratio(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when calculating {a} modulo {b}") - } - } - - (V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(*y))), + (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x.rem_euclid(y).into())), + (V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(y))), (V::Complex(x), V::Complex(y)) => { let n = x / y; let n = Complex64::new(n.re().floor(), n.im().floor()); @@ -263,33 +177,15 @@ impl Value { use Value as V; let (a, b) = promote(self, rhs); match (&a, &b) { - (V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer divsion by 0"), - (V::Int(x), V::Int(y)) => { - if let Some(v) = x.checked_div_euclid(*y) { - Ok(V::Int(v)) - } else { - throw!( - *SYM_VALUE_ERROR, - "overflow when integer dividing {a} and {b}" - ) - } + (V::Int(_), V::Int(y)) if y.is_zero() => { + throw!(*SYM_VALUE_ERROR, "integer divsion by 0") } - + (V::Int(x), V::Int(y)) => Ok(Value::from(x.div_euclid(y))), (V::Ratio(_), V::Ratio(y)) if y.is_zero() => { throw!(*SYM_VALUE_ERROR, "integer division by 0") } - (V::Ratio(x), V::Ratio(y)) => { - if let Some(v) = ratio_checked_div_euclid(x, y) { - Ok(V::Ratio(v)) - } else { - throw!( - *SYM_VALUE_ERROR, - "overflow when integer dividing {a} and {b}" - ) - } - } - - (V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(*y))), + (V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x.rem_euclid(y).into())), + (V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(y))), (V::Complex(x), V::Complex(y)) => { let n = x / y; Ok(V::from(Complex64::new(n.re().floor(), n.im().floor()))) @@ -304,29 +200,11 @@ impl Value { ////////////////////// #[inline] -fn ipow(n: i64, p: u64) -> Option { - match (n, p) { - (0, 0) => None, - (0, _) => Some(0), - (_, 0) => Some(1), - (1, _) => Some(1), - (-1, p) => (-1_i64).checked_pow((p % 2) as u32), - (_, p) if p > u32::MAX as u64 => None, - (n, p) => n.checked_pow(p as u32), - } -} - -#[inline] -fn rpow(n: i64, d: i64, p: i64) -> Option<(i64, i64)> { - match p { - i64::MIN => match (n, d) { - (0, _) => Some((0, 1)), - (1, 1) => Some((1, 1)), - (-1, 1) => Some((-1, 1)), - _ => None, - }, - 0.. => Some((ipow(n, p as u64)?, ipow(d, p as u64)?)), - _ => Some((ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?)), +fn rpow(n: &Int, d: &Int, p: &Int) -> Option { + let (sign, p) = (p.isignum(), p.abs()); + match sign { + 0.. => Some(Ratio::new(n.ipow(&p)?.into(), d.ipow(&p)?.into())), + _ => Some(Ratio::new(d.ipow(&p)?.into(), n.ipow(&p)?.into())), } } @@ -334,10 +212,10 @@ impl Value { pub fn pow(self, rhs: Value) -> Result { use Value as V; if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) { - if x.is_zero() && *y == 0 { + if x.is_zero() && y.is_zero() { throw!(*SYM_VALUE_ERROR, "rational zero to integer zero power") } - let Some(v) = rpow(*(*x).numer(), *(*x).denom(), *y) else { + let Some(v) = rpow(&x.numer().into(), &x.denom().into(), y) else { throw!( *SYM_VALUE_ERROR, "overflow when raising {self} to the power {rhs}" @@ -347,11 +225,11 @@ impl Value { } let (a, b) = promote(self, rhs); match (&a, &b) { - (V::Int(0), V::Int(0)) => { + (V::Int(x), V::Int(y)) if x.is_zero() && y.is_zero() => { throw!(*SYM_VALUE_ERROR, "integer zero to integer zero power") } - (V::Int(x), V::Int(y @ 0..)) => { - if let Some(v) = ipow(*x, *y as u64) { + (V::Int(x), V::Int(y)) if !y.is_negative() => { + if let Some(v) = x.ipow(y) { Ok(V::Int(v)) } else { throw!( @@ -361,7 +239,7 @@ impl Value { } } (V::Int(x), V::Int(y)) => { - if let Some(v) = rpow(*x, 1, *y) { + if let Some(v) = rpow(x, &Int::ONE, y) { Ok(V::Ratio(v.into())) } else { throw!( @@ -370,7 +248,7 @@ impl Value { ) } } - (V::Ratio(x), V::Ratio(y)) => Ok(V::Float(x.to_f64().powf(y.to_f64()))), + (V::Ratio(x), V::Ratio(y)) => Ok(V::Float(x.to_f64_uc().powf(y.to_f64_uc()))), (V::Float(x), V::Float(y)) => Ok(V::Float(x.powf(*y))), (V::Complex(x), V::Complex(y)) => Ok(V::Complex(x.powc(*y))), (l, r) => throw!(*SYM_TYPE_ERROR, "cannot exponentiate {l:#} and {r:#}"), @@ -389,7 +267,16 @@ impl Shl for Value { use Value as V; match (self, rhs) { (V::Int(a), V::Int(b)) => { - Ok(Value::Int((a as u64).wrapping_shl(b as u32) as i64)) + if b.is_negative() { + throw!( + *SYM_TYPE_ERROR, + "cannot shift {a} left by negative amount {b}" + ) + } + if b.gt_i64(u32::MAX as i64) { + throw!(*SYM_TYPE_ERROR, "cannot shift {a} left by {b}: too large") + } + Ok(Value::Int(a << b)) } (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} left by {r:#}"), } @@ -403,7 +290,13 @@ impl Shr for Value { use Value as V; match (self, rhs) { (V::Int(a), V::Int(b)) => { - Ok(Value::Int((a as u64).wrapping_shr(b as u32) as i64)) + if b.is_negative() { + throw!( + *SYM_TYPE_ERROR, + "cannot shift {a} right by negative amount {b}" + ) + } + Ok(Value::Int(a >> b)) } (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}"), } @@ -464,7 +357,6 @@ impl Not for Value { impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { - use super::range::RangeType as Rty; use Value as V; match (self, other) { (V::Nil, V::Nil) => true, @@ -473,31 +365,23 @@ impl PartialEq for Value { (V::Ratio(a), V::Ratio(b)) => *a == *b, (V::Float(a), V::Float(b)) => *a == *b, (V::Complex(a), V::Complex(b)) => *a == *b, - (V::Int(a), V::Ratio(b)) => Rational64::from(*a) == *b, - (V::Ratio(a), V::Int(b)) => *a == Rational64::from(*b), - (V::Int(a), V::Float(b)) => *a as f64 == *b, - (V::Float(a), V::Int(b)) => *a == *b as f64, - (V::Int(a), V::Complex(b)) => Complex64::from(*a as f64) == *b, - (V::Complex(a), V::Int(b)) => *a == Complex64::from(*b as f64), - (V::Ratio(a), V::Float(b)) => a.to_f64() == *b, - (V::Float(a), V::Ratio(b)) => *a == b.to_f64(), - (V::Ratio(a), V::Complex(b)) => Complex64::from(a.to_f64()) == *b, - (V::Complex(a), V::Ratio(b)) => *a == Complex64::from(b.to_f64()), + (V::Int(a), V::Ratio(b)) => b.is_integer() && a.to_big().as_ref() == b.numer(), + (V::Ratio(a), V::Int(b)) => a.is_integer() && a.numer() == b.to_big().as_ref(), + (V::Int(a), V::Float(b)) => a.to_f64_uc() == *b, + (V::Float(a), V::Int(b)) => *a == b.to_f64_uc(), + (V::Int(a), V::Complex(b)) => Complex64::from(a.to_f64_uc()) == *b, + (V::Complex(a), V::Int(b)) => *a == Complex64::from(b.to_f64_uc()), + (V::Ratio(a), V::Float(b)) => a.to_f64_uc() == *b, + (V::Float(a), V::Ratio(b)) => *a == b.to_f64_uc(), + (V::Ratio(a), V::Complex(b)) => Complex64::from(a.to_f64_uc()) == *b, + (V::Complex(a), V::Ratio(b)) => *a == Complex64::from(b.to_f64_uc()), (V::Float(a), V::Complex(b)) => Complex64::from(*a) == *b, (V::Complex(a), V::Float(b)) => *a == Complex64::from(*b), (V::String(a), V::String(b)) => *a == *b, (V::List(a), V::List(b)) => *a.borrow() == *b.borrow(), (V::Symbol(a), V::Symbol(b)) => a == b, (V::Cell(a), V::Cell(b)) => *a.borrow() == *b.borrow(), - (V::Range(a), V::Range(b)) => match (a.ty, b.ty) { - (Rty::Open, Rty::Open) | (Rty::Closed, Rty::Closed) => { - a.start == b.start && a.stop == b.stop - } - (Rty::Open, Rty::Closed) => a.start == b.start && a.stop == b.stop - 1, - (Rty::Closed, Rty::Open) => a.start == b.start && a.stop - 1 == b.stop, - (Rty::Endless, Rty::Endless) => a.start == b.start, - _ => false, - }, + (V::Range(a), V::Range(b)) => a.as_ref() == b.as_ref(), (V::Native(a), b) => a.partial_eq(b), (a, V::Native(b)) => b.partial_eq(a), _ => false, @@ -514,12 +398,12 @@ impl PartialOrd for Value { (V::Int(a), V::Int(b)) => a.partial_cmp(b), (V::Ratio(a), V::Ratio(b)) => a.partial_cmp(b), (V::Float(a), V::Float(b)) => a.partial_cmp(b), - (V::Int(a), V::Ratio(b)) => Rational64::from(*a).partial_cmp(b), - (V::Ratio(a), V::Int(b)) => a.partial_cmp(&Rational64::from(*b)), - (V::Int(a), V::Float(b)) => (*a as f64).partial_cmp(b), - (V::Float(a), V::Int(b)) => a.partial_cmp(&(*b as f64)), - (V::Ratio(a), V::Float(b)) => a.to_f64().partial_cmp(b), - (V::Float(a), V::Ratio(b)) => a.partial_cmp(&b.to_f64()), + (V::Int(a), V::Ratio(b)) => Ratio::from(a).partial_cmp(b), + (V::Ratio(a), V::Int(b)) => a.as_ref().partial_cmp(&Ratio::from(b)), + (V::Int(a), V::Float(b)) => a.to_f64_uc().partial_cmp(b), + (V::Float(a), V::Int(b)) => a.partial_cmp(&b.to_f64_uc()), + (V::Ratio(a), V::Float(b)) => a.to_f64_uc().partial_cmp(b), + (V::Float(a), V::Ratio(b)) => a.partial_cmp(&b.to_f64_uc()), (V::String(a), V::String(b)) => a.partial_cmp(b), (V::List(a), V::List(b)) => a.borrow().partial_cmp(&*b.borrow()), (V::Symbol(a), V::Symbol(b)) => a.partial_cmp(b), @@ -574,19 +458,9 @@ impl Value { } } - pub fn range(&self, other: &Self, closed: bool) -> Result { - if let (Value::Int(start), Value::Int(stop)) = (self, other) { - let ty = if closed { - RangeType::Closed - } else { - RangeType::Open - }; - Ok(Range { - start: *start, - stop: *stop, - ty, - } - .into()) + pub fn range(&self, other: &Self) -> Result { + if let (Value::Int(start), Value::Int(end)) = (self, other) { + Ok(Range::range(start, end).into()) } else { throw!( *SYM_TYPE_ERROR, @@ -595,19 +469,48 @@ impl Value { } } - pub fn range_endless(&self) -> Result { - if let Value::Int(start) = self { - Ok(Range { - start: *start, - stop: 0, - ty: RangeType::Endless, - } - .into()) + pub fn range_incl(&self, other: &Self) -> Result { + if let (Value::Int(start), Value::Int(end)) = (self, other) { + Ok(Range::range_incl(start, end).into()) } else { - throw!(*SYM_TYPE_ERROR, "cannot create endless range from {self:#}") + throw!( + *SYM_TYPE_ERROR, + "cannot create range between {self:#} and {other:#}" + ) } } + pub fn range_from(&self) -> Result { + if let Value::Int(start) = self { + Ok(Range::range_from(start).into()) + } else { + throw!(*SYM_TYPE_ERROR, "cannot create range from {self:#}") + } + } + + pub fn range_to(&self) -> Result { + if let Value::Int(end) = self { + Ok(Range::range_to(end).into()) + } else { + throw!(*SYM_TYPE_ERROR, "cannot create range to {self:#}") + } + } + + pub fn range_to_incl(&self) -> Result { + if let Value::Int(end) = self { + Ok(Range::range_to_incl(end).into()) + } else { + throw!(*SYM_TYPE_ERROR, "cannot create range to {self:#}") + } + } + + pub fn range_all() -> Self { + thread_local! { + static RANGE_ALL: Rc = Rc::new(Range::range_all()); + } + Self::Range(RANGE_ALL.with(|x| x.clone())) + } + pub fn to_cell(self) -> Self { Value::Cell(Rc::new(RefCell::new(self))) } @@ -630,7 +533,10 @@ impl Value { match self { Self::Function(_) | Self::NativeFunc(_) => Ok(self), Self::Range(range) => { - let range_iter = RefCell::new(range.into_iter()); + let Some(iter) = range.try_iterate() else { + throw!(*SYM_VALUE_ERROR, "cannot iterate range {range}") + }; + let range_iter = RefCell::new(iter); let f = move |_: &mut Vm, _: Vec| -> Result { Ok(Value::iter_pack( range_iter.borrow_mut().next().map(Value::from), diff --git a/talc-lang/src/value/range.rs b/talc-lang/src/value/range.rs deleted file mode 100644 index a6afa49..0000000 --- a/talc-lang/src/value/range.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RangeType { - Open, - Closed, - Endless, -} - -#[derive(Clone, Copy, Debug)] -pub struct Range { - pub start: i64, - pub stop: i64, - pub ty: RangeType, -} - -impl Range { - pub fn len(&self) -> Option { - match self.ty { - RangeType::Open => Some((self.stop - self.start).max(0) as usize), - RangeType::Closed => Some((self.stop - self.start + 1).max(0) as usize), - RangeType::Endless => None, - } - } - - pub fn is_empty(&self) -> bool { - match self.ty { - RangeType::Open => self.stop - self.start <= 0, - RangeType::Closed => self.stop - self.start < 0, - RangeType::Endless => false, - } - } -} - -impl IntoIterator for Range { - type Item = i64; - type IntoIter = Box>; - fn into_iter(self) -> Self::IntoIter { - match self.ty { - RangeType::Open => Box::new(self.start..self.stop), - RangeType::Closed => Box::new(self.start..=self.stop), - RangeType::Endless => Box::new(self.start..), - } - } -} diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index 5d93251..bd5e63e 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -74,8 +74,8 @@ pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result { BinaryOp::Ge => a.val_cmp(&b).map(|o| Value::Bool(o != Ordering::Less)), BinaryOp::Lt => a.val_cmp(&b).map(|o| Value::Bool(o == Ordering::Less)), BinaryOp::Le => a.val_cmp(&b).map(|o| Value::Bool(o != Ordering::Greater)), - BinaryOp::Range => a.range(&b, false), - BinaryOp::RangeIncl => a.range(&b, true), + BinaryOp::Range => a.range(&b), + BinaryOp::RangeIncl => a.range_incl(&b), BinaryOp::Concat => a.concat(&b), BinaryOp::Append => a.append(b), } @@ -86,7 +86,9 @@ pub fn unary_op(o: UnaryOp, a: Value) -> Result { UnaryOp::Neg => -a, UnaryOp::Not => Ok(Value::Bool(!a.truthy())), UnaryOp::BitNot => a.not(), - UnaryOp::RangeEndless => a.range_endless(), + UnaryOp::RangeFrom => a.range_from(), + UnaryOp::RangeTo => a.range_to(), + UnaryOp::RangeToIncl => a.range_to_incl(), } } @@ -352,7 +354,7 @@ impl Vm { self.push(Value::Symbol(sym)); } // [] -> [n] - I::Int(n) => self.push(Value::Int(i64::from(n))), + I::Int(n) => self.push(Value::from(i64::from(n))), // [x] -> [x,x] I::Dup => self.push(self.stack[self.stack.len() - 1].clone()), // [x,y] -> [x,y,x,y] diff --git a/talc-lang/tests/vm.rs b/talc-lang/tests/vm.rs index 75f3a27..7bd8beb 100644 --- a/talc-lang/tests/vm.rs +++ b/talc-lang/tests/vm.rs @@ -24,35 +24,35 @@ fn assert_eval(src: &str, value: Value) { fn scope() { assert_eval( " - var x = 7 - var y = 10 - do - var x = 200 - y = 100 - end - x + y - ", - Value::Int(7 + 100), + var x = 7 + var y = 10 + do + var x = 200 + y = 100 + end + x + y + ", + Value::from(7 + 100), ); assert_eval( " - var cond = true - var z = 2 - if cond then var z = 5 else var z = 6 end - z - ", - Value::Int(2), + var cond = true + var z = 2 + if cond then var z = 5 else var z = 6 end + z + ", + Value::from(2), ); assert_eval( " - var i = 55 - var j = 66 - for i in 0..10 do - j = i - end - i + j - ", - Value::Int(55 + 9), + var i = 55 + var j = 66 + for i in 0..10 do + j = i + end + i + j + ", + Value::from(55 + 9), ); } @@ -60,26 +60,26 @@ fn scope() { fn forloop() { assert_eval( " - sum = 0 - for i in 0..5 do - for j in 0..10 do - sum += i*j - end - end - sum - ", - Value::Int(45 * 10), + sum = 0 + for i in 0..5 do + for j in 0..10 do + sum += i*j + end + end + sum + ", + Value::from(45 * 10), ); assert_eval( " - map = {a=3, b=2, c=4, d=7} - prod = 1 - for k in map do - prod *= map[k] - end - prod - ", - Value::Int(3 * 2 * 4 * 7), + map = {a=3, b=2, c=4, d=7} + prod = 1 + for k in map do + prod *= map[k] + end + prod + ", + Value::from(3 * 2 * 4 * 7), ); } @@ -87,28 +87,28 @@ fn forloop() { fn closures() { assert_eval( " - var x = 2 - next = \\. do x = x * 2 + 1 end - next() + next() + next() - ", - Value::Int(5 + 11 + 23), + var x = 2 + next = \\->do x = x * 2 + 1 end + next() + next() + next() + ", + Value::from(5 + 11 + 23), ); assert_eval( " - var x = 0 - fn outer(n) do - fn inner() do - x += n - n += 1 - x - end - end + var x = 0 + fn outer(n) do + fn inner() do + x += n + n += 1 + x + end + end - var f = outer(2) - var g = outer(6) - f() + f() + g() + g() + f() - ", - Value::Int(2 + 5 + 11 + 18 + 22), + var f = outer(2) + var g = outer(6) + f() + f() + g() + g() + f() + ", + Value::from(2 + 5 + 11 + 18 + 22), ); } @@ -116,15 +116,15 @@ fn closures() { fn tailcall() { assert_eval( " - fn test(n, a) do - if n <= 0 then - a - else - self(n-1, n+a) - end - end - test(24, 0) - ", - Value::Int(300), + fn test(n, a) do + if n <= 0 then + a + else + self(n-1, n+a) + end + end + test(24, 0) + ", + Value::from(300), ) // = 24 + 23 + ... + 1 } diff --git a/talc-std/Cargo.toml b/talc-std/Cargo.toml index bfafd4d..07a8df7 100644 --- a/talc-std/Cargo.toml +++ b/talc-std/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" talc-lang = { path = "../talc-lang" } talc-macros = { path = "../talc-macros" } lazy_static = "1.5" +num-bigint = { version = "0.4", features = ["rand"], optional = true } regex = { version = "1.11", optional = true } rand = { version = "0.8", optional = true } [features] default = ["rand", "regex"] -rand = ["dep:rand"] +rand = ["dep:rand", "dep:num-bigint"] regex = ["dep:regex"] diff --git a/talc-std/src/collection.rs b/talc-std/src/collection.rs index 985cdbc..82f957d 100644 --- a/talc-std/src/collection.rs +++ b/talc-std/src/collection.rs @@ -2,6 +2,8 @@ use std::cmp::Ordering; use talc_lang::{ exception::{exception, Result}, + number::Int, + prelude::*, symbol::{symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{function::NativeFunc, Value}, @@ -69,7 +71,7 @@ pub fn clear(_: &mut Vm, args: Vec) -> Result { Ok(col) } -fn call_comparison(vm: &mut Vm, by: Value, l: Value, r: Value) -> Result { +fn call_comparison(vm: &mut Vm, by: Value, l: Value, r: Value) -> Result { let ord = vmcall!(vm; by, l, r)?; let Value::Int(ord) = ord else { throw!( @@ -117,17 +119,17 @@ fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, u while eq <= gt { let ord = call_comparison(vm, by.clone(), vals[eq].clone(), pivot.clone())?; - match ord { - ..=-1 => { + match ord.cmp(&Int::ZERO) { + Ordering::Less => { vals.swap(eq, lt); lt += 1; eq += 1; } - 1.. => { + Ordering::Greater => { vals.swap(eq, gt); gt -= 1; } - 0 => { + Ordering::Equal => { eq += 1; } } @@ -152,7 +154,7 @@ fn insertion_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<()> { while j > 0 { let ord = call_comparison(vm, by.clone(), vals[j - 1].clone(), vals[j].clone())?; - if ord <= 0 { + if !ord.is_positive() { break } vals.swap(j, j - 1); @@ -212,9 +214,9 @@ pub fn sort_key(vm: &mut Vm, args: Vec) -> Result { let a = vmcall!(vm; key.clone(), a)?; let b = vmcall!(vm; key.clone(), b)?; match a.partial_cmp(&b) { - Some(Ordering::Greater) => Ok(Value::Int(1)), - Some(Ordering::Equal) => Ok(Value::Int(0)), - Some(Ordering::Less) => Ok(Value::Int(-1)), + Some(Ordering::Greater) => Ok(Value::from(1)), + Some(Ordering::Equal) => Ok(Value::from(0)), + Some(Ordering::Less) => Ok(Value::from(-1)), None => throw!( *SYM_VALUE_ERROR, "values returned from sort key were incomparable" diff --git a/talc-std/src/file.rs b/talc-std/src/file.rs index 6d89ce2..27f87ab 100644 --- a/talc-std/src/file.rs +++ b/talc-std/src/file.rs @@ -13,6 +13,7 @@ use lazy_static::lazy_static; use talc_lang::{ exception::Result, lstring::LString, + prelude::*, symbol::{symbol, Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, @@ -294,7 +295,7 @@ pub fn read(_: &mut Vm, args: Vec) -> Result { let Value::Int(nbytes) = nbytes else { throw!(*SYM_TYPE_ERROR, "read expected integer, got {nbytes:#}") }; - let Ok(nbytes) = usize::try_from(nbytes) else { + let Some(nbytes) = nbytes.to_usize() else { throw!( *SYM_VALUE_ERROR, "number of bytes to read must be nonnegative" @@ -464,11 +465,14 @@ pub fn tcp_connect_timeout(_: &mut Vm, args: Vec) -> Result { 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::Int(n) => match n.to_usize() { + Some(n) => Duration::from_secs(n as u64), + None => throw!(*SYM_VALUE_ERROR, "tcp_connect_timeout: invalid timeout",), + }, Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => { Duration::from_secs_f64(n) } - Value::Int(_) | Value::Float(_) => { + Value::Float(_) => { throw!(*SYM_VALUE_ERROR, "tcp_connect_timeout: invalid timeout") } _ => throw!( @@ -755,7 +759,7 @@ pub fn exit_code(_: &mut Vm, args: Vec) -> Result { match proc.wait() { Ok(code) => Ok(code .code() - .map(|c| Value::Int(c as i64)) + .map(|c| Value::from(c as i64)) .unwrap_or_default()), Err(e) => throw!(*SYM_IO_ERROR, "{e}"), } diff --git a/talc-std/src/format.rs b/talc-std/src/format.rs index 3f6f9ab..c4f98e9 100644 --- a/talc-std/src/format.rs +++ b/talc-std/src/format.rs @@ -3,10 +3,12 @@ use std::io::Write; use talc_lang::{ exception::{exception, Result}, lstring::{LStr, LString}, - parser::{parse_int, to_lstring_radix}, + number::{Complex, Int, Ratio}, + parser::parse_int, + prelude::*, symbol::SYM_TYPE_ERROR, throw, - value::{Complex64, Rational64, Value}, + value::Value, vm::Vm, }; use talc_macros::native_func; @@ -235,16 +237,16 @@ fn get_val(args: &[Value], i: FmtIndex, faidx: &mut usize) -> Result { let Value::Int(v) = v else { throw!( *SYM_FORMAT_ERROR, - "expected positive integer argument, found {v:#}" + "expected small positive integer argument, found {v:#}" ) }; - if *v < 0 { - throw!( + match v.to_usize() { + Some(v) => Ok(v), + None => throw!( *SYM_FORMAT_ERROR, - "expected positive integer argument, found {v}" - ) + "expected small positive integer argument, found {v}" + ), } - Ok(*v as usize) } fn format_float( @@ -270,7 +272,7 @@ fn format_float( } fn format_complex( - cx: Complex64, + cx: Complex, prec: Option, ty: FmtType, buf: &mut LString, @@ -286,7 +288,7 @@ fn format_complex( } fn format_int( - n: i64, + n: &Int, prec: Option, ty: FmtType, buf: &mut LString, @@ -296,26 +298,25 @@ fn format_int( throw!(*SYM_FORMAT_ERROR, "invalid format specifier for {ty_name}") } let res = match ty { - FmtType::Str => write!(buf, "{}", Value::Int(n)), - FmtType::Repr => write!(buf, "{:#}", Value::Int(n)), - FmtType::Hex(caps) => write!(buf, "{}", to_lstring_radix(n, 16, caps)), - FmtType::Oct => write!(buf, "{}", to_lstring_radix(n, 8, false)), - FmtType::Sex => write!(buf, "{}", to_lstring_radix(n, 6, false)), - FmtType::Bin => write!(buf, "{}", to_lstring_radix(n, 2, false)), + FmtType::Str | FmtType::Repr => write!(buf, "{}", n), + FmtType::Hex(caps) => write!(buf, "{}", n.to_str_radix_case(16, caps)), + FmtType::Oct => write!(buf, "{}", n.to_str_radix(8)), + FmtType::Sex => write!(buf, "{}", n.to_str_radix(6)), + FmtType::Bin => write!(buf, "{}", n.to_str_radix(2)), _ => throw!(*SYM_FORMAT_ERROR, "invalid format specifier for {ty_name}"), }; res.map_err(|e| exception!(*SYM_FORMAT_ERROR, "{e}")) } fn format_ratio( - n: Rational64, + n: &Ratio, prec: Option, ty: FmtType, buf: &mut LString, ) -> Result<()> { - format_int(*n.numer(), prec, ty, buf, "ratio")?; + format_int(&Int::from(n.numer()), prec, ty, buf, "ratio")?; buf.push_char('/'); - format_int(*n.denom(), prec, ty, buf, "ratio")?; + format_int(&Int::from(n.denom()), prec, ty, buf, "ratio")?; Ok(()) } @@ -383,8 +384,8 @@ fn format_arg( let width_val = fmtcode.width.map(|i| get_val(args, i, faidx)).transpose()?; match fmt_arg { - Value::Int(n) => format_int(*n, prec_val, fmtcode.ty, &mut buf, "integer")?, - Value::Ratio(n) => format_ratio(*n, prec_val, fmtcode.ty, &mut buf)?, + Value::Int(n) => format_int(n, prec_val, fmtcode.ty, &mut buf, "integer")?, + Value::Ratio(n) => format_ratio(n, prec_val, fmtcode.ty, &mut buf)?, Value::Float(f) => format_float(*f, prec_val, fmtcode.ty, &mut buf, "float")?, Value::Complex(n) => format_complex(*n, prec_val, fmtcode.ty, &mut buf)?, Value::String(s) => format_string(s, prec_val, fmtcode.ty, &mut buf)?, diff --git a/talc-std/src/ints.rs b/talc-std/src/ints.rs new file mode 100644 index 0000000..1ee3a7f --- /dev/null +++ b/talc-std/src/ints.rs @@ -0,0 +1,452 @@ +use talc_lang::{ + exception::Result, + number::Int, + prelude::*, + symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, + throw, + value::Value, + vm::Vm, + vmcalliter, +}; +use talc_macros::native_func; + +use crate::unpack_args; + +fn miller_rabin_iter(n: &Int, s: u64, d: &Int, a: &Int) -> bool { + if a > n { + return true + } + let mut x = a.modpow(d, n); + for _ in 0..s { + let y = &(&x * &x) % n; + if y.eq_i64(1) && x.ne_i64(1) && x != (n - 1) { + return false + } + x = y; + } + x.eq_i64(1) +} + +fn miller_rabin(n: &Int) -> bool { + if n.lt_i64(2) { + return false + } + if n.is_even() { + return n.eq_i64(2) + } + for p in [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] { + if (n % p).is_zero() { + return n.eq_i64(p) + } + } + if n.lt_i64(41) { + return false + } + let s = (n - 1).trailing_zeros().unwrap(); + let d = n >> &Int::from(s); + for a in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] { + if !miller_rabin_iter(n, s, &d, &Int::from_small(a)) { + return false + } + } + true +} + +fn trial_div(n: u32) -> bool { + if n < 2 { + return false + } + if n % 2 == 0 { + return n == 2 + } + for p in [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] { + if n % p == 0 { + return n == p + } + } + if n < 41 { + return false + } + let lim = n.sqrt(); + let mut i = 41; + while i <= lim + 1 { + if n % i == 0 { + return false + } + if n % (i + 2) == 0 { + return false + } + i += 6; + } + true +} + +fn is_prime_inner(n: &Int) -> bool { + if n.lt_i64(2) { + return false + } + if let Some(m) = n.to_u32() { + trial_div(m) + } else { + miller_rabin(n) + } +} + +fn pollard_rho(n: &Int, a: Int, b: &Int) -> Option { + let mut x = a.clone(); + let mut y = a.clone(); + let mut d = Int::ONE; + while d.eq_i64(1) { + x = &(&(&x * &x) + b) % n; + y = &(&(&y * &y) + b) % n; + y = &(&(&y * &y) + b) % n; + d = (&x - &y).gcd(n); + } + if &d == n { + None + } else { + Some(d) + } +} + +fn pollard_rho_checks(n: &Int) -> Int { + let nminus2 = n - 2; + let mut a = Int::from_small(2); + while &a < n { + let mut b = Int::ONE; + while b < nminus2 { + if let Some(f) = pollard_rho(n, a.clone(), &b) { + return f + } + b += 1; + } + a += 1; + } + // we should never get here, hopefully + n.clone() +} + +fn recurse_pollard_rho(n: &Int, factors: &mut Vec) { + if is_prime_inner(n) { + factors.push(n.clone()); + return + } + let factor = pollard_rho_checks(n); + recurse_pollard_rho(&factor, factors); + recurse_pollard_rho(&(n / &factor), factors); +} + +fn factorize(n: &Int) -> Vec { + let mut n = n.abs(); + let mut factors = Vec::new(); + if n.lt_i64(2) { + return factors + } + let tz = n.trailing_zeros().unwrap(); + n = &n >> &Int::from(tz); + for _ in 0..tz { + factors.push(Int::from_small(2)); + } + while (&n % 3).is_zero() { + n /= 3; + factors.push(Int::from_small(3)); + } + + let nsqrt = n.sqrt(); + let limit = nsqrt.min(Int::from_small(1 << 15)); + + let mut i = Int::from_small(5); + while i < limit && n.gt_i64(1) { + while (&n % &i).is_zero() { + n /= &i; + factors.push(i.clone()); + } + i += 2; + while (&n % &i).is_zero() { + n /= &i; + factors.push(i.clone()); + } + i += 4; + } + if &i * &i <= n { + let len = factors.len(); + recurse_pollard_rho(&n, &mut factors); + factors[len..].sort_unstable(); + } else if n.gt_i64(1) { + factors.push(n); + } + factors +} + +pub fn load(vm: &mut Vm) { + vm.set_global_name("isprime", isprime().into()); + vm.set_global_name("factors", factors().into()); + vm.set_global_name("totient", totient().into()); + + vm.set_global_name("powmod", powmod().into()); + vm.set_global_name("invmod", invmod().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("iroot", iroot().into()); + + vm.set_global_name("jacobi", jacobi().into()); +} + +#[native_func(1)] +pub fn isprime(_: &mut Vm, args: Vec) -> Result { + let [_, n] = unpack_args!(args); + let Value::Int(n) = n else { + throw!( + *SYM_TYPE_ERROR, + "isprime expected integer argument, got {n:#}" + ) + }; + Ok(Value::from(is_prime_inner(&n))) +} + +#[native_func(1)] +pub fn factors(_: &mut Vm, args: Vec) -> Result { + let [_, n] = unpack_args!(args); + let Value::Int(n) = n else { + throw!( + *SYM_TYPE_ERROR, + "factors expected integer argument, got {n:#}" + ) + }; + let factors = factorize(&n); + let factors: Vec = factors.into_iter().map(Value::from).collect(); + Ok(factors.into()) +} + +#[native_func(1)] +pub fn totient(_: &mut Vm, args: Vec) -> Result { + let [_, n] = unpack_args!(args); + let Value::Int(n) = n else { + throw!( + *SYM_TYPE_ERROR, + "totient expected integer argument, got {n:#}" + ) + }; + if n.is_zero() { + return Ok(Value::from(n)) + } + let mut res = Int::ONE; + let mut last_factor = Int::ONE; + for factor in factorize(&n) { + if last_factor != factor { + res *= &factor - 1; + last_factor = factor + } else { + res *= factor + } + } + Ok(res.into()) +} + +#[native_func(3)] +pub fn powmod(_: &mut Vm, args: Vec) -> Result { + let [_, n, e, m] = unpack_args!(args); + let (Value::Int(n), Value::Int(e), Value::Int(m)) = (n, e, m) else { + throw!(*SYM_TYPE_ERROR, "powmod expected 3 integer arguments") + }; + if m.is_zero() { + throw!(*SYM_VALUE_ERROR, "powmod: modulus was zero") + } + if e.is_negative() { + throw!(*SYM_VALUE_ERROR, "powmod: exponent was negative") + } + Ok(n.modpow(&e, &m).into()) +} + +#[native_func(2)] +pub fn invmod(_: &mut Vm, args: Vec) -> Result { + let [_, n, m] = unpack_args!(args); + let (Value::Int(n), Value::Int(m)) = (n, m) else { + throw!(*SYM_TYPE_ERROR, "invmod expected 2 integer arguments") + }; + if m.is_zero() { + throw!(*SYM_VALUE_ERROR, "invmod: modulus was zero") + } + match n.modinv(&m) { + Some(v) => Ok(v.into()), + None => Ok(Value::Int(0.into())), + } +} + +#[native_func(2)] +pub fn gcd(_: &mut Vm, args: Vec) -> Result { + 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 {y:#}") + }; + Ok(x.gcd(&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 = Int::ZERO; + + while let Some(a) = vmcalliter!(vm; args.clone())? { + let Value::Int(a) = a else { + throw!(*SYM_TYPE_ERROR, "gcdn: cannot take gcd with {a:#}") + }; + g = g.gcd(&a); + } + + Ok(g.into()) +} + +#[native_func(2)] +pub fn lcm(_: &mut Vm, args: Vec) -> Result { + 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 {y:#}") + }; + Ok(x.lcm(&y).into()) +} + +#[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 = Int::ONE; + + while let Some(a) = vmcalliter!(vm; args.clone())? { + let Value::Int(a) = a else { + throw!(*SYM_TYPE_ERROR, "lcmn: cannot take lcm with {a:#}") + }; + l = l.lcm(&a); + } + + Ok(Value::from(l)) +} + +#[native_func(1)] +pub fn isqrt(_: &mut Vm, args: Vec) -> Result { + let [_, x] = unpack_args!(args); + let Value::Int(x) = x else { + throw!( + *SYM_TYPE_ERROR, + "isqrt expected integer argument, got {x:#}" + ) + }; + if x.is_negative() { + throw!(*SYM_VALUE_ERROR, "isqrt: radicand must be non-negative") + } + Ok(Value::Int(x.sqrt())) +} + +#[native_func(2)] +pub fn iroot(_: &mut Vm, args: Vec) -> Result { + let [_, n, x] = unpack_args!(args); + let (Value::Int(n), Value::Int(x)) = (&n, &x) else { + throw!( + *SYM_TYPE_ERROR, + "iroot expected integer arguments, got {n:#} and {x:#}" + ) + }; + if x.is_negative() { + throw!( + *SYM_VALUE_ERROR, + "isqrt: radicand must be non-negative, got {x}" + ) + } + if !n.is_positive() { + throw!(*SYM_VALUE_ERROR, "isqrt: degree must be positive, got {n}") + } + let Some(n) = n.to_u32() else { + throw!(*SYM_VALUE_ERROR, "isqrt: degree {n} is too large") + }; + Ok(Value::Int(x.nth_root(n))) +} + +#[native_func(2)] +pub fn jacobi(_: &mut Vm, args: Vec) -> Result { + let [_, a, n] = unpack_args!(args); + let (mut a, mut n) = match (a, n) { + (Value::Int(a), Value::Int(n)) => { + (a.to_big().into_owned(), n.to_big().into_owned()) + } + (a, n) => throw!( + *SYM_TYPE_ERROR, + "jacobi expected integer arguments, got {a:#} and {n:#}", + ), + }; + + // (a|0) = 1 if |a| = 1, 0 otherwise + if n.is_zero() { + if a.to_i32().is_some_and(|v| v == -1 || v == 1) { + return Ok(Value::from(1)) + } else { + return Ok(Value::from(0)) + } + } + + // (k|l) = 0 if gcd(k,l) != 1 + if a.is_even() && n.is_even() { + return Ok(Value::from(0)) + } + + let mut res = 1i64; + // (a|2n) = (a|2)(a|n), + // (a|2) = 0 if a is even, 1 if a = 1,7 mod 8, -1 if a = 3,5 mod 8 + let am8 = ((&a % 8i32).to_i32().unwrap() + 8) % 8; + while n.is_even() { + n /= 2; + if am8 % 2 == 0 { + return Ok(Value::from(0)) + } + if am8 == 3 || am8 == 5 { + res *= -1; + } + } + + // (a|-1) = -1 if a < 0, 1 if a >= 0 + if n.is_negative() { + n *= -1; + if a.is_negative() { + res *= -1; + } + } + + a = ((a % &n) + &n) % &n; + while !a.is_zero() { + // (2a|n) = (2|n)(a|n) + // (2|n) = 1 if a = 1,7 mod 8, -1 if a = 3,5 mod 8 + while a.is_even() { + a /= 2; + let nm8 = ((&n % 8i32).to_i32().unwrap() + 8) % 8; + if nm8 == 3 || nm8 == 5 { + res *= -1; + } + } + // (m|n)(n|m) = (-1)^((m-1)/2 (n-1)/2) + std::mem::swap(&mut a, &mut n); + let am4 = ((&a % 4i32).to_i32().unwrap() + 4) % 4; + let nm4 = ((&n % 4i32).to_i32().unwrap() + 4) % 4; + if am4 == 3 && nm4 == 3 { + res *= -1; + } + // (a|n) = (a mod n|n) + a = ((a % &n) + &n) % &n; + } + if n.to_u32().is_some_and(|v| v == 1) { + return Ok(Value::from(res)) + } else { + return Ok(Value::from(0)) + } +} diff --git a/talc-std/src/io.rs b/talc-std/src/io.rs index f63e559..8fd7e2a 100644 --- a/talc-std/src/io.rs +++ b/talc-std/src/io.rs @@ -8,6 +8,7 @@ use std::{ use talc_lang::{ exception::{throw, Result}, lstring::LString, + prelude::*, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::Value, vm::Vm, @@ -189,7 +190,14 @@ pub fn arg(vm: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "arg expected integer, got {n:#}") }; let cmd_args = vm.args(); - if n < 0 || (n as usize) >= cmd_args.len() { + let Some(n) = n.to_usize() else { + throw!( + *SYM_VALUE_ERROR, + "arg number must be small positive integer, got {}", + n, + ) + }; + if n >= cmd_args.len() { throw!( *SYM_VALUE_ERROR, "arg number {} out of range for {} arguments", @@ -197,7 +205,7 @@ pub fn arg(vm: &mut Vm, args: Vec) -> Result { cmd_args.len() ) } - Ok(Value::from(cmd_args[n as usize].clone())) + Ok(Value::from(cmd_args[n].clone())) } #[native_func(0)] diff --git a/talc-std/src/iter.rs b/talc-std/src/iter.rs index 26e3adc..a38db6f 100644 --- a/talc-std/src/iter.rs +++ b/talc-std/src/iter.rs @@ -2,9 +2,10 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use talc_lang::{ exception::Result, + prelude::*, symbol::{symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, - value::{function::NativeFunc, ops::RatioExt, range::RangeType, HashValue, Value}, + value::{function::NativeFunc, HashValue, Value}, vm::Vm, vmcall, vmcalliter, }; @@ -186,7 +187,7 @@ pub fn take(_: &mut Vm, args: Vec) -> Result { let Value::Int(count) = count else { throw!(*SYM_TYPE_ERROR, "take expected integer, got {count:#}") }; - let Ok(count) = count.try_into() else { + let Some(count) = count.to_usize() else { throw!( *SYM_VALUE_ERROR, "take expected nonnegative integer, got {count:#}" @@ -215,10 +216,10 @@ pub fn skip(_: &mut Vm, args: Vec) -> Result { let Value::Int(count) = count else { throw!(*SYM_TYPE_ERROR, "count expected integer, got {count:#}") }; - let Ok(count) = count.try_into() else { + let Some(count) = count.to_usize() else { throw!( *SYM_VALUE_ERROR, - "count expected nonnegative integer, got {count:#}" + "count expected small nonnegative integer, got {count:#}" ) }; let iter = iter.to_iter_function()?; @@ -305,12 +306,12 @@ pub fn step(_: &mut Vm, args: Vec) -> Result { let Value::Int(by) = by else { throw!(*SYM_TYPE_ERROR, "step expected integer, got {by:#}") }; - if by <= 0 { + let Some(by) = by.to_usize() else { throw!( *SYM_VALUE_ERROR, - "step expected positive integer, got {by:#}" + "step expected small positive integer, got {by:#}" ) - } + }; let state = RefCell::new(Step::First); let f = move |vm: &mut Vm, _| match state.take() { @@ -322,7 +323,7 @@ pub fn step(_: &mut Vm, args: Vec) -> Result { Ok(Value::iter_pack(res)) } Step::Going => { - for _ in 0..(by - 1) { + for _ in 1..by { if vmcall!(vm; iter.clone())? == Value::Nil { return Ok(Value::iter_pack(None)) } @@ -648,8 +649,10 @@ pub fn len(vm: &mut Vm, args: Vec) -> Result { Value::String(s) => return Ok((s.chars().count() as i64).into()), Value::List(l) => return Ok((l.borrow().len() as i64).into()), Value::Table(t) => return Ok((t.borrow().len() as i64).into()), - Value::Range(r) if r.ty != RangeType::Endless => { - return Ok((r.len().unwrap() as i64).into()) + Value::Range(ref r) => { + if let Some(len) = r.len() { + return Ok(len.into()) + } } _ => (), } @@ -692,7 +695,7 @@ pub fn sum(vm: &mut Vm, args: Vec) -> Result { let [_, iter] = unpack_args!(args); let iter = iter.to_iter_function()?; - let mut result = Value::Int(0); + let mut result = Value::from(0); while let Some(value) = vmcalliter!(vm; iter.clone())? { result = (result + value)?; } @@ -704,7 +707,7 @@ pub fn prod(vm: &mut Vm, args: Vec) -> Result { let [_, iter] = unpack_args!(args); let iter = iter.to_iter_function()?; - let mut result = Value::Int(1); + let mut result = Value::from(1); while let Some(value) = vmcalliter!(vm; iter.clone())? { result = (result * value)?; } @@ -757,6 +760,10 @@ pub fn nth(vm: &mut Vm, args: Vec) -> Result { throw!(*SYM_TYPE_ERROR, "nth expected integer") }; + let Some(n) = n.to_usize() else { + throw!(*SYM_VALUE_ERROR, "nth expected small positive integer") + }; + for _ in 0..n { if vmcalliter!(vm; iter.clone())?.is_none() { return Ok(Value::Nil) @@ -849,8 +856,8 @@ pub fn count(vm: &mut Vm, args: Vec) -> Result { while let Some(v) = vmcalliter!(vm; iter.clone())? { let hv = v.try_into()?; map.entry(hv) - .and_modify(|v: &mut Value| *v = (v.clone() + Value::Int(1)).unwrap()) - .or_insert(Value::Int(1)); + .and_modify(|v: &mut Value| *v = (v.clone() + Value::from(1)).unwrap()) + .or_insert(Value::from(1)); } Ok(map.into()) @@ -862,7 +869,7 @@ pub fn mean(vm: &mut Vm, args: Vec) -> Result { let iter = iter.to_iter_function()?; let mut sum = Value::Float(0.0); - let mut count = Value::Int(0); + let mut count = Value::from(0); while let Some(value) = vmcalliter!(vm; iter.clone())? { sum = (sum + value)?; count = (count + Value::from(1))?; @@ -876,11 +883,11 @@ fn variance_inner(vm: &mut Vm, iter: Value, pop: bool) -> Result { 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))?)?; + m = (m.clone() + ((value.clone() - m.clone())? / Value::from(k))?)?; s = (s + ((value.clone() - m.clone())? * (value - old_m)?)?)?; k += 1; } - s / Value::Int(k - if pop { 1 } else { 2 }) + s / Value::from(k - if pop { 1 } else { 2 }) } #[native_func(1)] @@ -898,9 +905,9 @@ pub fn stdev(vm: &mut Vm, args: Vec) -> Result { let v = variance_inner(vm, iter, false)?; Ok(match v { - Value::Int(n) => Value::Float((n as f64).sqrt()), + Value::Int(n) => Value::Float(n.to_f64_uc().sqrt()), Value::Float(f) => Value::Float(f.sqrt()), - Value::Ratio(r) => Value::Float(r.to_f64().sqrt()), + Value::Ratio(r) => Value::Float(r.to_f64_uc().sqrt()), Value::Complex(c) => Value::Complex(c.sqrt()), v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}"), }) @@ -921,9 +928,9 @@ pub fn pstdev(vm: &mut Vm, args: Vec) -> Result { let v = variance_inner(vm, iter, true)?; Ok(match v { - Value::Int(n) => Value::Float((n as f64).sqrt()), + Value::Int(n) => Value::Float(n.to_f64_uc().sqrt()), Value::Float(f) => Value::Float(f.sqrt()), - Value::Ratio(r) => Value::Float(r.to_f64().sqrt()), + Value::Ratio(r) => Value::Float(r.to_f64_uc().sqrt()), Value::Complex(c) => Value::Complex(c.sqrt()), v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}"), }) @@ -957,10 +964,10 @@ pub fn median(vm: &mut Vm, args: Vec) -> Result { 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) + (m1.0 + m2.0)? / Value::from(2) } else { let (_, _, _) = vals.select_nth_unstable(count / 2); let m = vals.swap_remove(count / 2); - m.0 / Value::Int(1) + m.0 / Value::from(1) } } diff --git a/talc-std/src/lib.rs b/talc-std/src/lib.rs index 7195381..8b0c876 100644 --- a/talc-std/src/lib.rs +++ b/talc-std/src/lib.rs @@ -9,9 +9,10 @@ pub mod collection; pub mod exception; pub mod file; pub mod format; +pub mod ints; pub mod io; pub mod iter; -pub mod num; +pub mod math; pub mod string; pub mod value; @@ -27,7 +28,8 @@ pub fn load_all(vm: &mut Vm) { format::load(vm); io::load(vm); iter::load(vm); - num::load(vm); + math::load(vm); + ints::load(vm); string::load(vm); value::load(vm); diff --git a/talc-std/src/num.rs b/talc-std/src/math.rs similarity index 64% rename from talc-std/src/num.rs rename to talc-std/src/math.rs index f33155e..ec7039b 100644 --- a/talc-std/src/num.rs +++ b/talc-std/src/math.rs @@ -1,14 +1,12 @@ -use std::cmp::Ordering; - use lazy_static::lazy_static; use talc_lang::{ exception::Result, - parser::{parse_int, to_lstring_radix}, + number::{Complex, Int, Ratio}, + prelude::*, symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, - value::{ops::RatioExt, Complex64, Value}, + value::Value, vm::Vm, - vmcalliter, }; use talc_macros::native_func; @@ -25,8 +23,8 @@ lazy_static! { #[inline] fn to_floaty(v: Value) -> Value { match v { - Value::Int(v) => Value::Float(v as f64), - Value::Ratio(v) => Value::Float(v.to_f64()), + Value::Int(v) => Value::Float(v.to_f64_uc()), + Value::Ratio(v) => Value::Float(v.to_f64_uc()), Value::Float(v) => Value::Float(v), Value::Complex(v) => Value::Complex(v), v => v, @@ -36,8 +34,8 @@ fn to_floaty(v: Value) -> Value { #[inline] fn to_complex(v: Value) -> Value { match v { - Value::Int(v) => Value::Complex((v as f64).into()), - Value::Ratio(v) => Value::Complex(v.to_f64().into()), + Value::Int(v) => Value::Complex(v.to_f64_uc().into()), + Value::Ratio(v) => Value::Complex(v.to_f64_uc().into()), Value::Float(v) => Value::Complex(v.into()), Value::Complex(v) => Value::Complex(v), v => v, @@ -57,8 +55,8 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("inf", (f64::INFINITY).into()); vm.set_global_name("NaN", (f64::NAN).into()); - vm.set_global_name("infi", Complex64::new(0.0, f64::INFINITY).into()); - vm.set_global_name("NaNi", Complex64::new(0.0, f64::NAN).into()); + vm.set_global_name("infi", Complex::new(0.0, f64::INFINITY).into()); + vm.set_global_name("NaNi", Complex::new(0.0, f64::NAN).into()); vm.set_global_name("bin", bin().into()); vm.set_global_name("sex", sex().into()); @@ -68,15 +66,6 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("to_radix_upper", to_radix_upper().into()); 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()); - vm.set_global_name("totient", totient().into()); - vm.set_global_name("min", min().into()); vm.set_global_name("max", max().into()); vm.set_global_name("floor", floor().into()); @@ -137,7 +126,7 @@ pub fn bin(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "bin expected integer argument, got {x:#}") }; - Ok(to_lstring_radix(x, 2, false).into()) + Ok(Value::from(x.to_str_radix_case(2, false))) } #[native_func(1)] @@ -146,7 +135,7 @@ pub fn sex(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}") }; - Ok(to_lstring_radix(x, 6, false).into()) + Ok(Value::from(x.to_str_radix_case(6, false))) } #[native_func(1)] @@ -155,7 +144,7 @@ pub fn oct(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}") }; - Ok(to_lstring_radix(x, 8, false).into()) + Ok(Value::from(x.to_str_radix_case(8, false))) } #[native_func(1)] @@ -164,7 +153,7 @@ pub fn hex(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}") }; - Ok(to_lstring_radix(x, 16, false).into()) + Ok(Value::from(x.to_str_radix_case(16, false))) } #[native_func(2)] @@ -176,10 +165,13 @@ pub fn to_radix(_: &mut Vm, args: Vec) -> Result { "to_radix expected integer arguments, got {x:#} and {radix:#}" ) }; - if *radix < 2 || *radix > 36 { - throw!(*SYM_VALUE_ERROR, "to_radix expected radix in range 0..=36") + let Some(radix) = radix.to_u32() else { + throw!(*SYM_VALUE_ERROR, "to_radix expected radix in range 2..=36") + }; + if !(2..=36).contains(&radix) { + throw!(*SYM_VALUE_ERROR, "to_radix expected radix in range 2..=36") } - Ok(to_lstring_radix(*x, *radix as u32, false).into()) + Ok(Value::from(x.to_str_radix_case(radix, false))) } #[native_func(2)] @@ -191,13 +183,19 @@ pub fn to_radix_upper(_: &mut Vm, args: Vec) -> Result { "to_radix_upper expected integer arguments, got {x:#} and {radix:#}" ) }; - if *radix < 2 || *radix > 36 { + let Some(radix) = radix.to_u32() else { throw!( *SYM_VALUE_ERROR, - "to_radix_upper expected radix in range 0..=36" + "to_radix_upper expected radix in range 2..=36" + ) + }; + if !(2..=36).contains(&radix) { + throw!( + *SYM_VALUE_ERROR, + "to_radix_upper expected radix in range 2..=36" ) } - Ok(to_lstring_radix(*x, *radix as u32, true).into()) + Ok(Value::from(x.to_str_radix_case(radix, true))) } #[native_func(2)] @@ -209,285 +207,32 @@ pub fn from_radix(_: &mut Vm, args: Vec) -> Result { "from_radix expected string and integer arguments, got {s:#} and {radix:#}" ) }; - if *radix < 2 || *radix > 36 { + let Some(radix) = radix.to_u32() else { throw!( *SYM_VALUE_ERROR, - "from_radix expected radix in range 0..=36" + "from_radix expected radix in range 2..=36" + ) + }; + if !(2..=36).contains(&radix) { + throw!( + *SYM_VALUE_ERROR, + "from_radix expected radix in range 2..=36" ) } - match parse_int(s.as_ref(), *radix as u32) { + let Ok(s) = s.to_str() else { + throw!(*SYM_VALUE_ERROR, "from_radix: string has invalid UTF-8",) + }; + match Int::from_str_radix(s, radix) { Ok(v) => Ok(v.into()), Err(_) => throw!( *SYM_VALUE_ERROR, - "string was not a valid integer in given radix" + "from_radix: string {:#} is not a valid integer in radix {}", + s, + radix ), } } -// -// integer operations -// - -fn isqrt_inner(mut n: i64) -> i64 { - assert!(n >= 0, "isqrt input should be nonnegative"); - if n < 2 { - return n - } - - let mut c = 0; - let mut d = 1 << 62; - - while d > n { - d >>= 2; - } - - while d != 0 { - if n >= c + d { - n -= c + d; - c = (c >> 1) + d; - } else { - c >>= 1; - } - d >>= 2; - } - c -} - -pub fn gcd_inner(a: i64, b: i64) -> i64 { - let (mut a, mut b) = (a.abs(), b.abs()); - if a == 0 { - return b - } - if b == 0 { - return a - } - - let az = a.trailing_zeros(); - a >>= az; - let bz = b.trailing_zeros(); - b >>= bz; - let z = az.min(bz); - - loop { - if a > b { - std::mem::swap(&mut a, &mut b); - } - b -= a; - if b == 0 { - return a << z - } - b >>= b.trailing_zeros(); - } -} - -#[native_func(1)] -pub fn isqrt(_: &mut Vm, args: Vec) -> Result { - let [_, x] = unpack_args!(args); - let Value::Int(x) = x else { - throw!( - *SYM_TYPE_ERROR, - "isqrt expected integer argument, got {x:#}" - ) - }; - if x < 0 { - throw!(*SYM_VALUE_ERROR, "isqrt: argument must be positive") - } - Ok(isqrt_inner(x).into()) -} - -#[native_func(1)] -pub fn isprime(_: &mut Vm, args: Vec) -> Result { - let [_, x] = unpack_args!(args); - let Value::Int(x) = x else { - throw!( - *SYM_TYPE_ERROR, - "isprime expected integer argument, got {x:#}" - ) - }; - if x < 2 { - return Ok(false.into()) - } - for p in [2, 3, 5, 7] { - if x % p == 0 { - return Ok((x == p).into()) - } - } - if x < 11 { - return Ok(false.into()) - } - let lim = isqrt_inner(x); - let mut i = 12; - while i <= lim + 1 { - if x % (i - 1) == 0 { - return Ok(false.into()) - } - if x % (i + 1) == 0 { - return Ok(false.into()) - } - i += 6; - } - Ok(true.into()) -} - -#[native_func(2)] -pub fn gcd(_: &mut Vm, args: Vec) -> Result { - 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 {y:#}") - }; - 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, "gcdn: cannot take gcd with {a:#}") - }; - g = gcd_inner(g, a); - } - - Ok(g.into()) -} - -#[native_func(2)] -pub fn lcm(_: &mut Vm, args: Vec) -> Result { - 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 {y:#}") - }; - let g = gcd_inner(x, y); - if g == 0 { - Ok(Value::from(0)) - } else { - (Value::from(x) / Value::from(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, "lcmn: cannot take lcm with {a:#}") - }; - let g = gcd_inner(l, a); - if g == 0 { - return Ok(Value::from(0)) - }; - let new_l = (Value::from(l).int_div(Value::from(g))? * Value::from(a))?; - let Value::Int(new_l) = new_l else { - unreachable!("int//int * int != int") - }; - l = new_l; - } - - Ok(Value::from(l)) -} - -#[native_func(1)] -pub fn factors(_: &mut Vm, args: Vec) -> Result { - let [_, x] = unpack_args!(args); - let Value::Int(mut x) = x else { - throw!( - *SYM_TYPE_ERROR, - "factors expected integer argument, got {x:#}" - ) - }; - let mut factors = Vec::new(); - if x <= 1 { - return Ok(factors.into()) - } - while x & 1 == 0 { - x >>= 1; - factors.push(Value::Int(2)); - } - while x % 3 == 0 { - x /= 3; - factors.push(Value::Int(3)); - } - let mut i = 5; - while x >= i * i { - while x % i == 0 { - x /= i; - factors.push(Value::Int(i)); - } - i += 2; - while x % i == 0 { - x /= i; - factors.push(Value::Int(i)); - } - i += 4; - } - if x > 1 { - factors.push(Value::Int(x)); - } - Ok(factors.into()) -} - -fn totient_prime(n: &mut u64, p: u64) -> u64 { - if *n % p != 0 { - return 1 - } - *n /= p; - let mut v = p - 1; - while *n % p == 0 { - *n /= p; - v *= p; - } - v -} - -#[native_func(1)] -pub fn totient(_: &mut Vm, args: Vec) -> Result { - let [_, x] = unpack_args!(args); - let Value::Int(x) = x else { - throw!( - *SYM_TYPE_ERROR, - "totient expected integer argument, got {x:#}" - ) - }; - if x <= 1 { - return Ok(1.into()) - } - let mut x = x as u64; - let mut totient = 1; - if x & 1 == 0 { - x >>= 1; - } - while x & 1 == 0 { - x >>= 1; - totient <<= 1; - } - totient *= totient_prime(&mut x, 3); - let mut i = 5; - while x >= i * i { - totient *= totient_prime(&mut x, i); - i += 2; - totient *= totient_prime(&mut x, i); - i += 4; - } - if x > 1 { - totient *= x - 1; - } - Ok((totient as i64).into()) -} - // // numeric operations // @@ -518,7 +263,7 @@ pub fn floor(_: &mut Vm, args: Vec) -> Result { match x { Value::Int(x) => Ok(Value::Int(x)), Value::Float(x) => Ok(Value::Float(x.floor())), - Value::Ratio(x) => Ok(Value::Ratio(x.floor())), + Value::Ratio(x) => Ok(Value::Ratio(x.floor().into())), x => throw!(*SYM_TYPE_ERROR, "floor expected real argument, got {x:#}"), } } @@ -529,7 +274,7 @@ pub fn ceil(_: &mut Vm, args: Vec) -> Result { match x { Value::Int(x) => Ok(Value::Int(x)), Value::Float(x) => Ok(Value::Float(x.ceil())), - Value::Ratio(x) => Ok(Value::Ratio(x.ceil())), + Value::Ratio(x) => Ok(Value::Ratio(x.ceil().into())), x => throw!(*SYM_TYPE_ERROR, "ceil expected real argument, got {x:#}"), } } @@ -540,7 +285,7 @@ pub fn round(_: &mut Vm, args: Vec) -> Result { match x { Value::Int(x) => Ok(Value::Int(x)), Value::Float(x) => Ok(Value::Float(x.round())), - Value::Ratio(x) => Ok(Value::Ratio(x.round())), + Value::Ratio(x) => Ok(Value::Ratio(x.round().into())), x => throw!(*SYM_TYPE_ERROR, "round expected real argument, got {x:#}"), } } @@ -551,7 +296,7 @@ pub fn trunc(_: &mut Vm, args: Vec) -> Result { match x { Value::Int(x) => Ok(Value::Int(x)), Value::Float(x) => Ok(Value::Float(x.trunc())), - Value::Ratio(x) => Ok(Value::Ratio(x.trunc())), + Value::Ratio(x) => Ok(Value::Ratio(x.trunc().into())), x => throw!(*SYM_TYPE_ERROR, "trunc expected real argument, got {x:#}"), } } @@ -560,9 +305,9 @@ pub fn trunc(_: &mut Vm, args: Vec) -> Result { pub fn fract(_: &mut Vm, args: Vec) -> Result { let [_, x] = unpack_args!(args); match x { - Value::Int(_) => Ok(Value::Int(0)), + Value::Int(_) => Ok(Value::from(0)), Value::Float(x) => Ok(Value::Float(x.fract())), - Value::Ratio(x) => Ok(Value::Ratio(x.fract())), + Value::Ratio(x) => Ok(Value::Ratio(x.fract().into())), x => throw!(*SYM_TYPE_ERROR, "fract expected real argument, got {x:#}"), } } @@ -572,12 +317,8 @@ pub fn sign(_: &mut Vm, args: Vec) -> Result { let [_, x] = unpack_args!(args); match x { Value::Int(x) => Ok(Value::Int(x.signum())), - Value::Float(x) => Ok(Value::Float(if x == 0.0 { 0.0 } else { x.signum() })), - Value::Ratio(x) => match x.cmp(&0.into()) { - Ordering::Greater => Ok(Value::Ratio(1.into())), - Ordering::Less => Ok(Value::Ratio((-1).into())), - Ordering::Equal => Ok(Value::Ratio(0.into())), - }, + Value::Float(x) => Ok(Value::Float(if x == 0.0 { x } else { x.signum() })), + Value::Ratio(x) => Ok(Value::Ratio(x.signum().into())), x => throw!(*SYM_TYPE_ERROR, "sign expected real argument, got {x:#}"), } } @@ -666,7 +407,7 @@ pub fn float_to_bits(_: &mut Vm, args: Vec) -> Result { "float_to_bits expected float argument, got {val:#}" ) }; - Ok(Value::Int(f.to_bits() as i64)) + Ok(Value::Int(Int::from_u64(f.to_bits()).unwrap())) } #[native_func(1)] @@ -678,7 +419,13 @@ pub fn float_of_bits(_: &mut Vm, args: Vec) -> Result { "float_of_bits expected integer argument, got {val:#}" ) }; - Ok(Value::Float(f64::from_bits(i as u64))) + let Some(v) = i.to_u64() else { + throw!( + *SYM_TYPE_ERROR, + "float_of_bits expected integer in 0..2^64, got {i}" + ) + }; + Ok(Value::Float(f64::from_bits(v))) } // @@ -690,7 +437,7 @@ pub fn numer(_: &mut Vm, args: Vec) -> Result { let [_, v] = unpack_args!(args); match v { Value::Int(x) => Ok(Value::Int(x)), - Value::Ratio(x) => Ok(Value::Int(*x.numer())), + Value::Ratio(x) => Ok(Value::from(Int::from(x.numer()))), v => throw!( *SYM_TYPE_ERROR, "numer expected rational argument, got {v:#}" @@ -702,8 +449,8 @@ pub fn numer(_: &mut Vm, args: Vec) -> Result { pub fn denom(_: &mut Vm, args: Vec) -> Result { let [_, v] = unpack_args!(args); match v { - Value::Int(_) => Ok(Value::Int(1)), - Value::Ratio(x) => Ok(Value::Int(*x.denom())), + Value::Int(_) => Ok(Value::from(1)), + Value::Ratio(x) => Ok(Value::from(Int::from(x.denom()))), v => throw!( *SYM_TYPE_ERROR, "denom expected rational argument, got {v:#}" @@ -731,8 +478,8 @@ pub fn re(_: &mut Vm, args: Vec) -> Result { pub fn im(_: &mut Vm, args: Vec) -> Result { let [_, v] = unpack_args!(args); match to_complex(v) { - Value::Int(_) => Ok(Value::Int(0)), - Value::Ratio(_) => Ok(Value::Ratio(0.into())), + Value::Int(_) => Ok(Value::from(0)), + Value::Ratio(_) => Ok(Value::Ratio(Ratio::zero().into())), Value::Float(_) => Ok(Value::Float(0.0)), Value::Complex(z) => Ok(Value::Float(z.im)), v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"), diff --git a/talc-std/src/random.rs b/talc-std/src/random.rs index 6ead898..2afc04a 100644 --- a/talc-std/src/random.rs +++ b/talc-std/src/random.rs @@ -1,9 +1,11 @@ +use num_bigint::RandBigInt; use rand::{seq::SliceRandom, Rng}; use talc_lang::{ exception::Result, + number::Int, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, - value::{range::RangeType, Value}, + value::Value, vm::Vm, vmcalliter, }; @@ -46,14 +48,13 @@ pub fn rand_in(vm: &mut Vm, args: Vec) -> Result { if r.is_empty() { 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_VALUE_ERROR, "rand_in: endless range"), + let (Some(s), Some(e)) = (&r.start, &r.end) else { + throw!(*SYM_VALUE_ERROR, "rand_in: infinite range") + }; + if r.inclusive { + Ok(Int::from(rand::thread_rng().gen_bigint_range(s, &(e + 1))).into()) + } else { + Ok(Int::from(rand::thread_rng().gen_bigint_range(s, e)).into()) } } col => { diff --git a/talc-std/src/string.rs b/talc-std/src/string.rs index 6efa92c..1cada6b 100644 --- a/talc-std/src/string.rs +++ b/talc-std/src/string.rs @@ -1,6 +1,7 @@ use talc_lang::{ exception::Result, lstring::LString, + prelude::*, symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, value::Value, @@ -40,7 +41,7 @@ pub fn ord(_: &mut Vm, args: Vec) -> Result { if chars.next().is_some() { throw!(*SYM_VALUE_ERROR, "argument to ord must have length 1") }; - Ok(Value::Int(c as u32 as i64)) + Ok(Value::from(c as u32 as i64)) } #[native_func(1)] @@ -49,7 +50,7 @@ pub fn chr(_: &mut Vm, args: Vec) -> Result { let Value::Int(i) = i else { throw!(*SYM_TYPE_ERROR, "chr expected integer argument, got {i:#}") }; - let Ok(i) = u32::try_from(i) else { + let Some(i) = i.to_u32() else { throw!(*SYM_VALUE_ERROR, "argument to chr is not a valid codepoint") }; let Some(c) = char::from_u32(i) else { @@ -67,7 +68,7 @@ pub fn len_bytes(_: &mut Vm, args: Vec) -> Result { "len_bytes expected string argument, got {s:#}" ) }; - Ok(Value::Int(s.len() as i64)) + Ok(Value::from(s.len() as i64)) } #[native_func(1)] @@ -205,12 +206,20 @@ 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_VALUE_ERROR, - "str_of_bytes expected list of integers in 0..=255" - ), + .map(|v| { + let Value::Int(i) = v else { + throw!( + *SYM_TYPE_ERROR, + "str_of_bytes expected list of integers in 0..=255" + ) + }; + let Some(n) = i.to_u8() else { + throw!( + *SYM_VALUE_ERROR, + "str_of_bytes expected list of integers in 0..=255" + ) + }; + Ok(n) }) .collect::>>()?; Ok(LString::from(bytes).into()) diff --git a/talc-std/src/value.rs b/talc-std/src/value.rs index a6c7f32..91e0b25 100644 --- a/talc-std/src/value.rs +++ b/talc-std/src/value.rs @@ -3,10 +3,12 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use talc_lang::{ exception::{exception, Result}, lformat, + number::{Int, Ratio}, parser::{parse_float, parse_int}, + prelude::*, symbol::{symbol, Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, throw, - value::{ops::RatioExt, HashValue, Rational64, Value}, + value::{HashValue, Value}, vm::Vm, }; use talc_macros::native_func; @@ -71,24 +73,22 @@ pub fn as_(_: &mut Vm, args: Vec) -> Result { (v, b"string") => Ok(Value::String(lformat!("{v}").into())), (v, b"bool") => Ok(Value::Bool(v.truthy())), - (Value::Symbol(s), b"int") => Ok(Value::Int(s.id() as i64)), + (Value::Symbol(s), b"int") => Ok(Value::from(s.id() as i64)), - (Value::Int(x), b"ratio") => Ok(Value::Ratio(x.into())), - (Value::Int(x), b"float") => Ok(Value::Float(x as f64)), - (Value::Int(x), b"complex") => Ok(Value::Complex((x as f64).into())), - (Value::Ratio(x), b"int") => Ok(Value::Int(x.to_integer())), - (Value::Ratio(x), b"float") => Ok(Value::Float(x.to_f64())), - (Value::Ratio(x), b"complex") => Ok(Value::Complex(x.to_f64().into())), - (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_VALUE_ERROR, - "float {x:?} could not be converted to ratio" - ) - })?; - Ok(Value::Ratio(r)) - } + (Value::Int(x), b"ratio") => Ok(Value::from(Ratio::from_integer(x.into()))), + (Value::Int(x), b"float") => Ok(Value::Float(x.to_f64_uc())), + (Value::Int(x), b"complex") => Ok(Value::Complex(x.to_f64_uc().into())), + (Value::Ratio(x), b"int") => Ok(Value::from(x.to_integer())), + (Value::Ratio(x), b"float") => Ok(Value::Float(x.to_f64_uc())), + (Value::Ratio(x), b"complex") => Ok(Value::Complex(x.to_f64_uc().into())), + (Value::Float(x), b"int") => Ok(Value::from(Int::from_f64(x).unwrap_or_default())), + (Value::Float(x), b"ratio") => match Ratio::approximate_float(x) { + Some(v) => Ok(Value::from(v)), + None => throw!( + *SYM_VALUE_ERROR, + "float {x} could not be converted to ratio" + ), + }, (Value::Float(x), b"complex") => Ok(Value::Complex(x.into())), (Value::String(s), b"int") => parse_int(s.as_ref(), 10)