From 0f8b0824ac6873cc85a4169e6676c52a88ad0002 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 18 Sep 2024 00:57:44 -0500 Subject: [PATCH 1/9] cl-parser: Fix precedence of comparison operators --- compiler/cl-parser/src/parser/prec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/cl-parser/src/parser/prec.rs b/compiler/cl-parser/src/parser/prec.rs index 138f10f..3024912 100644 --- a/compiler/cl-parser/src/parser/prec.rs +++ b/compiler/cl-parser/src/parser/prec.rs @@ -247,9 +247,9 @@ fn structor_body(p: &mut Parser, to: Path) -> PResult { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Precedence { Assign, + Logic, Compare, Range, - Logic, Bitwise, Shift, Factor, From fad28beb0582753ec9ff9c24d1caab28128c8e00 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 18 Sep 2024 01:02:09 -0500 Subject: [PATCH 2/9] interpreter: Add float machinery - operators - type casting --- compiler/cl-interpret/src/builtin.rs | 1 + compiler/cl-interpret/src/convalue.rs | 14 ++++++++++++-- compiler/cl-interpret/src/interpret.rs | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler/cl-interpret/src/builtin.rs b/compiler/cl-interpret/src/builtin.rs index 2ac205b..f14cfbe 100644 --- a/compiler/cl-interpret/src/builtin.rs +++ b/compiler/cl-interpret/src/builtin.rs @@ -223,6 +223,7 @@ builtins! { Ok(match tail { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(v.wrapping_neg()), + ConValue::Float(v) => ConValue::Float(-v), _ => Err(Error::TypeError)?, }) } diff --git a/compiler/cl-interpret/src/convalue.rs b/compiler/cl-interpret/src/convalue.rs index 999027c..5b9db2a 100644 --- a/compiler/cl-interpret/src/convalue.rs +++ b/compiler/cl-interpret/src/convalue.rs @@ -20,6 +20,8 @@ pub enum ConValue { Empty, /// An integer Int(Integer), + /// A floating point number + Float(f64), /// A boolean Bool(bool), /// A unicode character @@ -124,6 +126,7 @@ macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$( match (self, other) { (Self::Empty, Self::Empty) => Ok(Self::Bool($empty)), (Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)), + (Self::Float(a), Self::Float(b)) => Ok(Self::Bool(a $op b)), (Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)), (Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)), (Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)), @@ -150,6 +153,7 @@ impl From<&Sym> for ConValue { } from! { Integer => ConValue::Int, + f64 => ConValue::Float, bool => ConValue::Bool, char => ConValue::Char, Sym => ConValue::String, @@ -189,7 +193,8 @@ ops! { Add: add = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_add(b)), - (ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(), + (ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a + b), + (ConValue::String(a), ConValue::String(b)) => (a.to_string() + &*b).into(), (ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() } (ConValue::Char(a), ConValue::Char(b)) => { ConValue::String([a, b].into_iter().collect::().into()) @@ -219,18 +224,21 @@ ops! { (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| { eprintln!("Warning: Divide by zero in {a} / {b}"); a })), + (ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a / b), _ => Err(Error::TypeError)? ] Mul: mul = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)), + (ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a * b), _ => Err(Error::TypeError)? ] Rem: rem = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| { - eprintln!("Warning: Divide by zero in {a} % {b}"); a + println!("Warning: Divide by zero in {a} % {b}"); a })), + (ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a % b), _ => Err(Error::TypeError)? ] Shl: shl = [ @@ -246,6 +254,7 @@ ops! { Sub: sub = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)), + (ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a - b), _ => Err(Error::TypeError)? ] } @@ -254,6 +263,7 @@ impl std::fmt::Display for ConValue { match self { ConValue::Empty => "Empty".fmt(f), ConValue::Int(v) => v.fmt(f), + ConValue::Float(v) => v.fmt(f), ConValue::Bool(v) => v.fmt(f), ConValue::Char(v) => v.fmt(f), ConValue::String(v) => v.fmt(f), diff --git a/compiler/cl-interpret/src/interpret.rs b/compiler/cl-interpret/src/interpret.rs index 69a1819..6cb3c7d 100644 --- a/compiler/cl-interpret/src/interpret.rs +++ b/compiler/cl-interpret/src/interpret.rs @@ -358,6 +358,9 @@ fn cast(value: ConValue, ty: Sym) -> IResult { ConValue::Bool(b) => b as _, ConValue::Char(c) => c as _, ConValue::Ref(v) => return cast((*v).clone(), ty), + // TODO: This, better + ConValue::Float(_) if ty.starts_with('f') => return Ok(value), + ConValue::Float(f) => f as _, _ => Err(Error::TypeError)?, }; Ok(match &*ty { @@ -369,6 +372,8 @@ fn cast(value: ConValue, ty: Sym) -> IResult { "i32" => ConValue::Int(value as i32 as _), "u64" => ConValue::Int(value), "i64" => ConValue::Int(value), + "f32" => ConValue::Float(value as f32 as _), + "f64" => ConValue::Float(value as f64 as _), "char" => ConValue::Char(char::from_u32(value as _).unwrap_or('\u{fffd}')), "bool" => ConValue::Bool(value < 0), _ => Err(Error::NotDefined(ty))?, From c62df3d8b36bd4dea576874f2042df5af20fc0cf Mon Sep 17 00:00:00 2001 From: John Date: Wed, 18 Sep 2024 01:53:04 -0500 Subject: [PATCH 3/9] sample-code: Add square root demo --- sample-code/sqrt.cl | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 sample-code/sqrt.cl diff --git a/sample-code/sqrt.cl b/sample-code/sqrt.cl new file mode 100755 index 0000000..7e9672f --- /dev/null +++ b/sample-code/sqrt.cl @@ -0,0 +1,59 @@ +#!/usr/bin/env conlang-run +//! Square root approximation, and example applications + +/// A really small nonzero number +const EPSILON: f64 // = 8.854... * 10^-12 + = 88541878188 as f64 + / 10000000000 as f64 + / 1000000000000 as f64; + +/// Calcuates the absolute value of a number +fn f64_abs(n: f64) -> f64 { + let n = n as f64 + if n < (0 as f64) { -n } else { n } +} + +/// Square root approximation using Newton's method +fn sqrt(n: f64) -> f64 { + let n = n as f64 + if n < 0 as f64 { + return 0 as f64 / 0 as f64 // TODO: NaN constant + } + if n == 0 as f64 { + return 0 as f64 + } + + let z = n + loop { + let adj = (z * z - n) / (2 as f64 * z) + z -= adj + if adj.f64_abs() < EPSILON { + break z; + } + } +} + +/// Pythagorean theorem: a² + b² = c² +fn pythag(a: f64, b: f64) -> f64 { + sqrt(a * a + b * b) +} + +/// Quadratic formula: (-b ± √(b² - 4ac)) / 2a +fn quadratic(a: f64, b: f64, c: f64) -> (f64, f64) { + let a = a as f64; let b = b as f64; let c = c as f64; + ( + (-b + sqrt(b * b - 4 as f64 * a * c)) / 2 as f64 * a, + (-b - sqrt(b * b - 4 as f64 * a * c)) / 2 as f64 * a, + ) +} + +fn main() { + for i in 0..10 { + println("sqrt(",i,") ≅ ",sqrt(i)) + } + println("\nPythagorean Theorem") + println("Hypotenuse of ⊿(5, 12): ", pythag(5, 12)) + + println("\nQuadratic formula") + println("Roots of 10x² + 4x - 1: ", quadratic(10, 44, -1)) +} From 56e71d678270a6c331fb5a0f0c3933ec0ac70de3 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 13:16:27 -0500 Subject: [PATCH 4/9] cl-lexer: Add a hacky workaround for float support. It's disgusting, but better than nothing! --- compiler/cl-lexer/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/cl-lexer/src/lib.rs b/compiler/cl-lexer/src/lib.rs index e0e1328..2ed2d91 100644 --- a/compiler/cl-lexer/src/lib.rs +++ b/compiler/cl-lexer/src/lib.rs @@ -387,7 +387,19 @@ impl<'t> Lexer<'t> { while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) { value = value * B as u128 + self.digit::()? as u128; } - self.produce(Kind::Literal, value) + // TODO: find a better way to handle floats in the tokenizer + match self.peek() { + Ok('.') => { + let mut float = format!("{value}."); + self.consume()?; + while let Ok(true) = self.peek().as_ref().map(char::is_ascii_digit) { + float.push(self.iter.next().unwrap_or_default()); + } + let float = f64::from_str(&float).expect("must be parsable as float"); + self.produce(Kind::Literal, float) + } + _ => self.produce(Kind::Literal, value), + } } fn digit(&mut self) -> LResult { let digit = self.peek()?; From 5deb585054e9fedfa6926fe79fc0522fbe0345b6 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 13:20:19 -0500 Subject: [PATCH 5/9] cl-ast: Add float support - Smuggle floats as integers to maintain `eq` - This is bad, but not terrible for spec-compliant floats. Might have issues with NaN. cl_parser: Smuggle floats cl_interpret: unpack smuggled floats in float literal node --- compiler/cl-ast/src/ast.rs | 1 + compiler/cl-ast/src/ast_impl.rs | 1 + compiler/cl-ast/src/ast_visitor/fold.rs | 4 ++++ compiler/cl-ast/src/ast_visitor/visit.rs | 2 ++ compiler/cl-interpret/src/interpret.rs | 2 +- compiler/cl-parser/src/parser.rs | 2 +- 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/cl-ast/src/ast.rs b/compiler/cl-ast/src/ast.rs index b7275d0..d07c9cd 100644 --- a/compiler/cl-ast/src/ast.rs +++ b/compiler/cl-ast/src/ast.rs @@ -36,6 +36,7 @@ pub enum Literal { Bool(bool), Char(char), Int(u128), + Float(u64), String(String), } diff --git a/compiler/cl-ast/src/ast_impl.rs b/compiler/cl-ast/src/ast_impl.rs index 1e717bd..679a0bf 100644 --- a/compiler/cl-ast/src/ast_impl.rs +++ b/compiler/cl-ast/src/ast_impl.rs @@ -50,6 +50,7 @@ mod display { Literal::Bool(v) => v.fmt(f), Literal::Char(v) => write!(f, "'{}'", v.escape_debug()), Literal::Int(v) => v.fmt(f), + Literal::Float(v) => f64::from_bits(*v).fmt(f), Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()), } } diff --git a/compiler/cl-ast/src/ast_visitor/fold.rs b/compiler/cl-ast/src/ast_visitor/fold.rs index ce16d7d..1252a4f 100644 --- a/compiler/cl-ast/src/ast_visitor/fold.rs +++ b/compiler/cl-ast/src/ast_visitor/fold.rs @@ -37,6 +37,9 @@ pub trait Fold { fn fold_int(&mut self, i: u128) -> u128 { i } + fn fold_smuggled_float(&mut self, f: u64) -> u64 { + f + } fn fold_string(&mut self, s: String) -> String { s } @@ -384,6 +387,7 @@ pub fn or_fold_literal(folder: &mut F, lit: Literal) -> Litera Literal::Bool(b) => Literal::Bool(folder.fold_bool(b)), Literal::Char(c) => Literal::Char(folder.fold_char(c)), Literal::Int(i) => Literal::Int(folder.fold_int(i)), + Literal::Float(f) => Literal::Float(folder.fold_smuggled_float(f)), Literal::String(s) => Literal::String(folder.fold_string(s)), } } diff --git a/compiler/cl-ast/src/ast_visitor/visit.rs b/compiler/cl-ast/src/ast_visitor/visit.rs index 59754a8..df4b74d 100644 --- a/compiler/cl-ast/src/ast_visitor/visit.rs +++ b/compiler/cl-ast/src/ast_visitor/visit.rs @@ -23,6 +23,7 @@ pub trait Visit<'a>: Sized { fn visit_bool(&mut self, _b: &'a bool) {} fn visit_char(&mut self, _c: &'a char) {} fn visit_int(&mut self, _i: &'a u128) {} + fn visit_smuggled_float(&mut self, _f: &'a u64) {} fn visit_string(&mut self, _s: &'a str) {} fn visit_file(&mut self, f: &'a File) { let File { items } = f; @@ -339,6 +340,7 @@ pub fn or_visit_literal<'a, V: Visit<'a>>(visitor: &mut V, l: &'a Literal) { Literal::Bool(b) => visitor.visit_bool(b), Literal::Char(c) => visitor.visit_char(c), Literal::Int(i) => visitor.visit_int(i), + Literal::Float(f) => visitor.visit_smuggled_float(f), Literal::String(s) => visitor.visit_string(s), } } diff --git a/compiler/cl-interpret/src/interpret.rs b/compiler/cl-interpret/src/interpret.rs index 6cb3c7d..52977cc 100644 --- a/compiler/cl-interpret/src/interpret.rs +++ b/compiler/cl-interpret/src/interpret.rs @@ -453,7 +453,7 @@ impl Interpret for Literal { Literal::String(value) => ConValue::from(value.as_str()), Literal::Char(value) => ConValue::Char(*value), Literal::Bool(value) => ConValue::Bool(*value), - // Literal::Float(value) => todo!("Float values in interpreter: {value:?}"), + Literal::Float(value) => ConValue::Float(f64::from_bits(*value)), Literal::Int(value) => ConValue::Int(*value as _), }) } diff --git a/compiler/cl-parser/src/parser.rs b/compiler/cl-parser/src/parser.rs index afe86fe..939f472 100644 --- a/compiler/cl-parser/src/parser.rs +++ b/compiler/cl-parser/src/parser.rs @@ -245,7 +245,7 @@ impl Parse<'_> for Literal { TokenData::String(v) => Literal::String(v), TokenData::Character(v) => Literal::Char(v), TokenData::Integer(v) => Literal::Int(v), - TokenData::Float(v) => todo!("Literal::Float({v})"), + TokenData::Float(v) => Literal::Float(v.to_bits()), _ => panic!("Expected token data for {ty:?}"), }) } From 94be5d787f4865add651e05339273732d4f5d65b Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 14:00:22 -0500 Subject: [PATCH 6/9] cl-ast: always pretty-print decimal for floats --- compiler/cl-ast/src/ast_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/cl-ast/src/ast_impl.rs b/compiler/cl-ast/src/ast_impl.rs index 679a0bf..24f7aa6 100644 --- a/compiler/cl-ast/src/ast_impl.rs +++ b/compiler/cl-ast/src/ast_impl.rs @@ -50,7 +50,7 @@ mod display { Literal::Bool(v) => v.fmt(f), Literal::Char(v) => write!(f, "'{}'", v.escape_debug()), Literal::Int(v) => v.fmt(f), - Literal::Float(v) => f64::from_bits(*v).fmt(f), + Literal::Float(v) => write!(f, "{:?}", f64::from_bits(*v)), Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()), } } From f4fe07a08be7e94f848f82d2cd250166a0187708 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 14:02:02 -0500 Subject: [PATCH 7/9] cl-lexer: Hack around ambiguity between 1.0 and 1..0 This requires more than one token lookahead, but is already part of a hack itself, so... /shrug --- compiler/cl-lexer/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/cl-lexer/src/lib.rs b/compiler/cl-lexer/src/lib.rs index 2ed2d91..7e0c867 100644 --- a/compiler/cl-lexer/src/lib.rs +++ b/compiler/cl-lexer/src/lib.rs @@ -378,18 +378,22 @@ impl<'t> Lexer<'t> { Ok('d') => self.consume()?.digits::<10>(), Ok('o') => self.consume()?.digits::<8>(), Ok('b') => self.consume()?.digits::<2>(), - Ok('0'..='9') => self.digits::<10>(), + Ok('0'..='9' | '.') => self.digits::<10>(), _ => self.produce(Kind::Literal, 0), } } fn digits(&mut self) -> LResult { - let mut value = self.digit::()? as u128; + let mut value = 0; while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) { value = value * B as u128 + self.digit::()? as u128; } // TODO: find a better way to handle floats in the tokenizer match self.peek() { Ok('.') => { + // FIXME: hack: 0.. is not [0.0, '.'] + if let Ok('.') = self.clone().consume()?.next() { + return self.produce(Kind::Literal, value); + } let mut float = format!("{value}."); self.consume()?; while let Ok(true) = self.peek().as_ref().map(char::is_ascii_digit) { From b9f49949302cbfb1c3684a9334052b972dc332e4 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 14:02:58 -0500 Subject: [PATCH 8/9] sample-code: update sqrt.cl to use new float syntax --- sample-code/sqrt.cl | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/sample-code/sqrt.cl b/sample-code/sqrt.cl index 7e9672f..d4d5a5a 100755 --- a/sample-code/sqrt.cl +++ b/sample-code/sqrt.cl @@ -2,30 +2,27 @@ //! Square root approximation, and example applications /// A really small nonzero number -const EPSILON: f64 // = 8.854... * 10^-12 - = 88541878188 as f64 - / 10000000000 as f64 - / 1000000000000 as f64; +const EPSILON: f64 = 8.8541878188 / 1000000000000.0; /// Calcuates the absolute value of a number fn f64_abs(n: f64) -> f64 { let n = n as f64 - if n < (0 as f64) { -n } else { n } + if n < (0.0) { -n } else { n } } /// Square root approximation using Newton's method fn sqrt(n: f64) -> f64 { let n = n as f64 - if n < 0 as f64 { - return 0 as f64 / 0 as f64 // TODO: NaN constant + if n < 0.0 { + return 0.0 / 0.0 // TODO: NaN constant } - if n == 0 as f64 { - return 0 as f64 + if n == 0.0 { + return 0.0 } let z = n loop { - let adj = (z * z - n) / (2 as f64 * z) + let adj = (z * z - n) / (2.0 * z) z -= adj if adj.f64_abs() < EPSILON { break z; @@ -42,18 +39,18 @@ fn pythag(a: f64, b: f64) -> f64 { fn quadratic(a: f64, b: f64, c: f64) -> (f64, f64) { let a = a as f64; let b = b as f64; let c = c as f64; ( - (-b + sqrt(b * b - 4 as f64 * a * c)) / 2 as f64 * a, - (-b - sqrt(b * b - 4 as f64 * a * c)) / 2 as f64 * a, + (-b + sqrt(b * b - 4.0 * a * c)) / 2.0 * a, + (-b - sqrt(b * b - 4.0 * a * c)) / 2.0 * a, ) } fn main() { for i in 0..10 { - println("sqrt(",i,") ≅ ",sqrt(i)) + println("sqrt(",i,") ≅ ",sqrt(i as f64)) } println("\nPythagorean Theorem") - println("Hypotenuse of ⊿(5, 12): ", pythag(5, 12)) + println("Hypotenuse of ⊿(5, 12): ", pythag(5.0, 12.0)) println("\nQuadratic formula") - println("Roots of 10x² + 4x - 1: ", quadratic(10, 44, -1)) + println("Roots of 10x² + 4x - 1: ", quadratic(10.0, 44.0, -1.0)) } From 96be5aba6caed16dd490652b49c6f4307b32e226 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 19 Sep 2024 14:08:50 -0500 Subject: [PATCH 9/9] repline: Add a code sample demonstrating the use of prebaked::read_and --- repline/examples/repl_float.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 repline/examples/repl_float.rs diff --git a/repline/examples/repl_float.rs b/repline/examples/repl_float.rs new file mode 100644 index 0000000..c72ea89 --- /dev/null +++ b/repline/examples/repl_float.rs @@ -0,0 +1,17 @@ +//! Demonstrates the use of [read_and()]: +//! +//! The provided closure: +//! 1. Takes a line of input (a [String]) +//! 2. Performs some calculation (using [FromStr]) +//! 3. Returns a [Result] containing a [Response] or an [Err] + +use repline::{prebaked::read_and, Response}; +use std::{error::Error, str::FromStr}; + +fn main() -> Result<(), Box> { + read_and("\x1b[33m", " >", " ?>", |line| { + println!("-> {:?}", f64::from_str(line.trim())?); + Ok(Response::Accept) + })?; + Ok(()) +}