Merge pull request 'Basic floating point support (WIP)' (#18) from floats into main

Reviewed-on: #18
This commit is contained in:
John 2024-09-19 19:27:51 +00:00
commit ae11d87d68
12 changed files with 121 additions and 8 deletions

View File

@ -36,6 +36,7 @@ pub enum Literal {
Bool(bool), Bool(bool),
Char(char), Char(char),
Int(u128), Int(u128),
Float(u64),
String(String), String(String),
} }

View File

@ -50,6 +50,7 @@ mod display {
Literal::Bool(v) => v.fmt(f), Literal::Bool(v) => v.fmt(f),
Literal::Char(v) => write!(f, "'{}'", v.escape_debug()), Literal::Char(v) => write!(f, "'{}'", v.escape_debug()),
Literal::Int(v) => v.fmt(f), Literal::Int(v) => v.fmt(f),
Literal::Float(v) => write!(f, "{:?}", f64::from_bits(*v)),
Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()), Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()),
} }
} }

View File

@ -37,6 +37,9 @@ pub trait Fold {
fn fold_int(&mut self, i: u128) -> u128 { fn fold_int(&mut self, i: u128) -> u128 {
i i
} }
fn fold_smuggled_float(&mut self, f: u64) -> u64 {
f
}
fn fold_string(&mut self, s: String) -> String { fn fold_string(&mut self, s: String) -> String {
s s
} }
@ -384,6 +387,7 @@ pub fn or_fold_literal<F: Fold + ?Sized>(folder: &mut F, lit: Literal) -> Litera
Literal::Bool(b) => Literal::Bool(folder.fold_bool(b)), Literal::Bool(b) => Literal::Bool(folder.fold_bool(b)),
Literal::Char(c) => Literal::Char(folder.fold_char(c)), Literal::Char(c) => Literal::Char(folder.fold_char(c)),
Literal::Int(i) => Literal::Int(folder.fold_int(i)), 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)), Literal::String(s) => Literal::String(folder.fold_string(s)),
} }
} }

View File

@ -23,6 +23,7 @@ pub trait Visit<'a>: Sized {
fn visit_bool(&mut self, _b: &'a bool) {} fn visit_bool(&mut self, _b: &'a bool) {}
fn visit_char(&mut self, _c: &'a char) {} fn visit_char(&mut self, _c: &'a char) {}
fn visit_int(&mut self, _i: &'a u128) {} 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_string(&mut self, _s: &'a str) {}
fn visit_file(&mut self, f: &'a File) { fn visit_file(&mut self, f: &'a File) {
let File { items } = f; 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::Bool(b) => visitor.visit_bool(b),
Literal::Char(c) => visitor.visit_char(c), Literal::Char(c) => visitor.visit_char(c),
Literal::Int(i) => visitor.visit_int(i), Literal::Int(i) => visitor.visit_int(i),
Literal::Float(f) => visitor.visit_smuggled_float(f),
Literal::String(s) => visitor.visit_string(s), Literal::String(s) => visitor.visit_string(s),
} }
} }

View File

@ -223,6 +223,7 @@ builtins! {
Ok(match tail { Ok(match tail {
ConValue::Empty => ConValue::Empty, ConValue::Empty => ConValue::Empty,
ConValue::Int(v) => ConValue::Int(v.wrapping_neg()), ConValue::Int(v) => ConValue::Int(v.wrapping_neg()),
ConValue::Float(v) => ConValue::Float(-v),
_ => Err(Error::TypeError)?, _ => Err(Error::TypeError)?,
}) })
} }

View File

@ -20,6 +20,8 @@ pub enum ConValue {
Empty, Empty,
/// An integer /// An integer
Int(Integer), Int(Integer),
/// A floating point number
Float(f64),
/// A boolean /// A boolean
Bool(bool), Bool(bool),
/// A unicode character /// A unicode character
@ -124,6 +126,7 @@ macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$(
match (self, other) { match (self, other) {
(Self::Empty, Self::Empty) => Ok(Self::Bool($empty)), (Self::Empty, Self::Empty) => Ok(Self::Bool($empty)),
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)), (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::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
(Self::Char(a), Self::Char(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)), (Self::String(a), Self::String(b)) => Ok(Self::Bool(&**a $op &**b)),
@ -150,6 +153,7 @@ impl From<&Sym> for ConValue {
} }
from! { from! {
Integer => ConValue::Int, Integer => ConValue::Int,
f64 => ConValue::Float,
bool => ConValue::Bool, bool => ConValue::Bool,
char => ConValue::Char, char => ConValue::Char,
Sym => ConValue::String, Sym => ConValue::String,
@ -189,7 +193,8 @@ ops! {
Add: add = [ Add: add = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_add(b)), (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::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
(ConValue::Char(a), ConValue::Char(b)) => { (ConValue::Char(a), ConValue::Char(b)) => {
ConValue::String([a, b].into_iter().collect::<String>().into()) ConValue::String([a, b].into_iter().collect::<String>().into())
@ -219,18 +224,21 @@ ops! {
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| { (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_div(b).unwrap_or_else(|| {
eprintln!("Warning: Divide by zero in {a} / {b}"); a eprintln!("Warning: Divide by zero in {a} / {b}"); a
})), })),
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a / b),
_ => Err(Error::TypeError)? _ => Err(Error::TypeError)?
] ]
Mul: mul = [ Mul: mul = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)), (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)? _ => Err(Error::TypeError)?
] ]
Rem: rem = [ Rem: rem = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| { (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)? _ => Err(Error::TypeError)?
] ]
Shl: shl = [ Shl: shl = [
@ -246,6 +254,7 @@ ops! {
Sub: sub = [ Sub: sub = [
(ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Empty, ConValue::Empty) => ConValue::Empty,
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)), (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)? _ => Err(Error::TypeError)?
] ]
} }
@ -254,6 +263,7 @@ impl std::fmt::Display for ConValue {
match self { match self {
ConValue::Empty => "Empty".fmt(f), ConValue::Empty => "Empty".fmt(f),
ConValue::Int(v) => v.fmt(f), ConValue::Int(v) => v.fmt(f),
ConValue::Float(v) => v.fmt(f),
ConValue::Bool(v) => v.fmt(f), ConValue::Bool(v) => v.fmt(f),
ConValue::Char(v) => v.fmt(f), ConValue::Char(v) => v.fmt(f),
ConValue::String(v) => v.fmt(f), ConValue::String(v) => v.fmt(f),

View File

@ -358,6 +358,9 @@ fn cast(value: ConValue, ty: Sym) -> IResult<ConValue> {
ConValue::Bool(b) => b as _, ConValue::Bool(b) => b as _,
ConValue::Char(c) => c as _, ConValue::Char(c) => c as _,
ConValue::Ref(v) => return cast((*v).clone(), ty), 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)?, _ => Err(Error::TypeError)?,
}; };
Ok(match &*ty { Ok(match &*ty {
@ -369,6 +372,8 @@ fn cast(value: ConValue, ty: Sym) -> IResult<ConValue> {
"i32" => ConValue::Int(value as i32 as _), "i32" => ConValue::Int(value as i32 as _),
"u64" => ConValue::Int(value), "u64" => ConValue::Int(value),
"i64" => 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}')), "char" => ConValue::Char(char::from_u32(value as _).unwrap_or('\u{fffd}')),
"bool" => ConValue::Bool(value < 0), "bool" => ConValue::Bool(value < 0),
_ => Err(Error::NotDefined(ty))?, _ => Err(Error::NotDefined(ty))?,
@ -448,7 +453,7 @@ impl Interpret for Literal {
Literal::String(value) => ConValue::from(value.as_str()), Literal::String(value) => ConValue::from(value.as_str()),
Literal::Char(value) => ConValue::Char(*value), Literal::Char(value) => ConValue::Char(*value),
Literal::Bool(value) => ConValue::Bool(*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 _), Literal::Int(value) => ConValue::Int(*value as _),
}) })
} }

View File

@ -378,16 +378,32 @@ impl<'t> Lexer<'t> {
Ok('d') => self.consume()?.digits::<10>(), Ok('d') => self.consume()?.digits::<10>(),
Ok('o') => self.consume()?.digits::<8>(), Ok('o') => self.consume()?.digits::<8>(),
Ok('b') => self.consume()?.digits::<2>(), Ok('b') => self.consume()?.digits::<2>(),
Ok('0'..='9') => self.digits::<10>(), Ok('0'..='9' | '.') => self.digits::<10>(),
_ => self.produce(Kind::Literal, 0), _ => self.produce(Kind::Literal, 0),
} }
} }
fn digits<const B: u32>(&mut self) -> LResult<Token> { fn digits<const B: u32>(&mut self) -> LResult<Token> {
let mut value = self.digit::<B>()? as u128; let mut value = 0;
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) { while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
value = value * B as u128 + self.digit::<B>()? as u128; value = value * B as u128 + self.digit::<B>()? 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<const B: u32>(&mut self) -> LResult<u32> { fn digit<const B: u32>(&mut self) -> LResult<u32> {
let digit = self.peek()?; let digit = self.peek()?;

View File

@ -245,7 +245,7 @@ impl Parse<'_> for Literal {
TokenData::String(v) => Literal::String(v), TokenData::String(v) => Literal::String(v),
TokenData::Character(v) => Literal::Char(v), TokenData::Character(v) => Literal::Char(v),
TokenData::Integer(v) => Literal::Int(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:?}"), _ => panic!("Expected token data for {ty:?}"),
}) })
} }

View File

@ -247,9 +247,9 @@ fn structor_body(p: &mut Parser, to: Path) -> PResult<Structor> {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Precedence { pub enum Precedence {
Assign, Assign,
Logic,
Compare, Compare,
Range, Range,
Logic,
Bitwise, Bitwise,
Shift, Shift,
Factor, Factor,

View File

@ -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<dyn Error>> {
read_and("\x1b[33m", " >", " ?>", |line| {
println!("-> {:?}", f64::from_str(line.trim())?);
Ok(Response::Accept)
})?;
Ok(())
}

56
sample-code/sqrt.cl Executable file
View File

@ -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))
}