diff --git a/talc-bin/src/helper.rs b/talc-bin/src/helper.rs index e73ac94..90a821d 100644 --- a/talc-bin/src/helper.rs +++ b/talc-bin/src/helper.rs @@ -63,7 +63,8 @@ impl Highlighter for TalcHelper { | TokenKind::True | TokenKind::False | TokenKind::Integer - | TokenKind::Float => "\x1b[93m", + | TokenKind::Float + | TokenKind::Imaginary => "\x1b[93m", TokenKind::String => "\x1b[92m", TokenKind::Symbol => "\x1b[96m", _ => "", diff --git a/talc-lang/src/exception.rs b/talc-lang/src/exception.rs index 79a5adf..4c8176f 100644 --- a/talc-lang/src/exception.rs +++ b/talc-lang/src/exception.rs @@ -76,11 +76,11 @@ impl Display for Exception { #[macro_export] macro_rules! exception { - ($exc_ty:expr, $fstr:literal, $($arg:expr),*) => { + ($exc_ty:expr, $($t:tt)*) => { $crate::exception::Exception::new_with_msg( $exc_ty, $crate::lstring::LString::from( - format!($fstr, $($arg),*) + format!($($t)*) ).into() ) }; diff --git a/talc-lang/src/parser/lexer.rs b/talc-lang/src/parser/lexer.rs index f1a38f3..d4596ae 100644 --- a/talc-lang/src/parser/lexer.rs +++ b/talc-lang/src/parser/lexer.rs @@ -67,6 +67,7 @@ pub enum TokenKind { Identifier, Integer, Float, + Imaginary, String, Symbol, And, @@ -154,6 +155,7 @@ impl TokenKind { K::Identifier => "identifier", K::Integer => "integer", K::Float => "float", + K::Imaginary => "imaginary", K::String => "string", K::Symbol => "symbol", K::And => "'and'", @@ -324,6 +326,15 @@ impl<'s> Lexer<'s> { } } + fn next_imag(&mut self) -> Result> { + self.next()?; + if is_xid_start(self.peek()?) { + self.unexpected() + } else { + self.emit(K::Imaginary) + } + } + fn next_float(&mut self, mut has_e: bool) -> Result> { while !has_e { while matches!(self.peek()?, '_' | '0'..='9') { @@ -331,6 +342,7 @@ impl<'s> Lexer<'s> { } match self.peek()? { 'e' => { self.next()?; has_e = true } + 'i' => return self.next_imag(), c if is_xid_start(c) => return self.unexpected(), _ => return self.emit(K::Float) } @@ -341,10 +353,10 @@ impl<'s> Lexer<'s> { while matches!(self.peek()?, '_' | '0'..='9') { self.next()?; } - if is_xid_start(self.peek()?) { - self.unexpected() - } else { - self.emit(K::Float) + match self.peek()? { + 'i' => self.next_imag(), + c if is_xid_start(c) => self.unexpected(), + _ => self.emit(K::Float) } } @@ -356,8 +368,8 @@ impl<'s> Lexer<'s> { 'o' => { self.next()?; return self.next_int_base(8) }, 's' => { self.next()?; return self.next_int_base(6) }, 'b' => { self.next()?; return self.next_int_base(2) }, + '0'..='9' | '.' | 'e' | 'i' => (), c if is_xid_start(c) => return self.unexpected(), - '0'..='9' => (), _ => return self.emit(K::Integer) } } @@ -367,6 +379,7 @@ impl<'s> Lexer<'s> { match self.peek()? { 'r' => todo!("arbitrary radix integer literals"), 'e' => { self.next()?; self.next_float(true) }, + 'i' => self.next_imag(), '.' => { if self.peek_n(1) == Some('.') { self.emit(K::Integer) diff --git a/talc-lang/src/parser/parser.rs b/talc-lang/src/parser/parser.rs index fa19f5f..c270e68 100644 --- a/talc-lang/src/parser/parser.rs +++ b/talc-lang/src/parser/parser.rs @@ -4,6 +4,7 @@ use std::iter::Peekable; use crate::{lstr, lstring::LStr, symbol::Symbol, value::Value}; use super::{ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, UnaryOp}, parse_float, parse_int_literal, parse_str_escapes, Lexer, ParserError, Span, SpanParserError, Token, TokenKind}; +use num_complex::Complex64; use TokenKind as T; use ExprKind as E; @@ -179,6 +180,7 @@ impl TokenKind { | T::Try | T::Integer | T::Float + | T::Imaginary | T::String | T::Symbol | T::True @@ -371,6 +373,11 @@ impl<'s> Parser<'s> { .span_err(tok.span)?; Ok(E::Literal(x.into()).span(tok.span)) }, + T::Imaginary => { + let x = parse_float(&tok.content[..tok.content.len()-1]) + .span_err(tok.span)?; + Ok(E::Literal(Complex64::new(0.0, x).into()).span(tok.span)) + }, T::String => { let inner = &tok.content[1..tok.content.len()-1]; let s = if &tok.content[..1] == "\"" { @@ -554,7 +561,6 @@ impl<'s> Parser<'s> { fn parse_assign(&mut self) -> Result> { if let Some(tok) = try_next!(self, T::Global | T::Var) { - self.next()?; let name = expect!(self, T::Identifier); expect!(self, T::Equal); let val = self.parse_or()?; diff --git a/talc-lang/src/value/ops.rs b/talc-lang/src/value/ops.rs index 3c20c76..11679cb 100644 --- a/talc-lang/src/value/ops.rs +++ b/talc-lang/src/value/ops.rs @@ -5,7 +5,7 @@ use num_complex::{Complex64, ComplexFloat}; use num_rational::Rational64; use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Signed, Zero}; -use crate::{exception::{exception, throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::range::RangeType, Vm}; +use crate::{exception::{throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR, SYM_VALUE_ERROR}, value::range::RangeType, Vm}; use super::{function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value}; @@ -40,7 +40,6 @@ impl Value { } } -#[expect(clippy::cast_precision_loss)] pub fn promote(a: Value, b: Value) -> (Value, Value) { use Value as V; match (&a, &b) { @@ -61,6 +60,11 @@ pub fn promote(a: Value, b: Value) -> (Value, Value) { } +//////////////////////// +// unary arithmetic // +//////////////////////// + + impl Neg for Value { type Output = Result; fn neg(self) -> Self::Output { @@ -83,74 +87,65 @@ impl Neg for Value { } } -impl Add for Value { - type Output = Result; - fn add(self, rhs: Value) -> Self::Output { +impl Value { + pub fn abs(self) -> Result { 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_add(y) { - Ok(V::Int(v)) + match self { + V::Int(x) => if let Some(x) = x.checked_abs() { + Ok(V::Int(x)) } else { - throw!(*SYM_VALUE_ERROR, "overflow when adding {a} and {b}") + throw!(*SYM_VALUE_ERROR, "overflow when finding absolute value of {self}") }, - (V::Ratio(x), V::Ratio(y)) => if let Some(v) = x.checked_add(y) { - Ok(V::Ratio(v)) + V::Ratio(x) => if let Some((x, _)) = ratio_checked_absign(&x) { + Ok(V::Ratio(x)) } else { - throw!(*SYM_VALUE_ERROR, "overflow when adding {a} and {b}") - }, - (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 add {l:#} and {r:#}") + throw!(*SYM_VALUE_ERROR, "overflow when finding absolute value of {self}") + } + V::Float(x) => Ok(V::Float(x.abs())), + V::Complex(x) => Ok(V::Float(x.norm())), + a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}") } } } -impl Sub for Value { - type Output = Result; - fn sub(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_sub(y) { - Ok(V::Int(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when subtracting {a} and {b}") - }, - (V::Ratio(x), V::Ratio(y)) => if let Some(v) = x.checked_sub(y) { - Ok(V::Ratio(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when subtracting {a} and {b}") - }, - (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 subtract {l:#} and {r:#}") - } - } + +///////////////////////// +// binary arithmetic // +///////////////////////// + + +macro_rules! impl_value_arith { + ($trait:ident, $name:ident, $checked: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 Mul for Value { - type Output = Result; - fn mul(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_mul(y) { - Ok(V::Int(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when multiplying {a} and {b}") - }, - (V::Ratio(x), V::Ratio(y)) => if let Some(v) = x.checked_mul(y) { - Ok(V::Ratio(v)) - } else { - throw!(*SYM_VALUE_ERROR, "overflow when multiplying {a} and {b}") - }, - (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 multiply {l:#} and {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 Div for Value { type Output = Result; @@ -174,42 +169,35 @@ impl Div for Value { } } +/////////////////////////////////// +// modulo and integer division // +/////////////////////////////////// + + #[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), - } +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())) } -#[expect(clippy::cast_sign_loss)] #[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 ratio_checked_rem_euclid(r1: &Rational64, r2: &Rational64) -> Option { - todo!() -} - fn ratio_checked_div_euclid(r1: &Rational64, r2: &Rational64) -> Option { - todo!() + 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; @@ -267,7 +255,42 @@ impl Value { (l, r) => throw!(*SYM_TYPE_ERROR, "cannot integer divide {l:#} and {r:#}") } } +} + +////////////////////// +// exponentiation // +////////////////////// + + +#[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)?)), + } +} + +impl Value { pub fn pow(self, rhs: Value) -> Result { use Value as V; if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) { @@ -304,6 +327,12 @@ impl Value { } } + +////////////////////////// +// Bitwise operations // +////////////////////////// + + impl Shl for Value { type Output = Result; @@ -364,8 +393,13 @@ impl BitOr for Value { } } + +///////////////////////////// +// Equality and ordering // +///////////////////////////// + + impl PartialEq for Value { - #[expect(clippy::cast_precision_loss)] fn eq(&self, other: &Self) -> bool { use Value as V; use super::range::RangeType as Rty; @@ -408,7 +442,6 @@ impl PartialEq for Value { } impl PartialOrd for Value { - #[expect(clippy::cast_precision_loss)] fn partial_cmp(&self, other: &Self) -> Option { use Value as V; match (self, other) { @@ -431,6 +464,12 @@ impl PartialOrd for Value { } } + +//////////// +// misc // +//////////// + + impl Value { pub fn val_cmp(&self, other: &Self) -> Result { match self.partial_cmp(other) { diff --git a/talc-std/src/num.rs b/talc-std/src/num.rs index 1a93655..076841e 100644 --- a/talc-std/src/num.rs +++ b/talc-std/src/num.rs @@ -67,6 +67,7 @@ pub fn load(vm: &mut Vm) { 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()); @@ -364,7 +365,7 @@ pub fn lcm(_: &mut Vm, args: Vec) -> Result { if g == 0 { Ok(Value::from(0)) } else { - Value::from(x/g) * Value::from(y) + (Value::from(x)/Value::from(g))? * Value::from(y) } } @@ -381,7 +382,11 @@ pub fn lcmn(vm: &mut Vm, args: Vec) -> Result { }; let g = gcd_inner(l, a); if g == 0 { return Ok(Value::from(0)) }; - l = (l/g).wrapping_mul(a); + 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)) @@ -391,7 +396,7 @@ pub fn lcmn(vm: &mut Vm, args: Vec) -> Result { pub fn factors(_: &mut Vm, args: Vec) -> Result { let [_, x] = unpack_args!(args); let Value::Int(mut x) = x else { - throw!(*SYM_TYPE_ERROR, "factords expected integer argument, got {x:#}") + throw!(*SYM_TYPE_ERROR, "factors expected integer argument, got {x:#}") }; let mut factors = Vec::new(); if x <= 1 { @@ -424,6 +429,49 @@ pub fn factors(_: &mut Vm, args: Vec) -> Result { 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 @@ -669,22 +717,16 @@ pub fn arg(_: &mut Vm, args: Vec) -> Result { #[native_func(1)] pub fn abs(_: &mut Vm, args: Vec) -> Result { let [_, x] = unpack_args!(args); - match x { - Value::Int(x) => Ok(Value::Int(x.abs())), - Value::Ratio(x) => Ok(Value::Ratio(if x < 0.into() { -x } else { x })), - Value::Float(x) => Ok(Value::Float(x.abs())), - Value::Complex(x) => Ok(Value::Float(x.norm())), - x => throw!(*SYM_TYPE_ERROR, "abs expected numeric argument, got {x:#}"), - } + x.abs() } #[native_func(1)] pub fn abs_sq(_: &mut Vm, args: Vec) -> Result { let [_, x] = unpack_args!(args); match x { - Value::Int(x) => Ok(Value::Int(x * x)), - Value::Ratio(x) => Ok(Value::Ratio(x * x)), - Value::Float(x) => Ok(Value::Float(x * x)), + Value::Int(_) => x.clone() * x, + Value::Ratio(_) => x.clone() * x, + Value::Float(_) => x.clone() * x, Value::Complex(x) => Ok(Value::Float(x.norm_sqr())), x => throw!(*SYM_TYPE_ERROR, "abs_sq expected numeric argument, got {x:#}"), }