From 892b697bd30ef35e74b43866e46070b81a338a2c Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Mon, 17 Oct 2022 20:58:08 +0200 Subject: [PATCH] Add BigInt support to Lisp (#415) * Add bigint support to Lisp * Remove Box * Replace BigInt by i64 * Add back big int * Work around big int errors * Print floats with a dot * Add conversion to f64 * Use Number#parse instead of double * Add trigo functions to Number * Add conversion from number to byte * Add addition to Number * Add multiplication to Number * Add negation and substraction to Number * Add division to Number * Add reminder to Number * Add pow to Number * Fix tests * Re-enable BigInt * Add parsing and printing of BigInt * Add sign * Add operations on BigInt * Fix compilation issues * Add support for add and mul overflow * Fix bigint conversion to and from str * Add number-type function * Add tests * Add support for pow overflow * Fix tests * Add more checks for overflow * Add check for division by zero * Fix typo * Return inf for large exponential operations * Check for division by zero in modulo * Add shift operations * Rewrite comparisons * Add lazy eval to cond expressions * Add set fonction * Add loop function * Add pi example * Add builtin pi example to shell * Update allocation error messages * Rewrite number conversions * Remove debug output from pi example * Move pi command to a dedicated file * Rewrite bytes->number and number->bytes * Update doc * Move op impl to Number * Add macros to dry code * Add more macros * Run clippy --- Cargo.lock | 33 ++- Cargo.toml | 3 +- doc/lisp.md | 5 + dsk/lib/lisp/core.lsp | 4 +- dsk/tmp/lisp/pi.lsp | 40 ++- src/lib.rs | 5 +- src/sys/idt.rs | 11 +- src/usr/lisp.rs | 605 +++++++++++++++++++++++++++++++++++------- src/usr/mod.rs | 1 + src/usr/pi.rs | 56 ++++ src/usr/shell.rs | 1 + 11 files changed, 633 insertions(+), 131 deletions(-) create mode 100644 src/usr/pi.rs diff --git a/Cargo.lock b/Cargo.lock index 99acb59..cfeb5a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,15 +131,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - [[package]] name = "funty" version = "1.2.0" @@ -253,13 +244,14 @@ dependencies = [ "base64", "bit_field", "bootloader", - "float-cmp", "hmac", "lazy_static", "libm", "linked_list_allocator", "littlewing", "nom", + "num-bigint", + "num-traits", "object", "pbkdf2", "pc-keyboard", @@ -292,6 +284,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 854a775..d087292 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,14 @@ aml = "0.16.1" base64 = { version = "0.13.0", default-features = false } bit_field = "0.10.0" bootloader = { version = "0.9.23", features = ["map_physical_memory"] } -float-cmp = "0.9.0" hmac = { version = "0.12.1", default-features = false } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } libm = "0.2.5" linked_list_allocator = "0.10.4" littlewing = { version = "0.7.0", default-features = false } nom = { version = "7.1.1", default-features = false, features = ["alloc"] } +num-bigint = { version = "0.4.3", default-features = false } +num-traits = { version = "0.2.15", default-features = false } object = { version = "0.29.0", default-features = false, features = ["read"] } pbkdf2 = { version = "0.11.0", default-features = false } pc-keyboard = "0.5.1" diff --git a/doc/lisp.md b/doc/lisp.md index cbb1994..97611cb 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -14,6 +14,11 @@ In version 0.2.0 the whole implementation was refactored and the parser was rewritten to use [Nom](https://github.com/Geal/nom). This allowed the addition of strings to the language and reading from the filesystem. + +## Types +- Basics: `bool`, `list`, `symbol`, `string` +- Numbers: `float`, `int`, `bigint` + ## Seven Primitive Operators - `quote` (with the `'` syntax) - `atom` (aliased to `atom?`) diff --git a/dsk/lib/lisp/core.lsp b/dsk/lib/lisp/core.lsp index d6f4d02..bfdb6bb 100644 --- a/dsk/lib/lisp/core.lsp +++ b/dsk/lib/lisp/core.lsp @@ -97,10 +97,10 @@ (print "\n"))) (define (uptime) - (bytes->number (read-file-bytes "/dev/clk/uptime" 8))) + (bytes->number (read-file-bytes "/dev/clk/uptime" 8) "float")) (define (realtime) - (bytes->number (read-file-bytes "realtime" 8))) + (bytes->number (read-file-bytes "realtime" 8) "float")) (define (write-file path str) (write-file-bytes path (string->bytes str))) diff --git a/dsk/tmp/lisp/pi.lsp b/dsk/tmp/lisp/pi.lsp index 7c92480..3df8971 100644 --- a/dsk/tmp/lisp/pi.lsp +++ b/dsk/tmp/lisp/pi.lsp @@ -1,14 +1,36 @@ (load "/lib/lisp/core.lsp") -(define (pi-nth n) - (* (^ 16 (- n)) (- - (/ 4 (+ 1 (* 8 n))) - (/ 2 (+ 4 (* 8 n))) - (/ 1 (+ 5 (* 8 n))) - (/ 1 (+ 6 (* 8 n)))))) - -(define (pi-digits n) - (apply + (map pi-nth (range 0 n)))) +(define (pi-digits y) + (do + (define dot true) + (define q 1) + (define r 0) + (define t 1) + (define k 1) + (define n 3) + (define l 3) + (map + (lambda (j) + (do + (cond + ((< (- (+ (* q 4) r) t) (* n t)) (do + (print (string n (cond (dot ".") (true "")))) + (set dot false) + (define nr (* 10 (- r (* n t)))) + (set n (- (/ (* 10 (+ (* 3 q) r)) t) (* 10 n))) + (set q (* q 10)) + (set r nr))) + (true (do + (define nr (* (+ (* 2 q) r) l)) + (define nn (/ (+ 2 (* q k 7) (* r l)) (* t l))) + (set q (* q k)) + (set t (* t l)) + (set l (+ l 2)) + (set k (+ k 1)) + (set n nn) + (set r nr)))))) + (range 0 y)) + n)) (println (cond diff --git a/src/lib.rs b/src/lib.rs index f8f8733..bb88952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,10 @@ pub fn init(boot_info: &'static BootInfo) { #[alloc_error_handler] fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { - panic!("allocation error: {:?}", layout) + let csi_color = api::console::Style::color("LightRed"); + let csi_reset = api::console::Style::reset(); + printk!("{}Error:{} Could not allocate {} bytes\n", csi_color, csi_reset, layout.size()); + hlt_loop(); } pub trait Testable { diff --git a/src/sys/idt.rs b/src/sys/idt.rs index 8ab6874..d7ce061 100644 --- a/src/sys/idt.rs +++ b/src/sys/idt.rs @@ -1,6 +1,5 @@ -use crate::api; +use crate::{api, sys, hlt_loop}; use crate::api::process::ExitCode; -use crate::sys; use crate::sys::process::Registers; use core::arch::asm; @@ -112,13 +111,13 @@ extern "x86-interrupt" fn page_fault_handler(_stack_frame: InterruptStackFrame, //debug!("EXCEPTION: PAGE FAULT ({:?})", error_code); let addr = Cr2::read().as_u64(); if sys::allocator::alloc_pages(addr, 1).is_err() { + let csi_color = api::console::Style::color("LightRed"); + let csi_reset = api::console::Style::reset(); + printk!("{}Error:{} Could not allocate address {:#x}\n", csi_color, csi_reset, addr); if error_code.contains(PageFaultErrorCode::USER_MODE) { - let csi_color = api::console::Style::color("LightRed"); - let csi_reset = api::console::Style::reset(); - printk!("{}Error:{} Could not allocate address {:#x}\n", csi_color, csi_reset, addr); api::syscall::exit(ExitCode::PageFaultError); } else { - panic!(); + hlt_loop(); } } } diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs index 16da8e9..5062203 100644 --- a/src/usr/lisp.rs +++ b/src/usr/lisp.rs @@ -15,10 +15,15 @@ use alloc::vec; use core::borrow::Borrow; use core::cell::RefCell; use core::convert::TryInto; +use core::convert::TryFrom; use core::f64::consts::PI; use core::fmt; -use float_cmp::approx_eq; +use core::ops::{Neg, Add, Div, Mul, Sub, Rem, Shl, Shr}; +use core::str::FromStr; use lazy_static::lazy_static; +use num_bigint::BigInt; +use num_traits::cast::ToPrimitive; +use num_traits::Zero; use spin::Mutex; use nom::IResult; @@ -29,13 +34,15 @@ use nom::bytes::complete::tag; use nom::bytes::complete::take_while1; use nom::character::complete::char; use nom::character::complete::multispace0; +use nom::character::complete::digit1; use nom::combinator::map; use nom::combinator::opt; use nom::combinator::value; use nom::multi::many0; -use nom::number::complete::double; use nom::sequence::delimited; use nom::sequence::preceded; +use nom::sequence::tuple; +use nom::combinator::recognize; // Eval & Env adapted from Risp // Copyright 2019 Stepan Parunashvili @@ -52,13 +59,320 @@ use nom::sequence::preceded; // Types +#[derive(Clone, PartialEq, PartialOrd)] +pub enum Number { + BigInt(BigInt), + Float(f64), + Int(i64), +} + +macro_rules! trigonometric_method { + ($op:ident) => { + fn $op(&self) -> Number { + Number::Float(libm::$op(self.into())) + } + } +} + +macro_rules! arithmetic_method { + ($op:ident, $checked_op:ident) => { + fn $op(self, other: Number) -> Number { + match (self, other) { + (Number::BigInt(a), Number::BigInt(b)) => Number::BigInt(a.$op(b)), + (Number::BigInt(a), Number::Int(b)) => Number::BigInt(a.$op(b)), + (Number::Int(a), Number::BigInt(b)) => Number::BigInt(a.$op(b)), + (Number::Int(a), Number::Int(b)) => { + if let Some(r) = a.$checked_op(b) { + Number::Int(r) + } else { + Number::BigInt(BigInt::from(a).$op(BigInt::from(b))) + } + } + (Number::Int(a), Number::Float(b)) => Number::Float((a as f64).$op(b)), + (Number::Float(a), Number::Int(b)) => Number::Float(a.$op(b as f64)), + (Number::Float(a), Number::Float(b)) => Number::Float(a.$op(b)), + _ => Number::Float(f64::NAN), // TODO + } + } + } +} + +impl Number { + trigonometric_method!(cos); + trigonometric_method!(sin); + trigonometric_method!(tan); + trigonometric_method!(acos); + trigonometric_method!(asin); + trigonometric_method!(atan); + + arithmetic_method!(add, checked_add); + arithmetic_method!(sub, checked_sub); + arithmetic_method!(mul, checked_mul); + arithmetic_method!(div, checked_div); + + // NOTE: Rem use `libm::fmod` for `f64` instead of `rem` + fn rem(self, other: Number) -> Number { + match (self, other) { + (Number::BigInt(a), Number::BigInt(b)) => Number::BigInt(a.rem(b)), + (Number::BigInt(a), Number::Int(b)) => Number::BigInt(a.rem(b)), + (Number::Int(a), Number::BigInt(b)) => Number::BigInt(a.rem(b)), + (Number::Int(a), Number::Int(b)) => { + if let Some(r) = a.checked_rem(b) { + Number::Int(r) + } else { + Number::BigInt(BigInt::from(a).rem(BigInt::from(b))) + } + } + (Number::Int(a), Number::Float(b)) => Number::Float(libm::fmod(a as f64, b)), + (Number::Float(a), Number::Int(b)) => Number::Float(libm::fmod(a, b as f64)), + (Number::Float(a), Number::Float(b)) => Number::Float(libm::fmod(a, b)), + _ => Number::Float(f64::NAN), // TODO + } + } + + fn pow(&self, other: &Number) -> Number { + let bmax = BigInt::from(u32::MAX); + let imax = u32::MAX as i64; + match (self, other) { + (_, Number::BigInt(b)) if *b > bmax => Number::Float(f64::INFINITY), + (_, Number::Int(b)) if *b > imax => Number::Float(f64::INFINITY), + (Number::BigInt(a), Number::Int(b)) => Number::BigInt(a.pow(*b as u32)), + (Number::Int(a), Number::Int(b)) => { + if let Some(r) = a.checked_pow(*b as u32) { + Number::Int(r) + } else { + Number::BigInt(BigInt::from(*a)).pow(other) + } + } + (Number::Int(a), Number::Float(b)) => Number::Float(libm::pow(*a as f64, *b)), + (Number::Float(a), Number::Int(b)) => Number::Float(libm::pow(*a, *b as f64)), + (Number::Float(a), Number::Float(b)) => Number::Float(libm::pow(*a, *b)), + _ => Number::Float(f64::NAN), // TODO + } + } + + fn neg(self) -> Number { + match self { + Number::BigInt(a) => Number::BigInt(-a), + Number::Int(a) => { + if let Some(r) = a.checked_neg() { + Number::Int(r) + } else { + Number::BigInt(-BigInt::from(a)) + } + } + Number::Float(a) => Number::Float(-a), + } + } + + fn shl(self, other: Number) -> Number { + match (self, other) { + (Number::BigInt(a), Number::Int(b)) => Number::BigInt(a.shl(b)), + (Number::Int(a), Number::Int(b)) => { + if let Some(r) = a.checked_shl(b as u32) { + Number::Int(r) + } else { + Number::BigInt(BigInt::from(a).shl(b)) + } + } + _ => Number::Float(f64::NAN), // TODO + } + } + + fn shr(self, other: Number) -> Number { + match (self, other) { + (Number::BigInt(a), Number::Int(b)) => Number::BigInt(a.shr(b)), + (Number::Int(a), Number::Int(b)) => { + if let Some(r) = a.checked_shr(b as u32) { + Number::Int(r) + } else { + Number::BigInt(BigInt::from(a).shr(b)) + } + } + _ => Number::Float(f64::NAN), // TODO + } + } + + fn to_be_bytes(&self) -> Vec { + match self { + Number::Int(n) => n.to_be_bytes().to_vec(), + Number::Float(n) => n.to_be_bytes().to_vec(), + Number::BigInt(n) => n.to_bytes_be().1, // TODO + } + } + + fn is_zero(&self) -> bool { + match self { + Number::Int(n) => *n == 0, + Number::Float(n) => *n == 0.0, + Number::BigInt(n) => n.is_zero(), + } + } +} + +impl Neg for Number { + type Output = Number; + fn neg(self) -> Number { + self.neg() + } +} + +macro_rules! operator { + ($t:ty, $op:ident) => { + impl $t for Number { + type Output = Number; + fn $op(self, other: Number) -> Number { + self.$op(other) + } + } + } +} + +operator!(Add, add); +operator!(Sub, sub); +operator!(Mul, mul); +operator!(Div, div); +operator!(Rem, rem); +operator!(Shl, shl); +operator!(Shr, shr); + +impl FromStr for Number { + type Err = Err; + + fn from_str(s: &str) -> Result { + if s.contains('.') { + if let Ok(n) = s.parse() { + return Ok(Number::Float(n)); + } + } else if let Ok(n) = s.parse() { + return Ok(Number::Int(n)); + } else { + let mut chars = s.chars().peekable(); + let is_neg = chars.peek() == Some(&'-'); + if is_neg { + chars.next().unwrap(); + } + let mut res = BigInt::from(0); + for c in chars { + let d = c as u8 - b'0'; + res = res * BigInt::from(10) + BigInt::from(d as u32); + } + res *= BigInt::from(if is_neg { -1 } else { 1 }); + return Ok(Number::BigInt(res)); + } /* else if let Ok(n) = s.parse() { // FIXME: rust-lld: error: undefined symbol: fmod + return Ok(Number::BigInt(n)); + } */ + Err(Err::Reason("Could not parse number".to_string())) + } +} + +impl From<&str> for Number { + fn from(s: &str) -> Self { + if let Ok(num) = s.parse() { + num + } else { + Number::Float(f64::NAN) + } + } +} + +impl From for Number { + fn from(num: f64) -> Self { + Number::Float(num) + } +} + +impl From for Number { + fn from(num: u8) -> Self { + Number::Int(num as i64) + } +} + +impl From for Number { + fn from(num: usize) -> Self { + if num > i64::MAX as usize { + Number::BigInt(BigInt::from(num)) + } else { + Number::Int(num as i64) + } + } +} + +impl From<&Number> for f64 { + fn from(num: &Number) -> f64 { + match num { + Number::Float(n) => *n, + Number::Int(n) => *n as f64, + Number::BigInt(n) => n.to_f64().unwrap_or(f64::NAN), + } + } +} + +macro_rules! try_from_number { + ($int:ident, $to_int:ident) => { + impl TryFrom for $int { + type Error = Err; + + fn try_from(num: Number) -> Result { + let err = Err::Reason(format!("Expected an integer between 0 and {}", $int::MAX)); + match num { + Number::Float(n) => $int::try_from(n as i64).or(Err(err)), + Number::Int(n) => $int::try_from(n).or(Err(err)), + Number::BigInt(n) => n.$to_int().ok_or(err), + } + } + } + } +} + +try_from_number!(usize, to_usize); +try_from_number!(u8, to_u8); + +impl fmt::Display for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + //write!(f, "{}", self), // FIXME: alloc error + match self { + Number::Int(n) => { + write!(f, "{}", n) + } + Number::BigInt(n) => { + //write!(f, "{}", n), // FIXME: rust-lld: error: undefined symbol: fmod + let mut v = Vec::new(); + let mut n = n.clone(); + if n < BigInt::from(0) { + write!(f, "-").ok(); + n = -n; + } + loop { + v.push((n.clone() % BigInt::from(10)).to_u64().unwrap()); + n = n / BigInt::from(10); + if n == BigInt::from(0) { + break; + } + } + for d in v.iter().rev() { + write!(f, "{}", d).ok(); + } + Ok(()) + } + Number::Float(n) => { + if n - libm::trunc(*n) == 0.0 { + write!(f, "{}.0", n) + } else { + write!(f, "{}", n) + } + } + } + } +} + #[derive(Clone)] enum Exp { Primitive(fn(&[Exp]) -> Result), Lambda(Lambda), List(Vec), Bool(bool), - Num(f64), + Num(Number), Str(String), Sym(String), } @@ -102,7 +416,7 @@ struct Lambda { } #[derive(Debug)] -enum Err { +pub enum Err { Reason(String), } @@ -153,8 +467,12 @@ fn parse_sym(input: &str) -> IResult<&str, Exp> { } fn parse_num(input: &str) -> IResult<&str, Exp> { - let (input, num) = double(input)?; - Ok((input, Exp::Num(num))) + let (input, num) = recognize(tuple(( + opt(alt((char('+'), char('-')))), + digit1, + opt(tuple((char('.'), digit1))) + )))(input)?; + Ok((input, Exp::Num(Number::from(num)))) } fn parse_bool(input: &str) -> IResult<&str, Exp> { @@ -186,24 +504,6 @@ fn parse(input: &str)-> Result<(String, Exp), Err> { // Env -macro_rules! ensure_tonicity { - ($check_fn:expr) => { - |args: &[Exp]| -> Result { - let floats = list_of_floats(args)?; - ensure_length_gt!(floats, 0); - let first = &floats[0]; - let rest = &floats[1..]; - fn f(prev: &f64, xs: &[f64]) -> bool { - match xs.first() { - Some(x) => $check_fn(*prev, *x) && f(x, &xs[1..]), - None => true, - } - } - Ok(Exp::Bool(f(first, rest))) - } - }; -} - macro_rules! ensure_length_eq { ($list:expr, $count:expr) => { if $list.len() != $count { @@ -224,100 +524,125 @@ macro_rules! ensure_length_gt { fn default_env() -> Rc> { let mut data: BTreeMap = BTreeMap::new(); - data.insert("pi".to_string(), Exp::Num(PI)); - data.insert("=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b)))); - data.insert(">".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a > b))); - data.insert(">=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a > b))); - data.insert("<".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a < b))); - data.insert("<=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a < b))); + data.insert("pi".to_string(), Exp::Num(Number::from(PI))); + data.insert("=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] == nums[1]))) + })); + data.insert(">".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] > nums[1]))) + })); + data.insert(">=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] >= nums[1]))) + })); + data.insert("<".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] < nums[1]))) + })); + data.insert("<=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] <= nums[1]))) + })); data.insert("*".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { - let res = list_of_floats(args)?.iter().fold(1.0, |acc, a| acc * a); + let res = list_of_numbers(args)?.iter().fold(Number::Int(1), |acc, a| acc * a.clone()); Ok(Exp::Num(res)) })); data.insert("+".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { - let res = list_of_floats(args)?.iter().fold(0.0, |acc, a| acc + a); + let res = list_of_numbers(args)?.iter().fold(Number::Int(0), |acc, a| acc + a.clone()); Ok(Exp::Num(res)) })); data.insert("-".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); - let args = list_of_floats(args)?; - let car = args[0]; + let args = list_of_numbers(args)?; + let car = args[0].clone(); if args.len() == 1 { Ok(Exp::Num(-car)) } else { - let res = args[1..].iter().fold(0.0, |acc, a| acc + a); + let res = args[1..].iter().fold(Number::Int(0), |acc, a| acc + a.clone()); Ok(Exp::Num(car - res)) } })); data.insert("/".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); - let args = list_of_floats(args)?; - let car = args[0]; + let mut args = list_of_numbers(args)?; if args.len() == 1 { - Ok(Exp::Num(1.0 / car)) - } else { - let res = args[1..].iter().fold(car, |acc, a| acc / a); - Ok(Exp::Num(res)) + args.insert(0, Number::Int(1)); } + for arg in &args[1..] { + if arg.is_zero() { + return Err(Err::Reason("Division by zero".to_string())); + } + } + let car = args[0].clone(); + let res = args[1..].iter().fold(car, |acc, a| acc / a.clone()); + Ok(Exp::Num(res)) })); data.insert("%".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); - let args = list_of_floats(args)?; - let car = args[0]; - let res = args[1..].iter().fold(car, |acc, a| libm::fmod(acc, *a)); + let args = list_of_numbers(args)?; + for arg in &args[1..] { + if arg.is_zero() { + return Err(Err::Reason("Division by zero".to_string())); + } + } + let car = args[0].clone(); + let res = args[1..].iter().fold(car, |acc, a| acc % a.clone()); Ok(Exp::Num(res)) })); data.insert("^".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); - let args = list_of_floats(args)?; - let car = args[0]; - let res = args[1..].iter().fold(car, |acc, a| libm::pow(acc, *a)); + let args = list_of_numbers(args)?; + let car = args[0].clone(); + let res = args[1..].iter().fold(car, |acc, a| acc.pow(a)); + Ok(Exp::Num(res)) + })); + data.insert("<<".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + ensure_length_eq!(args, 2); + let args = list_of_numbers(args)?; + let res = args[0].clone() << args[1].clone(); + Ok(Exp::Num(res)) + })); + data.insert(">>".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + ensure_length_eq!(args, 2); + let args = list_of_numbers(args)?; + let res = args[0].clone() >> args[1].clone(); Ok(Exp::Num(res)) })); data.insert("cos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - Ok(Exp::Num(libm::cos(args[0]))) + Ok(Exp::Num(number(&args[0])?.cos())) })); data.insert("acos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - if -1.0 <= args[0] && args[0] <= 1.0 { - Ok(Exp::Num(libm::acos(args[0]))) + if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 { + Ok(Exp::Num(number(&args[0])?.acos())) } else { Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string())) } })); data.insert("asin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - if -1.0 <= args[0] && args[0] <= 1.0 { - Ok(Exp::Num(libm::asin(args[0]))) + if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 { + Ok(Exp::Num(number(&args[0])?.asin())) } else { Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string())) } })); data.insert("atan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - Ok(Exp::Num(libm::atan(args[0]))) + Ok(Exp::Num(number(&args[0])?.atan())) })); data.insert("sin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - Ok(Exp::Num(libm::sin(args[0]))) + Ok(Exp::Num(number(&args[0])?.sin())) })); data.insert("tan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let args = list_of_floats(args)?; - Ok(Exp::Num(libm::tan(args[0]))) + Ok(Exp::Num(number(&args[0])?.tan())) })); data.insert("system".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); let cmd = string(&args[0])?; match usr::shell::exec(&cmd) { - Ok(()) => Ok(Exp::Num(0.0)), - Err(code) => Ok(Exp::Num(code as u8 as f64)), + Ok(()) => Ok(Exp::Num(Number::from(0 as u8))), + Err(code) => Ok(Exp::Num(Number::from(code as u8))), } })); data.insert("read-file".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { @@ -329,11 +654,11 @@ fn default_env() -> Rc> { data.insert("read-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); let path = string(&args[0])?; - let len = float(&args[1])?; - let mut buf = vec![0; len as usize]; + let len = number(&args[1])?; + let mut buf = vec![0; len.try_into()?]; let bytes = fs::read(&path, &mut buf).or(Err(Err::Reason("Could not read file".to_string())))?; buf.resize(bytes, 0); - Ok(Exp::List(buf.iter().map(|b| Exp::Num(*b as f64)).collect())) + Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect())) })); data.insert("write-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); @@ -342,7 +667,7 @@ fn default_env() -> Rc> { Exp::List(list) => { let buf = list_of_bytes(list)?; let bytes = fs::write(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?; - Ok(Exp::Num(bytes as f64)) + Ok(Exp::Num(Number::from(bytes))) } _ => Err(Err::Reason("Expected second arg to be a list".to_string())) } @@ -354,7 +679,7 @@ fn default_env() -> Rc> { Exp::List(list) => { let buf = list_of_bytes(list)?; let bytes = fs::append(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?; - Ok(Exp::Num(bytes as f64)) + Ok(Exp::Num(Number::from(bytes))) } _ => Err(Err::Reason("Expected second arg to be a list".to_string())) } @@ -370,7 +695,7 @@ fn default_env() -> Rc> { ensure_length_eq!(args, 1); let s = string(&args[0])?; let buf = s.as_bytes(); - Ok(Exp::List(buf.iter().map(|b| Exp::Num(*b as f64)).collect())) + Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect())) })); data.insert("bytes->string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); @@ -384,27 +709,32 @@ fn default_env() -> Rc> { } })); data.insert("bytes->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { - ensure_length_eq!(args, 1); - match &args[0] { - Exp::List(list) => { + ensure_length_eq!(args, 2); + match (&args[0], &args[1]) { // TODO: default type to "int" and make it optional + (Exp::List(list), Exp::Str(kind)) => { let bytes = list_of_bytes(list)?; ensure_length_eq!(bytes, 8); - Ok(Exp::Num(f64::from_be_bytes(bytes[0..8].try_into().unwrap()))) + match kind.as_str() { // TODO: bigint + "int" => Ok(Exp::Num(Number::Int(i64::from_be_bytes(bytes[0..8].try_into().unwrap())))), + "float" => Ok(Exp::Num(Number::Float(f64::from_be_bytes(bytes[0..8].try_into().unwrap())))), + _ => Err(Err::Reason("Invalid number type".to_string())), + } } - _ => Err(Err::Reason("Expected arg to be a list".to_string())) + _ => Err(Err::Reason("Expected args to be the number type and a list of bytes".to_string())) } + })); data.insert("number->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); - let f = float(&args[0])?; - Ok(Exp::List(f.to_be_bytes().iter().map(|b| Exp::Num(*b as f64)).collect())) + let n = number(&args[0])?; + Ok(Exp::List(n.to_be_bytes().iter().map(|b| Exp::Num(Number::from(*b))).collect())) })); data.insert("regex-find".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); match (&args[0], &args[1]) { (Exp::Str(regex), Exp::Str(s)) => { let res = Regex::new(regex).find(s).map(|(a, b)| { - vec![Exp::Num(a as f64), Exp::Num(b as f64)] + vec![Exp::Num(Number::from(a)), Exp::Num(Number::from(b))] }).unwrap_or(vec![]); Ok(Exp::List(res)) } @@ -436,6 +766,15 @@ fn default_env() -> Rc> { }; Ok(Exp::Str(exp.to_string())) })); + data.insert("number-type".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + ensure_length_eq!(args, 1); + match args[0] { + Exp::Num(Number::Int(_)) => Ok(Exp::Str("int".to_string())), + Exp::Num(Number::BigInt(_)) => Ok(Exp::Str("bigint".to_string())), + Exp::Num(Number::Float(_)) => Ok(Exp::Str("float".to_string())), + _ => Err(Err::Reason("Expected arg to be a number".to_string())) + } + })); data.insert("list".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { Ok(Exp::List(args.to_vec())) })); @@ -466,8 +805,8 @@ fn list_of_symbols(form: &Exp) -> Result, Err> { } } -fn list_of_floats(args: &[Exp]) -> Result, Err> { - args.iter().map(float).collect() +fn list_of_numbers(args: &[Exp]) -> Result, Err> { + args.iter().map(number).collect() } fn list_of_bytes(args: &[Exp]) -> Result, Err> { @@ -481,22 +820,24 @@ fn string(exp: &Exp) -> Result { } } -fn float(exp: &Exp) -> Result { +fn number(exp: &Exp) -> Result { match exp { - Exp::Num(num) => Ok(*num), + Exp::Num(num) => Ok(num.clone()), _ => Err(Err::Reason("Expected a number".to_string())), } } -fn byte(exp: &Exp) -> Result { - let num = float(exp)?; - if num >= 0.0 && num < u8::MAX.into() && (num - libm::trunc(num) == 0.0) { - Ok(num as u8) - } else { - Err(Err::Reason(format!("Expected an integer between 0 and {}", u8::MAX))) +fn float(exp: &Exp) -> Result { + match exp { + Exp::Num(num) => Ok(num.into()), + _ => Err(Err::Reason("Expected a float".to_string())), } } +fn byte(exp: &Exp) -> Result { + number(exp)?.try_into() +} + // Eval fn eval_quote_args(args: &[Exp]) -> Result { @@ -558,10 +899,8 @@ fn eval_cond_args(args: &[Exp], env: &mut Rc>) -> Result match arg { Exp::List(list) => { ensure_length_eq!(list, 2); - let pred = eval(&list[0], env)?; - let exp = eval(&list[1], env)?; - match pred { - Exp::Bool(b) if b => return Ok(exp), + match eval(&list[0], env)? { + Exp::Bool(b) if b => return eval(&list[1], env), _ => continue, } }, @@ -593,6 +932,25 @@ fn eval_label_args(args: &[Exp], env: &mut Rc>) -> Result } } +fn eval_set_args(args: &[Exp], env: &mut Rc>) -> Result { + ensure_length_eq!(args, 2); + match &args[0] { + Exp::Sym(name) => { + let exp = eval(&args[1], env)?; + env_set(name, exp, env)?; + Ok(Exp::Sym(name.clone())) + } + _ => Err(Err::Reason("Expected first argument to be a symbol".to_string())) + } +} + +fn eval_loop_args(args: &[Exp], env: &mut Rc>) -> Result { + ensure_length_eq!(args, 1); + loop { + eval(&args[0], env)?; + } +} + fn eval_lambda_args(args: &[Exp]) -> Result { ensure_length_eq!(args, 2); Ok(Exp::Lambda(Lambda { @@ -673,6 +1031,8 @@ fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Rc>) -> Op "label" | "define" | "def" => Some(eval_label_args(args, env)), "lambda" | "function" | "fn" => Some(eval_lambda_args(args)), + "set" => Some(eval_set_args(args, env)), + "loop" => Some(eval_loop_args(args, env)), "defun" | "defn" => Some(eval_defun_args(args, env)), "apply" => Some(eval_apply_args(args, env)), "eval" => Some(eval_eval_args(args, env)), @@ -698,6 +1058,22 @@ fn env_get(key: &str, env: &Rc>) -> Result { } } +fn env_set(key: &str, val: Exp, env: &Rc>) -> Result<(), Err> { + let mut env = env.borrow_mut(); + match env.data.get(key) { + Some(_) => { + env.data.insert(key.to_string(), val); + Ok(()) + } + None => { + match &env.outer { + Some(outer_env) => env_set(key, val, outer_env.borrow()), + None => Err(Err::Reason(format!("Unexpected symbol '{}'", key))), + } + } + } +} + fn lambda_env(params: Rc, args: &[Exp], outer: &mut Rc>) -> Result>, Err> { let ks = list_of_symbols(¶ms)?; if ks.len() != args.len() { @@ -718,7 +1094,7 @@ fn eval_args(args: &[Exp], env: &mut Rc>) -> Result, Err> fn eval(exp: &Exp, env: &mut Rc>) -> Result { match exp { - Exp::Sym(key) => env_get(&key, &env), + Exp::Sym(key) => env_get(key, env), Exp::Bool(_) => Ok(exp.clone()), Exp::Num(_) => Ok(exp.clone()), Exp::Str(_) => Ok(exp.clone()), @@ -888,7 +1264,8 @@ fn test_lisp() { assert_eq!(eval!("(eq \"a\" 'b)"), "false"); assert_eq!(eval!("(eq 1 1)"), "true"); assert_eq!(eval!("(eq 1 2)"), "false"); - assert_eq!(eval!("(eq 1 1.0)"), "true"); + assert_eq!(eval!("(eq 1 1.0)"), "false"); + assert_eq!(eval!("(eq 1.0 1.0)"), "true"); // car assert_eq!(eval!("(car (quote (1)))"), "1"); @@ -949,9 +1326,11 @@ fn test_lisp() { assert_eq!(eval!("(* 2 (* 3 4))"), "24"); // division - assert_eq!(eval!("(/ 4)"), "0.25"); + assert_eq!(eval!("(/ 4)"), "0"); + assert_eq!(eval!("(/ 4.0)"), "0.25"); assert_eq!(eval!("(/ 4 2)"), "2"); - assert_eq!(eval!("(/ 1 2)"), "0.5"); + assert_eq!(eval!("(/ 1 2)"), "0"); + assert_eq!(eval!("(/ 1 2.0)"), "0.5"); assert_eq!(eval!("(/ 8 4 2)"), "1"); // exponential @@ -963,13 +1342,17 @@ fn test_lisp() { // comparisons assert_eq!(eval!("(< 6 4)"), "false"); - assert_eq!(eval!("(> 6 4 3 1)"), "true"); + assert_eq!(eval!("(> 6 4)"), "true"); + assert_eq!(eval!("(> 6 4 2)"), "true"); + assert_eq!(eval!("(> 6)"), "true"); + assert_eq!(eval!("(>)"), "true"); assert_eq!(eval!("(= 6 4)"), "false"); assert_eq!(eval!("(= 6 6)"), "true"); - assert_eq!(eval!("(= (+ 0.15 0.15) (+ 0.1 0.2))"), "true"); + assert_eq!(eval!("(= (+ 0.15 0.15) (+ 0.1 0.2))"), "false"); // FIXME? // number - assert_eq!(eval!("(bytes->number (number->bytes 42))"), "42"); + assert_eq!(eval!("(bytes->number (number->bytes 42) \"int\")"), "42"); + assert_eq!(eval!("(bytes->number (number->bytes 42.0) \"float\")"), "42.0"); // string assert_eq!(eval!("(parse \"9.75\")"), "9.75"); @@ -990,14 +1373,32 @@ fn test_lisp() { assert_eq!(eval!("(acos (cos pi))"), PI.to_string()); assert_eq!(eval!("(acos 0)"), (PI / 2.0).to_string()); assert_eq!(eval!("(asin 1)"), (PI / 2.0).to_string()); - assert_eq!(eval!("(atan 0)"), "0"); - assert_eq!(eval!("(cos pi)"), "-1"); - assert_eq!(eval!("(sin (/ pi 2))"), "1"); - assert_eq!(eval!("(tan 0)"), "0"); + assert_eq!(eval!("(atan 0)"), "0.0"); + assert_eq!(eval!("(cos pi)"), "-1.0"); + assert_eq!(eval!("(sin (/ pi 2))"), "1.0"); + assert_eq!(eval!("(tan 0)"), "0.0"); // list assert_eq!(eval!("(list)"), "()"); assert_eq!(eval!("(list 1)"), "(1)"); assert_eq!(eval!("(list 1 2)"), "(1 2)"); assert_eq!(eval!("(list 1 2 (+ 1 2))"), "(1 2 3)"); + + // bigint + assert_eq!(eval!("9223372036854775807"), "9223372036854775807"); // -> int + assert_eq!(eval!("9223372036854775808"), "9223372036854775808"); // -> bigint + assert_eq!(eval!("(+ 9223372036854775807 0)"), "9223372036854775807"); // -> int + assert_eq!(eval!("(- 9223372036854775808 1)"), "9223372036854775807"); // -> bigint + assert_eq!(eval!("(+ 9223372036854775807 1)"), "9223372036854775808"); // -> bigint + assert_eq!(eval!("(+ 9223372036854775807 1.0)"), "9223372036854776000.0"); // -> float + assert_eq!(eval!("(+ 9223372036854775807 10)"), "9223372036854775817"); // -> bigint + assert_eq!(eval!("(* 9223372036854775807 10)"), "92233720368547758070"); // -> bigint + + assert_eq!(eval!("(^ 2 16)"), "65536"); // -> int + assert_eq!(eval!("(^ 2 128)"), "340282366920938463463374607431768211456"); // -> bigint + assert_eq!(eval!("(^ 2.0 128)"), "340282366920938500000000000000000000000.0"); // -> float + + assert_eq!(eval!("(number-type 9223372036854775807)"), "\"int\""); + assert_eq!(eval!("(number-type 9223372036854775808)"), "\"bigint\""); + assert_eq!(eval!("(number-type 9223372036854776000.0)"), "\"float\""); } diff --git a/src/usr/mod.rs b/src/usr/mod.rs index 706cb24..7b441fd 100644 --- a/src/usr/mod.rs +++ b/src/usr/mod.rs @@ -26,6 +26,7 @@ pub mod list; pub mod memory; pub mod net; pub mod pci; +pub mod pi; pub mod pow; pub mod r#move; pub mod read; diff --git a/src/usr/pi.rs b/src/usr/pi.rs new file mode 100644 index 0000000..8355120 --- /dev/null +++ b/src/usr/pi.rs @@ -0,0 +1,56 @@ +use crate::{usr, sys}; +use crate::api::process::ExitCode; + +use alloc::format; +use num_bigint::BigInt; +use usr::lisp::Number; + +pub fn main(args: &[&str]) -> Result<(), ExitCode> { + let mut digits = None; + if args.len() == 2 { + if let Ok(n) = args[1].parse() { + digits = Some(n); + } + } + + let mut q = BigInt::from(1); + let mut r = BigInt::from(0); + let mut t = BigInt::from(1); + let mut k = BigInt::from(1); + let mut n = BigInt::from(3); + let mut l = BigInt::from(3); + let mut first = true; + loop { + if sys::console::end_of_text() { + break; + } + if &q * 4 + &r - &t < &n * &t { + print!("{}", Number::BigInt(n.clone())); + if first { + print!("."); + first = false; + } + match digits { + Some(0) => break, + Some(i) => digits = Some(i - 1), + None => {}, + } + + let nr = (&r - &n * &t) * 10; + n = (&q * 3 + &r) * 10 / &t - &n * 10; + q *= 10; + r = nr; + } else { + let nr = (&q * 2 + &r) * &l; + let nn = (&q * &k * 7 + 2 + &r * &l) / (&t * &l); + q *= &k; + t *= &l; + l += 2; + k += 1; + n = nn; + r = nr; + } + } + println!(); + Ok(()) +} diff --git a/src/usr/shell.rs b/src/usr/shell.rs index 15c663e..ed60008 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -471,6 +471,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> { "move" => usr::r#move::main(&args), "net" => usr::net::main(&args), "pci" => usr::pci::main(&args), + "pi" => usr::pi::main(&args), "proc" => cmd_proc(&args), "quit" => Err(ExitCode::ShellExit), "read" => usr::read::main(&args),