diff --git a/src/backend/cranelift.rs b/src/backend/cranelift.rs index 3406757..dd300ad 100644 --- a/src/backend/cranelift.rs +++ b/src/backend/cranelift.rs @@ -158,6 +158,8 @@ impl CraneliftBackend { Ty::I16 | Ty::U16 => types::I16, Ty::I32 | Ty::U32 => types::I32, Ty::I64 | Ty::U64 => types::I64, + Ty::F32 => types::F32, + Ty::F64 => types::F64, Ty::Bool => types::I8, // Booleans are represented as 8-bit integers _ => unimplemented!("Unsupported type for Cranelift lowering: {:?}", ty), } @@ -221,6 +223,7 @@ impl<'a> FunctionTranslator<'a> { match op { Operand::Copy(local_id) => self.locals[local_id.0].ty.clone(), Operand::Constant(ConstantValue::Integer(_, ty)) => ty.clone(), + Operand::Constant(ConstantValue::Float(_, ty)) => ty.clone(), Operand::Constant(ConstantValue::Boolean(_)) => Ty::Bool, } } @@ -235,6 +238,11 @@ impl<'a> FunctionTranslator<'a> { let cl_ty = CraneliftBackend::lower_type(ty); self.builder.ins().iconst(cl_ty, *val as i64) } + Operand::Constant(ConstantValue::Float(val, ty)) => match ty { + Ty::F32 => self.builder.ins().f32const(*val as f32), + Ty::F64 => self.builder.ins().f64const(*val), + _ => unreachable!(), + }, Operand::Constant(ConstantValue::Boolean(val)) => self .builder .ins() @@ -247,10 +255,17 @@ impl<'a> FunctionTranslator<'a> { Rvalue::Use(op) => self.translate_operand(op), Rvalue::UnaryOp(op, inner) => { let inner_val = self.translate_operand(inner); + let ty = self.get_operand_type(inner); + match op { - UnaryOp::Neg => self.builder.ins().ineg(inner_val), + UnaryOp::Neg => { + if ty.is_float() { + self.builder.ins().fneg(inner_val) + } else { + self.builder.ins().ineg(inner_val) + } + } UnaryOp::Not => { - let ty = self.get_operand_type(inner); let cl_ty = CraneliftBackend::lower_type(&ty); let zero = self.builder.ins().iconst(cl_ty, 0); self.builder @@ -265,66 +280,137 @@ impl<'a> FunctionTranslator<'a> { let ty = self.get_operand_type(lhs); let is_signed = matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64); + let is_float = ty.is_float(); match op { - BinaryOp::Add => self.builder.ins().iadd(lhs_val, rhs_val), - BinaryOp::Sub => self.builder.ins().isub(lhs_val, rhs_val), - BinaryOp::Mul => self.builder.ins().imul(lhs_val, rhs_val), + BinaryOp::Add => { + if is_float { + self.builder.ins().fadd(lhs_val, rhs_val) + } else { + self.builder.ins().iadd(lhs_val, rhs_val) + } + } + BinaryOp::Sub => { + if is_float { + self.builder.ins().fsub(lhs_val, rhs_val) + } else { + self.builder.ins().isub(lhs_val, rhs_val) + } + } + BinaryOp::Mul => { + if is_float { + self.builder.ins().fmul(lhs_val, rhs_val) + } else { + self.builder.ins().imul(lhs_val, rhs_val) + } + } BinaryOp::Div => { - if is_signed { + if is_float { + self.builder.ins().fdiv(lhs_val, rhs_val) + } else if is_signed { self.builder.ins().sdiv(lhs_val, rhs_val) } else { self.builder.ins().udiv(lhs_val, rhs_val) } } BinaryOp::Rem => { - if is_signed { + if is_float { + unimplemented!("float remainder is not natively supported in Cranelift") + } else if is_signed { self.builder.ins().srem(lhs_val, rhs_val) } else { self.builder.ins().urem(lhs_val, rhs_val) } } BinaryOp::Eq => { - self.builder - .ins() - .icmp(ir::condcodes::IntCC::Equal, lhs_val, rhs_val) + if is_float { + self.builder + .ins() + .fcmp(ir::condcodes::FloatCC::Equal, lhs_val, rhs_val) + } else { + self.builder + .ins() + .icmp(ir::condcodes::IntCC::Equal, lhs_val, rhs_val) + } } BinaryOp::Neq => { - self.builder - .ins() - .icmp(ir::condcodes::IntCC::NotEqual, lhs_val, rhs_val) + if is_float { + self.builder.ins().fcmp( + ir::condcodes::FloatCC::NotEqual, + lhs_val, + rhs_val, + ) + } else { + self.builder.ins().icmp( + ir::condcodes::IntCC::NotEqual, + lhs_val, + rhs_val, + ) + } } BinaryOp::Lt => { - let cc = if is_signed { - ir::condcodes::IntCC::SignedLessThan + if is_float { + self.builder.ins().fcmp( + ir::condcodes::FloatCC::LessThan, + lhs_val, + rhs_val, + ) } else { - ir::condcodes::IntCC::UnsignedLessThan - }; - self.builder.ins().icmp(cc, lhs_val, rhs_val) + let cc = if is_signed { + ir::condcodes::IntCC::SignedLessThan + } else { + ir::condcodes::IntCC::UnsignedLessThan + }; + self.builder.ins().icmp(cc, lhs_val, rhs_val) + } } BinaryOp::Le => { - let cc = if is_signed { - ir::condcodes::IntCC::SignedLessThanOrEqual + if is_float { + self.builder.ins().fcmp( + ir::condcodes::FloatCC::LessThanOrEqual, + lhs_val, + rhs_val, + ) } else { - ir::condcodes::IntCC::UnsignedLessThanOrEqual - }; - self.builder.ins().icmp(cc, lhs_val, rhs_val) + let cc = if is_signed { + ir::condcodes::IntCC::SignedLessThanOrEqual + } else { + ir::condcodes::IntCC::UnsignedLessThanOrEqual + }; + self.builder.ins().icmp(cc, lhs_val, rhs_val) + } } BinaryOp::Gt => { - let cc = if is_signed { - ir::condcodes::IntCC::SignedGreaterThan + if is_float { + self.builder.ins().fcmp( + ir::condcodes::FloatCC::GreaterThan, + lhs_val, + rhs_val, + ) } else { - ir::condcodes::IntCC::UnsignedGreaterThan - }; - self.builder.ins().icmp(cc, lhs_val, rhs_val) + let cc = if is_signed { + ir::condcodes::IntCC::SignedGreaterThan + } else { + ir::condcodes::IntCC::UnsignedGreaterThan + }; + self.builder.ins().icmp(cc, lhs_val, rhs_val) + } } BinaryOp::Ge => { - let cc = if is_signed { - ir::condcodes::IntCC::SignedGreaterThanOrEqual + if is_float { + self.builder.ins().fcmp( + ir::condcodes::FloatCC::GreaterThanOrEqual, + lhs_val, + rhs_val, + ) } else { - ir::condcodes::IntCC::UnsignedGreaterThanOrEqual - }; - self.builder.ins().icmp(cc, lhs_val, rhs_val) + let cc = if is_signed { + ir::condcodes::IntCC::SignedGreaterThanOrEqual + } else { + ir::condcodes::IntCC::UnsignedGreaterThanOrEqual + }; + self.builder.ins().icmp(cc, lhs_val, rhs_val) + } } } } diff --git a/src/frontend/ast.rs b/src/frontend/ast.rs index 112982a..2363d48 100644 --- a/src/frontend/ast.rs +++ b/src/frontend/ast.rs @@ -34,18 +34,18 @@ pub type TypedStmtKind = StmtKind; pub type TypedExpr = Expr; pub type TypedExprKind = ExprKind; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub struct Module { pub decls: Vec>, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub struct Decl { pub kind: DeclKind

, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub enum DeclKind { Function { name: String, @@ -79,16 +79,18 @@ pub enum TypeKind { U16, U32, U64, + F32, + F64, Bool, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub struct Stmt { pub kind: StmtKind

, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub enum StmtKind { Compound { inner: Vec>, @@ -118,14 +120,14 @@ pub enum StmtKind { Continue, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub struct Expr { pub kind: ExprKind

, pub ty: P::ExprType, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub enum ExprKind { Identifier { name: String, @@ -133,6 +135,9 @@ pub enum ExprKind { Integer { value: u64, }, + Float { + value: f64, + }, Boolean { value: bool, }, diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index b680abf..d595df0 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -80,6 +80,8 @@ impl<'src> Lexer<'src> { "u16" => TokenKind::U16, "u32" => TokenKind::U32, "u64" => TokenKind::U64, + "f32" => TokenKind::F32, + "f64" => TokenKind::F64, "bool" => TokenKind::Bool, "true" | "false" => TokenKind::BooleanLit, @@ -108,6 +110,43 @@ impl<'src> Lexer<'src> { self.advance_while(|ch| ch.is_digit(radix)); + // Handle floating point literals (fraction and optional exponent) + if radix == 10 { + let mut is_float = false; + + if self.peek() == Some('.') { + let mut chars = self.source[self.cursor..].chars(); + chars.next(); // consume '.' + if chars.next().is_some_and(|ch| ch.is_ascii_digit()) { + self.advance(); // consume '.' + self.advance_while(|ch| ch.is_ascii_digit()); + is_float = true; + } + } + + if matches!(self.peek(), Some('e' | 'E')) { + let mut chars = self.source[self.cursor..].chars(); + chars.next(); // consume 'e' + let next_char = chars.next(); + + if next_char.is_some_and(|ch| ch.is_ascii_digit()) + || (matches!(next_char, Some('+' | '-')) + && chars.next().is_some_and(|ch| ch.is_ascii_digit())) + { + self.advance(); // consume 'e' + if matches!(self.peek(), Some('+' | '-')) { + self.advance(); // consume sign + } + self.advance_while(|ch| ch.is_ascii_digit()); + is_float = true; + } + } + + if is_float { + return TokenKind::FloatLit; + } + } + TokenKind::IntegerLit } @@ -240,6 +279,20 @@ mod test { ) } + #[test] + fn float_literals() { + assert_eq!( + tokenize("3.14 0.001 42.0 1e5 2.5e-3"), + vec![ + Token::new(TokenKind::FloatLit, "3.14", Span::new(0, 4)), + Token::new(TokenKind::FloatLit, "0.001", Span::new(5, 10)), + Token::new(TokenKind::FloatLit, "42.0", Span::new(11, 15)), + Token::new(TokenKind::FloatLit, "1e5", Span::new(16, 19)), + Token::new(TokenKind::FloatLit, "2.5e-3", Span::new(20, 26)), + ] + ); + } + #[test] fn boolean_literals() { assert_eq!( @@ -254,7 +307,7 @@ mod test { #[test] fn types() { assert_eq!( - tokenize("i8 i16 i32 i64 u8 u16 u32 u64 bool"), + tokenize("i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 bool"), vec![ Token::new(TokenKind::I8, "i8", Span::new(0, 2)), Token::new(TokenKind::I16, "i16", Span::new(3, 6)), @@ -264,7 +317,9 @@ mod test { Token::new(TokenKind::U16, "u16", Span::new(18, 21)), Token::new(TokenKind::U32, "u32", Span::new(22, 25)), Token::new(TokenKind::U64, "u64", Span::new(26, 29)), - Token::new(TokenKind::Bool, "bool", Span::new(30, 34)), + Token::new(TokenKind::F32, "f32", Span::new(30, 33)), + Token::new(TokenKind::F64, "f64", Span::new(34, 37)), + Token::new(TokenKind::Bool, "bool", Span::new(38, 42)), ] ) } diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index ef1c6f6..4b58f09 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -223,6 +223,7 @@ impl<'src> Parser<'src> { /// ```ebnf /// type = "i8" | "i16" | "i32" | "i64" /// | "u8" | "u16" | "u32" | "u64" + /// | "f32" | "f64" /// | "bool" ; /// ``` pub fn parse_type(&mut self) -> ParseResult { @@ -261,6 +262,14 @@ impl<'src> Parser<'src> { self.advance(); TypeKind::U64 } + TokenKind::F32 => { + self.advance(); + TypeKind::F32 + } + TokenKind::F64 => { + self.advance(); + TypeKind::F64 + } TokenKind::Bool => { self.advance(); TypeKind::Bool @@ -617,6 +626,17 @@ impl<'src> Parser<'src> { }) } + TokenKind::FloatLit => { + let token = self.advance().unwrap(); + let value = token.text.parse().unwrap(); + + Ok(Expr { + kind: ExprKind::Float { value }, + ty: (), + span: token.span, + }) + } + TokenKind::BooleanLit => { let token = self.advance().unwrap(); @@ -706,8 +726,8 @@ mod test { token::Span, }; - #[derive(Debug, PartialEq, Eq)] - enum TestResult { + #[derive(Debug, PartialEq)] + enum TestResult { Success(T), Recovered(T, Vec), Error(Vec), @@ -715,7 +735,7 @@ mod test { use TestResult::*; - fn parse<'src, T: Debug + Eq + PartialEq>( + fn parse<'src, T: Debug + PartialEq>( source: &'src str, method: impl Fn(&mut Parser<'src>) -> ParseResult, ) -> TestResult { @@ -775,6 +795,18 @@ mod test { ); } + #[test] + fn float_literals() { + assert_eq!( + parse("3.14;", Parser::parse_expr), + Success(Expr { + kind: ExprKind::Float { value: 3.14 }, + ty: (), + span: Span::new(0, 4) + }) + ); + } + #[test] fn boolean_literals() { assert_eq!( @@ -969,6 +1001,29 @@ mod test { ); } + #[test] + fn let_stmt_float() { + assert_eq!( + parse("let a: f32 = 3.14;", Parser::parse_stmt), + Success(Stmt { + kind: StmtKind::Let { + name: "a".to_string(), + name_span: Span::new(4, 5), + ty: Some(Type { + kind: TypeKind::F32, + span: Span::new(7, 10), + }), + initializer: Some(Expr { + kind: ExprKind::Float { value: 3.14 }, + ty: (), + span: Span::new(13, 17), + }), + }, + span: Span::new(0, 18) + }) + ); + } + #[test] fn while_stmt() { assert_eq!( diff --git a/src/frontend/sema.rs b/src/frontend/sema.rs index 73ad6c0..2acc75e 100644 --- a/src/frontend/sema.rs +++ b/src/frontend/sema.rs @@ -35,6 +35,8 @@ pub enum Ty { U16, U32, U64, + F32, + F64, Bool, Unit, Var(usize), @@ -49,6 +51,16 @@ impl Ty { Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64 | Ty::U8 | Ty::U16 | Ty::U32 | Ty::U64 ) } + + /// Returns `true` if the type is a fundamental floating point type. + pub fn is_float(&self) -> bool { + matches!(self, Ty::F32 | Ty::F64) + } + + /// Returns `true` if the type is numeric (integer or float). + pub fn is_numeric(&self) -> bool { + self.is_integer() || self.is_float() + } } impl From<&TypeKind> for Ty { @@ -62,6 +74,8 @@ impl From<&TypeKind> for Ty { TypeKind::U16 => Ty::U16, TypeKind::U32 => Ty::U32, TypeKind::U64 => Ty::U64, + TypeKind::F32 => Ty::F32, + TypeKind::F64 => Ty::F64, TypeKind::Bool => Ty::Bool, } } @@ -77,7 +91,8 @@ pub struct Sema { errors: Vec, deferred_unary_neg: Vec<(Span, Ty, Ty, Option)>, deferred_binary: Vec<(Span, Ty)>, - deferred_literals: Vec<(Span, Ty)>, + deferred_int_literals: Vec<(Span, Ty)>, + deferred_float_literals: Vec<(Span, Ty)>, loop_depth: usize, } @@ -91,7 +106,8 @@ impl Sema { errors: Vec::new(), deferred_unary_neg: Vec::new(), deferred_binary: Vec::new(), - deferred_literals: Vec::new(), + deferred_int_literals: Vec::new(), + deferred_float_literals: Vec::new(), loop_depth: 0, } } @@ -469,7 +485,7 @@ impl Sema { ExprKind::Integer { value } => { let ty = self.new_var(); - self.deferred_literals.push((expr.span, ty.clone())); + self.deferred_int_literals.push((expr.span, ty.clone())); TypedExpr { kind: TypedExprKind::Integer { value: *value }, @@ -478,6 +494,17 @@ impl Sema { } } + ExprKind::Float { value } => { + let ty = self.new_var(); + self.deferred_float_literals.push((expr.span, ty.clone())); + + TypedExpr { + kind: TypedExprKind::Float { value: *value }, + ty, + span: expr.span, + } + } + ExprKind::Boolean { value } => TypedExpr { kind: TypedExprKind::Boolean { value: *value }, ty: Ty::Bool, @@ -685,6 +712,7 @@ impl Sema { let kind = match expr.kind { TypedExprKind::Identifier { name } => TypedExprKind::Identifier { name }, TypedExprKind::Integer { value } => TypedExprKind::Integer { value }, + TypedExprKind::Float { value } => TypedExprKind::Float { value }, TypedExprKind::Boolean { value } => TypedExprKind::Boolean { value }, TypedExprKind::Unary { op, expr } => TypedExprKind::Unary { @@ -715,7 +743,7 @@ impl Sema { let result_resolved = self.apply_subst(&result_ty); let inner_final = if let Ty::Var(_) = inner_resolved { - if result_resolved.is_integer() { + if result_resolved.is_numeric() { let _ = self.unify(&inner_resolved, &result_resolved); result_resolved.clone() } else { @@ -733,7 +761,7 @@ impl Sema { // If the value is known at compile time, we can avoid promoting to a larger size // as long as the exact value fits within the signed equivalent of the same size. let promoted_ty = match inner_final { - Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64 => Ok(inner_final), + Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64 | Ty::F32 | Ty::F64 => Ok(inner_final), Ty::U8 => Ok(if known_value.is_some_and(|v| v <= i8::MAX as u64) { Ty::I8 @@ -762,7 +790,7 @@ impl Sema { Err("cannot promote u64 to a larger signed type") } } - _ => Err("unary minus only works on integer types"), + _ => Err("unary minus only works on numeric types"), }; match promoted_ty { @@ -788,15 +816,15 @@ impl Sema { resolved }; - if !final_ty.is_integer() { + if !final_ty.is_numeric() { self.errors.push(SemanticError::new( - "binary operators only work on integer types", + "binary operators only work on numeric types", span, )); } } - for (span, ty) in std::mem::take(&mut self.deferred_literals) { + for (span, ty) in std::mem::take(&mut self.deferred_int_literals) { let resolved = self.apply_subst(&ty); let final_ty = if let Ty::Var(_) = resolved { @@ -814,6 +842,23 @@ impl Sema { )); } } + + for (span, ty) in std::mem::take(&mut self.deferred_float_literals) { + let resolved = self.apply_subst(&ty); + + let final_ty = if let Ty::Var(_) = resolved { + let default = Ty::F32; + let _ = self.unify(&resolved, &default); + default + } else { + resolved + }; + + if !final_ty.is_float() { + self.errors + .push(SemanticError::new("expected float type for literal", span)); + } + } } } @@ -867,6 +912,48 @@ mod test { assert!(errors[0].message.contains("undeclared identifier `x`")); } + #[test] + fn valid_float_ops() { + let src = "fn test(a: f32, b: f32) -> f32 { return a + b - a * b / a; }"; + assert!(analyze(src).is_ok()); + } + + #[test] + fn valid_float_comparison() { + let src = "fn test(a: f64, b: f64) -> bool { return a <= b; }"; + assert!(analyze(src).is_ok()); + } + + #[test] + fn valid_float_unary_neg() { + let src = "fn test(a: f64) -> f64 { return -a; }"; + assert!(analyze(src).is_ok()); + } + + #[test] + fn float_type_mismatch() { + let src = "fn test(a: f32, b: i32) -> f32 { return a + b; }"; + let errors = analyze(src).unwrap_err(); + assert!(errors.iter().any(|e| e.message.contains("type mismatch"))); + } + + #[test] + fn float_literal_inference() { + let src = "fn test() { let a = 3.14; }"; + assert!(analyze(src).is_ok()); + } + + #[test] + fn float_literal_invalid_type() { + let src = "fn test() { let a: i32 = 3.14; }"; + let errors = analyze(src).unwrap_err(); + assert!( + errors + .iter() + .any(|e| e.message.contains("expected float type for literal")) + ); + } + #[test] fn binary_op_non_integer() { let src = "fn bad_bin() -> bool { return true + false; }"; @@ -874,7 +961,7 @@ mod test { assert!(errors.iter().any(|e| { e.message - .contains("binary operators only work on integer types") + .contains("binary operators only work on numeric types") })); } @@ -898,7 +985,7 @@ mod test { assert!(errors.iter().any(|e| { e.message - .contains("unary minus only works on integer types") + .contains("unary minus only works on numeric types") })); } @@ -921,16 +1008,6 @@ mod test { assert!(analyze(src).is_ok()); } - #[test] - fn invalid_comparison() { - let src = "fn test(a: bool, b: bool) -> bool { return a == b; }"; - let errors = analyze(src).unwrap_err(); - assert!(errors.iter().any(|e| { - e.message - .contains("binary operators only work on integer types") - })); - } - #[test] fn valid_if() { let src = "fn test() { if true { return; } }"; diff --git a/src/frontend/token.rs b/src/frontend/token.rs index 27c30c4..9a88c68 100644 --- a/src/frontend/token.rs +++ b/src/frontend/token.rs @@ -53,6 +53,7 @@ pub enum TokenKind { Identifier, IntegerLit, BooleanLit, + FloatLit, // Keywords Fn, @@ -73,6 +74,8 @@ pub enum TokenKind { U16, U32, U64, + F32, + F64, Bool, // Operators @@ -115,6 +118,7 @@ impl Display for TokenKind { TokenKind::Identifier => "an identifier", TokenKind::IntegerLit => "an integer", TokenKind::BooleanLit => "a boolean", + TokenKind::FloatLit => "a float", TokenKind::Fn => "`fn`", TokenKind::If => "`if`", TokenKind::Else => "`else`", @@ -131,6 +135,8 @@ impl Display for TokenKind { TokenKind::U16 => "`u16`", TokenKind::U32 => "`u32`", TokenKind::U64 => "`u64`", + TokenKind::F32 => "`f32`", + TokenKind::F64 => "`f64`", TokenKind::Bool => "`bool`", TokenKind::Plus => "`+`", TokenKind::Minus => "`-`", diff --git a/src/middle/builder.rs b/src/middle/builder.rs index ff74938..a2f6447 100644 --- a/src/middle/builder.rs +++ b/src/middle/builder.rs @@ -343,6 +343,9 @@ impl FuncBuilder { TypedExprKind::Integer { value } => { Operand::Constant(ConstantValue::Integer(*value, expr.ty.clone())) } + TypedExprKind::Float { value } => { + Operand::Constant(ConstantValue::Float(*value, expr.ty.clone())) + } TypedExprKind::Boolean { value } => Operand::Constant(ConstantValue::Boolean(*value)), TypedExprKind::Unary { op, expr: inner } => { let inner_op = self.lower_expr(inner); diff --git a/src/middle/fold.rs b/src/middle/fold.rs index 5a0c0d0..12cd094 100644 --- a/src/middle/fold.rs +++ b/src/middle/fold.rs @@ -94,6 +94,7 @@ fn evaluate_unary(op: UnaryOp, val: &ConstantValue) -> Option { (UnaryOp::Neg, ConstantValue::Integer(v, ty)) => { Some(ConstantValue::Integer(v.wrapping_neg(), ty.clone())) } + (UnaryOp::Neg, ConstantValue::Float(v, ty)) => Some(ConstantValue::Float(-v, ty.clone())), (UnaryOp::Not, ConstantValue::Boolean(b)) => Some(ConstantValue::Boolean(!b)), _ => None, } @@ -165,6 +166,20 @@ fn evaluate_binary( _ => None, }, + (ConstantValue::Float(l, ty), ConstantValue::Float(r, _)) => match op { + BinaryOp::Add => Some(ConstantValue::Float(l + r, ty.clone())), + BinaryOp::Sub => Some(ConstantValue::Float(l - r, ty.clone())), + BinaryOp::Mul => Some(ConstantValue::Float(l * r, ty.clone())), + BinaryOp::Div => Some(ConstantValue::Float(l / r, ty.clone())), + BinaryOp::Rem => Some(ConstantValue::Float(l % r, ty.clone())), + BinaryOp::Eq => Some(ConstantValue::Boolean(l == r)), + BinaryOp::Neq => Some(ConstantValue::Boolean(l != r)), + BinaryOp::Lt => Some(ConstantValue::Boolean(l < r)), + BinaryOp::Le => Some(ConstantValue::Boolean(l <= r)), + BinaryOp::Gt => Some(ConstantValue::Boolean(l > r)), + BinaryOp::Ge => Some(ConstantValue::Boolean(l >= r)), + }, + _ => None, } } diff --git a/src/middle/mir.rs b/src/middle/mir.rs index 3c3939f..fedfa62 100644 --- a/src/middle/mir.rs +++ b/src/middle/mir.rs @@ -72,6 +72,7 @@ pub enum Operand { #[derive(Debug, Clone)] pub enum ConstantValue { Integer(u64, Ty), + Float(f64, Ty), Boolean(bool), } diff --git a/tests/float_comparisons.test b/tests/float_comparisons.test new file mode 100644 index 0000000..fd905db --- /dev/null +++ b/tests/float_comparisons.test @@ -0,0 +1,24 @@ +[code] +fn is_less_f64(x: f64, y: f64) -> bool { + return x < y; +} + +fn is_greater_eq_f32(x: f32, y: f32) -> bool { + return x >= y; +} + +[harness] +#include + +extern bool is_less_f64(double x, double y); +extern bool is_greater_eq_f32(float x, float y); + +int main() { + if (!is_less_f64(3.14, 5.0)) return 1; + if (is_less_f64(5.0, 3.14)) return 2; + if (!is_greater_eq_f32(2.5f, 2.5f)) return 3; + return 0; +} + +[expected_return_code] +0 \ No newline at end of file diff --git a/tests/float_f32_arithmetic.test b/tests/float_f32_arithmetic.test new file mode 100644 index 0000000..d06c67d --- /dev/null +++ b/tests/float_f32_arithmetic.test @@ -0,0 +1,20 @@ +[code] +fn test_f32(x: f32, y: f32) -> f32 { + let z: f32 = 1.5; + // (4.0 + 2.0) * 1.5 - (4.0 / 2.0) = 6.0 * 1.5 - 2.0 = 9.0 - 2.0 = 7.0 + return (x + y) * z - (x / y); +} + +[harness] +extern float test_f32(float x, float y); + +int main() { + float result = test_f32(4.0f, 2.0f); + if (result == 7.0f) { + return 0; + } + return 1; +} + +[expected_return_code] +0 \ No newline at end of file diff --git a/tests/float_f64_arithmetic.test b/tests/float_f64_arithmetic.test new file mode 100644 index 0000000..adac6f5 --- /dev/null +++ b/tests/float_f64_arithmetic.test @@ -0,0 +1,20 @@ +[code] +fn test_f64(x: f64, y: f64) -> f64 { + let z: f64 = 2.5; + // (7.0 - 2.0) / 2.5 + (7.0 * 2.0) = 5.0 / 2.5 + 14.0 = 2.0 + 14.0 = 16.0 + return (x - y) / z + (x * y); +} + +[harness] +extern double test_f64(double x, double y); + +int main() { + double result = test_f64(7.0, 2.0); + if (result == 16.0) { + return 0; + } + return 1; +} + +[expected_return_code] +0 \ No newline at end of file diff --git a/tests/float_parsing.test b/tests/float_parsing.test new file mode 100644 index 0000000..953da89 --- /dev/null +++ b/tests/float_parsing.test @@ -0,0 +1,17 @@ +[code] +fn test_exponents() -> f64 { + let a: f64 = 1e3; + let b: f64 = 2.5e-1; + return a * b; // 1000.0 * 0.25 = 250.0 +} + +[harness] +extern double test_exponents(); + +int main() { + if (test_exponents() == 250.0) return 0; + return 1; +} + +[expected_return_code] +0 \ No newline at end of file diff --git a/tests/float_unary.test b/tests/float_unary.test new file mode 100644 index 0000000..3aeae81 --- /dev/null +++ b/tests/float_unary.test @@ -0,0 +1,21 @@ +[code] +fn negate_f32(x: f32) -> f32 { + return -x; +} + +fn negate_f64(x: f64) -> f64 { + return -x; +} + +[harness] +extern float negate_f32(float x); +extern double negate_f64(double x); + +int main() { + if (negate_f32(3.5f) != -3.5f) return 1; + if (negate_f64(-4.2) != 4.2) return 2; + return 0; +} + +[expected_return_code] +0 \ No newline at end of file