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..24f7aa6 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) => write!(f, "{:?}", f64::from_bits(*v)), 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/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..52977cc 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))?, @@ -448,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-lexer/src/lib.rs b/compiler/cl-lexer/src/lib.rs index e0e1328..7e0c867 100644 --- a/compiler/cl-lexer/src/lib.rs +++ b/compiler/cl-lexer/src/lib.rs @@ -378,16 +378,32 @@ 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; } - self.produce(Kind::Literal, value) + // 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) { + 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()?; 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:?}"), }) } 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, 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(()) +} diff --git a/sample-code/sqrt.cl b/sample-code/sqrt.cl new file mode 100755 index 0000000..d4d5a5a --- /dev/null +++ b/sample-code/sqrt.cl @@ -0,0 +1,56 @@ +#!/usr/bin/env conlang-run +//! Square root approximation, and example applications + +/// A really small nonzero number +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.0) { -n } else { n } +} + +/// Square root approximation using Newton's method +fn sqrt(n: f64) -> f64 { + let n = n as f64 + if n < 0.0 { + return 0.0 / 0.0 // TODO: NaN constant + } + if n == 0.0 { + return 0.0 + } + + let z = n + loop { + let adj = (z * z - n) / (2.0 * 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.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 as f64)) + } + println("\nPythagorean Theorem") + println("Hypotenuse of ⊿(5, 12): ", pythag(5.0, 12.0)) + + println("\nQuadratic formula") + println("Roots of 10x² + 4x - 1: ", quadratic(10.0, 44.0, -1.0)) +}