diff --git a/src/checker.rs b/src/checker.rs index 7699a5c..a0c95a2 100644 --- a/src/checker.rs +++ b/src/checker.rs @@ -210,6 +210,47 @@ impl Checker { self.constraints.push(c); } + fn check_int_literal_suffix( + &mut self, + id: ExprID, + value: i64, + suffix: IntLiteralSuffix, + arena: &ExprArena, + negative: bool, + ) -> TypeID { + let shown_value = if negative { + format!("-{value}") + } else { + value.to_string() + }; + + match suffix { + IntLiteralSuffix::I32 => { + let max = if negative { + -(i32::MIN as i64) + } else { + i32::MAX as i64 + }; + if value > max { + self.errors.push(TypeError { + location: arena.locs[id], + message: format!("integer literal {shown_value} is out of range for i32"), + }); + } + mk_type(Type::Int32) + } + IntLiteralSuffix::U32 => { + if value > u32::MAX as i64 { + self.errors.push(TypeError { + location: arena.locs[id], + message: format!("integer literal {value} is out of range for u32"), + }); + } + mk_type(Type::UInt32) + } + } + } + fn check_unop( &mut self, id: ExprID, @@ -218,10 +259,18 @@ impl Checker { arena: &ExprArena, decls: &DeclTable, ) -> TypeID { - let argt = self.check_expr(arg, arena, decls); - match op { Unop::Neg => { + // A leading `-` in literal position is considered by signed + // suffix range checks, so `-2147483648i32` is valid even + // though `2147483648i32` is not. + if let Expr::Int(n, Some(suffix @ IntLiteralSuffix::I32)) = &arena[arg] { + let ty = self.check_int_literal_suffix(arg, *n, *suffix, arena, true); + self.types[arg] = ty; + return ty; + } + + let argt = self.check_expr(arg, arena, decls); let ft = self.fresh(); let alts: Vec<_> = self .neg_overloads @@ -246,6 +295,7 @@ impl Checker { r } Unop::Not => { + let argt = self.check_expr(arg, arena, decls); self.eq( argt, mk_type(Type::Bool), @@ -372,7 +422,7 @@ impl Checker { fn check_expr(&mut self, id: ExprID, arena: &ExprArena, decls: &DeclTable) -> TypeID { let ty = match &arena[id] { Expr::True | Expr::False => mk_type(Type::Bool), - Expr::Int(_) => { + Expr::Int(_, None) => { let ty = self.fresh(); let alts = vec![ Alt { @@ -384,8 +434,12 @@ impl Checker { self.add_constraint(Constraint::Or(ty, alts, arena.locs[id], None)); ty } - Expr::UInt(_) => mk_type(Type::UInt32), - Expr::Real(_) => mk_type(Type::Float32), + Expr::Int(n, Some(suffix)) => { + self.check_int_literal_suffix(id, *n, *suffix, arena, false) + } + Expr::Real(_, None) => mk_type(Type::Float32), + Expr::Real(_, Some(FloatLiteralSuffix::F32)) => mk_type(Type::Float32), + Expr::Real(_, Some(FloatLiteralSuffix::F64)) => mk_type(Type::Float64), Expr::Char(_) => mk_type(Type::Int8), Expr::String(s) => { let int8 = mk_type(Type::Int8); @@ -801,8 +855,7 @@ impl Checker { // Extract constant size when available (e.g. [0; 5]). let array_size = match &arena[*size] { - Expr::Int(n) => ArraySize::Known(*n as i32), - Expr::UInt(n) => ArraySize::Known(*n as i32), + Expr::Int(n, _) => ArraySize::Known(*n as i32), _ => ArraySize::Known(0), }; diff --git a/src/decl.rs b/src/decl.rs index f0012ce..12ef6c5 100644 --- a/src/decl.rs +++ b/src/decl.rs @@ -336,7 +336,12 @@ fn format_func_decl(func: &FuncDecl, is_macro: bool) -> String { let requires = func .requires .iter() - .map(|&r| format!(" require {}", func.arena.exprs[r].pretty_print(&func.arena, 0))) + .map(|&r| { + format!( + " require {}", + func.arena.exprs[r].pretty_print(&func.arena, 0) + ) + }) .collect::>() .join(""); @@ -564,7 +569,7 @@ mod tests { let mut arena = ExprArena::new(); // Create body: { x + 1 } let x_id = arena.add(Expr::Id(Name::str("x")), test_loc()); - let one_id = arena.add(Expr::Int(1), test_loc()); + let one_id = arena.add(Expr::Int(1, None), test_loc()); let add_id = arena.add(Expr::Binop(Binop::Plus, x_id, one_id), test_loc()); let body_id = arena.add(Expr::Block(vec![add_id]), test_loc()); diff --git a/src/defs.rs b/src/defs.rs index 9686acd..5ba97f3 100644 --- a/src/defs.rs +++ b/src/defs.rs @@ -5,6 +5,36 @@ use std::fmt; use std::hash::Hash; use std::ops::Deref; +#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] +pub enum IntLiteralSuffix { + I32, + U32, +} + +impl fmt::Display for IntLiteralSuffix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IntLiteralSuffix::I32 => write!(f, "i32"), + IntLiteralSuffix::U32 => write!(f, "u32"), + } + } +} + +#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] +pub enum FloatLiteralSuffix { + F32, + F64, +} + +impl fmt::Display for FloatLiteralSuffix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FloatLiteralSuffix::F32 => write!(f, "f32"), + FloatLiteralSuffix::F64 => write!(f, "f64"), + } + } +} + /// An interned string. #[derive(Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct Name(Intern); diff --git a/src/expr.rs b/src/expr.rs index cad3a41..40cab28 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -12,14 +12,11 @@ pub enum Expr { /// Identifier expression. Id(Name), - /// Signed integer literal. - Int(i64), + /// Integer literal, with optional explicit suffix. + Int(i64, Option), - /// Unsigned integer literal. - UInt(u64), - - /// Floating-point literal as string. - Real(String), // f64 is not hashable so we just use the string representation + /// Floating-point literal as string, with optional explicit suffix. + Real(String, Option), // f64 is not hashable so we just use the string representation /// Function call expression with function and arguments. Call(ExprID, Vec), @@ -129,7 +126,7 @@ impl Expr { /// /// ```ignore /// let mut arena = ExprArena::new(); - /// let expr_id = arena.add(Expr::Int(42), test_loc()); + /// let expr_id = arena.add(Expr::Int(42, None), test_loc()); /// let output = arena.exprs[expr_id].pretty_print(&arena, 0); /// // Output: "42" /// ``` @@ -140,9 +137,14 @@ impl Expr { let args_str: Vec<_> = args.iter().map(|t| t.pretty_print()).collect(); format!("{}⟨{}⟩", name, args_str.join(", ")) } - Expr::Int(n) => n.to_string(), - Expr::UInt(n) => n.to_string(), - Expr::Real(s) => s.clone(), + Expr::Int(n, suffix) => match suffix { + Some(suffix) => format!("{n}{suffix}"), + None => n.to_string(), + }, + Expr::Real(s, suffix) => match suffix { + Some(suffix) => format!("{s}{suffix}"), + None => s.clone(), + }, Expr::String(s) => format!("\"{}\"", s), Expr::Char(c) => format!("'{}'", c), Expr::True => "true".to_string(), @@ -358,9 +360,8 @@ pub fn copy_expr( dst_arena.add(Expr::Id(*name), loc) } Expr::TypeApp(name, args) => dst_arena.add(Expr::TypeApp(*name, args.clone()), loc), - Expr::Int(n) => dst_arena.add(Expr::Int(*n), loc), - Expr::UInt(n) => dst_arena.add(Expr::UInt(*n), loc), - Expr::Real(s) => dst_arena.add(Expr::Real(s.clone()), loc), + Expr::Int(n, suffix) => dst_arena.add(Expr::Int(*n, *suffix), loc), + Expr::Real(s, suffix) => dst_arena.add(Expr::Real(s.clone(), *suffix), loc), Expr::String(s) => dst_arena.add(Expr::String(s.clone()), loc), Expr::Char(c) => dst_arena.add(Expr::Char(*c), loc), Expr::True => dst_arena.add(Expr::True, loc), @@ -551,10 +552,13 @@ mod tests { fn test_pretty_print_literals() { let arena = ExprArena::new(); - assert_eq!(Expr::Int(42).pretty_print(&arena, 0), "42"); - assert_eq!(Expr::UInt(100).pretty_print(&arena, 0), "100"); + assert_eq!(Expr::Int(42, None).pretty_print(&arena, 0), "42"); + assert_eq!( + Expr::Int(100, Some(IntLiteralSuffix::U32)).pretty_print(&arena, 0), + "100u32" + ); assert_eq!( - Expr::Real("3.14".to_string()).pretty_print(&arena, 0), + Expr::Real("3.14".to_string(), None).pretty_print(&arena, 0), "3.14" ); assert_eq!( @@ -581,8 +585,8 @@ mod tests { fn test_pretty_print_binop() { let mut arena = ExprArena::new(); - let lhs_id = arena.add(Expr::Int(1), test_loc()); - let rhs_id = arena.add(Expr::Int(2), test_loc()); + let lhs_id = arena.add(Expr::Int(1, None), test_loc()); + let rhs_id = arena.add(Expr::Int(2, None), test_loc()); let add_expr = Expr::Binop(Binop::Plus, lhs_id, rhs_id); assert_eq!(add_expr.pretty_print(&arena, 0), "1 + 2"); @@ -598,7 +602,7 @@ mod tests { fn test_pretty_print_unop() { let mut arena = ExprArena::new(); - let expr_id = arena.add(Expr::Int(42), test_loc()); + let expr_id = arena.add(Expr::Int(42, None), test_loc()); let neg_expr = Expr::Unop(Unop::Neg, expr_id); assert_eq!(neg_expr.pretty_print(&arena, 0), "-42"); @@ -613,8 +617,8 @@ mod tests { let mut arena = ExprArena::new(); let func_id = arena.add(Expr::Id(Name::str("foo")), test_loc()); - let arg1_id = arena.add(Expr::Int(1), test_loc()); - let arg2_id = arena.add(Expr::Int(2), test_loc()); + let arg1_id = arena.add(Expr::Int(1, None), test_loc()); + let arg2_id = arena.add(Expr::Int(2, None), test_loc()); let call_expr = Expr::Call(func_id, vec![arg1_id, arg2_id]); assert_eq!(call_expr.pretty_print(&arena, 0), "foo(1, 2)"); @@ -627,9 +631,9 @@ mod tests { fn test_pretty_print_array_literal() { let mut arena = ExprArena::new(); - let elem1 = arena.add(Expr::Int(1), test_loc()); - let elem2 = arena.add(Expr::Int(2), test_loc()); - let elem3 = arena.add(Expr::Int(3), test_loc()); + let elem1 = arena.add(Expr::Int(1, None), test_loc()); + let elem2 = arena.add(Expr::Int(2, None), test_loc()); + let elem3 = arena.add(Expr::Int(3, None), test_loc()); let arr_expr = Expr::ArrayLiteral(vec![elem1, elem2, elem3]); assert_eq!(arr_expr.pretty_print(&arena, 0), "[1, 2, 3]"); @@ -640,7 +644,7 @@ mod tests { let mut arena = ExprArena::new(); let arr_id = arena.add(Expr::Id(Name::str("arr")), test_loc()); - let idx_id = arena.add(Expr::Int(0), test_loc()); + let idx_id = arena.add(Expr::Int(0, None), test_loc()); let index_expr = Expr::ArrayIndex(arr_id, idx_id); assert_eq!(index_expr.pretty_print(&arena, 0), "arr[0]"); @@ -669,7 +673,7 @@ mod tests { assert_eq!(var2.pretty_print(&arena, 0), "var x: i32"); // var x = 42 - let init_id = arena.add(Expr::Int(42), test_loc()); + let init_id = arena.add(Expr::Int(42, None), test_loc()); let var3 = Expr::Var(Name::str("x"), Some(init_id), None); assert_eq!(var3.pretty_print(&arena, 0), "var x = 42"); @@ -682,7 +686,7 @@ mod tests { fn test_pretty_print_let() { let mut arena = ExprArena::new(); - let value_id = arena.add(Expr::Int(42), test_loc()); + let value_id = arena.add(Expr::Int(42, None), test_loc()); // let x = 42 let let1 = Expr::Let(Name::str("x"), value_id, None); @@ -699,7 +703,7 @@ mod tests { // |x| x + 1 let x_id = arena.add(Expr::Id(Name::str("x")), test_loc()); - let one_id = arena.add(Expr::Int(1), test_loc()); + let one_id = arena.add(Expr::Int(1, None), test_loc()); let body_id = arena.add(Expr::Binop(Binop::Plus, x_id, one_id), test_loc()); let lambda = Expr::Lambda { @@ -718,8 +722,8 @@ mod tests { let mut arena = ExprArena::new(); let cond_id = arena.add(Expr::True, test_loc()); - let then_id = arena.add(Expr::Int(1), test_loc()); - let else_id = arena.add(Expr::Int(2), test_loc()); + let then_id = arena.add(Expr::Int(1, None), test_loc()); + let else_id = arena.add(Expr::Int(2, None), test_loc()); // if true 1 else 2 (simplified without blocks) let if_expr = Expr::If(cond_id, then_id, Some(else_id)); @@ -745,8 +749,8 @@ mod tests { fn test_pretty_print_for() { let mut arena = ExprArena::new(); - let start_id = arena.add(Expr::Int(0), test_loc()); - let end_id = arena.add(Expr::Int(10), test_loc()); + let start_id = arena.add(Expr::Int(0, None), test_loc()); + let end_id = arena.add(Expr::Int(10, None), test_loc()); let body_id = arena.add(Expr::Block(vec![]), test_loc()); let for_expr = Expr::For { @@ -768,8 +772,8 @@ mod tests { assert_eq!(empty_block.pretty_print(&arena, 0), "{}"); // Block with expressions - let expr1_id = arena.add(Expr::Int(1), test_loc()); - let expr2_id = arena.add(Expr::Int(2), test_loc()); + let expr1_id = arena.add(Expr::Int(1, None), test_loc()); + let expr2_id = arena.add(Expr::Int(2, None), test_loc()); let block = Expr::Block(vec![expr1_id, expr2_id]); assert_eq!(block.pretty_print(&arena, 0), "{\n 1\n 2\n}"); @@ -779,7 +783,7 @@ mod tests { fn test_pretty_print_return() { let mut arena = ExprArena::new(); - let value_id = arena.add(Expr::Int(42), test_loc()); + let value_id = arena.add(Expr::Int(42, None), test_loc()); let return_expr = Expr::Return(value_id); assert_eq!(return_expr.pretty_print(&arena, 0), "return 42"); @@ -789,7 +793,7 @@ mod tests { fn test_pretty_print_tuple() { let mut arena = ExprArena::new(); - let elem1 = arena.add(Expr::Int(1), test_loc()); + let elem1 = arena.add(Expr::Int(1, None), test_loc()); let elem2 = arena.add(Expr::String("hello".to_string()), test_loc()); let tuple_expr = Expr::Tuple(vec![elem1, elem2]); diff --git a/src/jit.rs b/src/jit.rs index aa590b7..08fd6a0 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -797,8 +797,11 @@ impl<'a> FunctionTranslator<'a> { match &decl.arena[expr] { Expr::False => self.builder.ins().iconst(I8, 0), Expr::True => self.builder.ins().iconst(I8, 1), - Expr::Int(imm) => self.builder.ins().iconst(I32, *imm), - Expr::Real(s) => { + Expr::Int(imm, _) => self + .builder + .ins() + .iconst(decl.types[expr].cranelift_type(), *imm), + Expr::Real(s, _) => { let val: f64 = s.parse().expect("invalid float literal"); match &*decl.types[expr] { crate::Type::Float32 => self.builder.ins().f32const(val as f32), @@ -1303,7 +1306,7 @@ impl<'a> FunctionTranslator<'a> { if matches!(*lhs_ty, crate::types::Type::Float32x4) { let vec = self.translate_expr(*lhs, decl, decls); // Try constant lane index - if let Expr::Int(n) = &decl.arena.exprs[*rhs] { + if let Expr::Int(n, _) = &decl.arena.exprs[*rhs] { return self.builder.ins().extractlane(vec, *n as u8); } // Dynamic index: spill to stack and load @@ -1843,7 +1846,6 @@ impl<'a> FunctionTranslator<'a> { panic!("JIT lambda: expected function type, got {:?}", lambda_ty); } } - Expr::UInt(n) => self.builder.ins().iconst(I32, *n as i64), Expr::AsTy(expr_id, target_ty) => { let val = self.translate_expr(*expr_id, decl, decls); let src_ty = decl.types[*expr_id]; @@ -2697,9 +2699,8 @@ fn collect_free_vars_rec( } } // Terminal expressions — no sub-expressions. - Expr::Int(_) - | Expr::UInt(_) - | Expr::Real(_) + Expr::Int(_, _) + | Expr::Real(_, _) | Expr::String(_) | Expr::Char(_) | Expr::True diff --git a/src/lexer.rs b/src/lexer.rs index ec3216d..c7386a9 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -3,9 +3,9 @@ use crate::defs::*; #[derive(Clone, Copy, PartialEq, Debug)] pub enum Token { Id(Name), - Integer(i64), - UInteger(u64), - Real(Name), + Integer(i64, Option), + Real(Name, Option), + InvalidNumberSuffix(Name), Lparen, Rparen, Lbrace, @@ -235,22 +235,47 @@ impl Lexer { self.i += 1; } - if self.i < n && bytes[self.i] == b'u' { - self.i += 1; + let number_end = self.i; - if fraction { - return Token::Error; - } else if let Ok(uint_value) = self.code[start..self.i - 1].parse() { - return Token::UInteger(uint_value); - } else { - return Token::Error; + if self.i < n && bytes[self.i].is_ascii_alphabetic() { + let suffix_start = self.i; + while self.i < n && id_byte(bytes[self.i]) { + self.i += 1; } + + let suffix = &self.code[suffix_start..self.i]; + return if fraction { + match suffix { + "f32" => Token::Real( + Name::new(self.code[start..number_end].into()), + Some(FloatLiteralSuffix::F32), + ), + "f64" => Token::Real( + Name::new(self.code[start..number_end].into()), + Some(FloatLiteralSuffix::F64), + ), + _ => Token::InvalidNumberSuffix(Name::str(suffix)), + } + } else { + match suffix { + "i32" => match self.code[start..number_end].parse() { + Ok(value) => Token::Integer(value, Some(IntLiteralSuffix::I32)), + Err(_) => Token::Error, + }, + // `u` is accepted as a normalized alias for `u32`. + "u" | "u32" => match self.code[start..number_end].parse() { + Ok(value) => Token::Integer(value, Some(IntLiteralSuffix::U32)), + Err(_) => Token::Error, + }, + _ => Token::InvalidNumberSuffix(Name::str(suffix)), + } + }; } return if fraction { - Token::Real(Name::new(self.code[start..self.i].into())) + Token::Real(Name::new(self.code[start..self.i].into()), None) } else if let Ok(int_value) = self.code[start..self.i].parse() { - Token::Integer(int_value) + Token::Integer(int_value, None) } else { Token::Error }; @@ -481,12 +506,41 @@ mod tests { assert_eq!(tokens("x"), vec![id("x")]); assert_eq!(tokens(" x "), vec![id("x")]); assert_eq!(tokens("_x"), vec![id("_x")]); - assert_eq!(tokens("42"), vec![Integer(42)]); - assert_eq!(tokens("42.0"), vec![Real(Name::str("42.0"))]); - assert_eq!(tokens(".5"), vec![Dot, Integer(5)]); - assert_eq!(tokens("42u"), vec![UInteger(42)]); - assert_eq!(tokens("2 + 2"), vec![Integer(2), Plus, Integer(2)]); - assert_eq!(tokens("2u + 2u"), vec![UInteger(2), Plus, UInteger(2)]); + assert_eq!(tokens("42"), vec![Integer(42, None)]); + assert_eq!(tokens("42.0"), vec![Real(Name::str("42.0"), None)]); + assert_eq!(tokens(".5"), vec![Dot, Integer(5, None)]); + assert_eq!( + tokens("42u"), + vec![Integer(42, Some(IntLiteralSuffix::U32))] + ); + assert_eq!( + tokens("42u32"), + vec![Integer(42, Some(IntLiteralSuffix::U32))] + ); + assert_eq!( + tokens("42i32"), + vec![Integer(42, Some(IntLiteralSuffix::I32))] + ); + assert_eq!( + tokens("42.0f32"), + vec![Real(Name::str("42.0"), Some(FloatLiteralSuffix::F32))] + ); + assert_eq!( + tokens("42.0f64"), + vec![Real(Name::str("42.0"), Some(FloatLiteralSuffix::F64))] + ); + assert_eq!( + tokens("2 + 2"), + vec![Integer(2, None), Plus, Integer(2, None)] + ); + assert_eq!( + tokens("2u + 2u"), + vec![ + Integer(2, Some(IntLiteralSuffix::U32)), + Plus, + Integer(2, Some(IntLiteralSuffix::U32)) + ] + ); assert_eq!(tokens("foo()"), vec![id("foo"), Lparen, Rparen]); assert_eq!(tokens("x <= y"), vec![id("x"), Leq, id("y")]); assert_eq!(tokens("x >= y"), vec![id("x"), Geq, id("y")]); @@ -496,7 +550,12 @@ mod tests { tokens("((x))"), vec![Lparen, Lparen, id("x"), Rparen, Rparen] ); - assert_eq!(tokens("1x"), vec![Integer(1), id("x")]); + assert_eq!(tokens("1x"), vec![InvalidNumberSuffix(Name::str("x"))]); + assert_eq!(tokens("1i8"), vec![InvalidNumberSuffix(Name::str("i8"))]); + assert_eq!(tokens("1u8"), vec![InvalidNumberSuffix(Name::str("u8"))]); + assert_eq!(tokens("1i64"), vec![InvalidNumberSuffix(Name::str("i64"))]); + assert_eq!(tokens("1f64"), vec![InvalidNumberSuffix(Name::str("f64"))]); + assert_eq!(tokens("1.0u"), vec![InvalidNumberSuffix(Name::str("u"))]); assert_eq!(tokens("void"), vec![Void]); assert_eq!(tokens("i8"), vec![Int8]); assert_eq!(tokens("i32"), vec![Int32]); diff --git a/src/llvm_jit.rs b/src/llvm_jit.rs index 071c3ee..d984f1d 100644 --- a/src/llvm_jit.rs +++ b/src/llvm_jit.rs @@ -658,8 +658,13 @@ impl LLVMJIT { // the Context in the same struct and guarantee drop order (EE before Context). let context_ref: &'static Context = unsafe { &*(context.as_ref() as *const Context) }; - let (state, _compile_elapsed) = - compile_with_context(context_ref, decls, entry_points, self.print_ir, self.no_recursion)?; + let (state, _compile_elapsed) = compile_with_context( + context_ref, + decls, + entry_points, + self.print_ir, + self.no_recursion, + )?; let ee = state .module @@ -801,10 +806,7 @@ impl<'ctx> LLVMJITState<'ctx> { /// Signature matches the Rust shim used by JIT: /// i64 slice_eq(ptr a, ptr b, i64 elem_size) /// Returns 1 if the slices are equal, 0 otherwise. - fn get_or_emit_aot_slice_eq( - &mut self, - fn_ty: FunctionType<'ctx>, - ) -> FunctionValue<'ctx> { + fn get_or_emit_aot_slice_eq(&mut self, fn_ty: FunctionType<'ctx>) -> FunctionValue<'ctx> { const SYM: &str = "__lyte_aot_slice_eq"; if let Some(f) = self.module.get_function(SYM) { return f; @@ -1521,7 +1523,9 @@ impl<'a, 'ctx> FunctionTranslator<'a, 'ctx> { }) .unwrap_or(decl.types[expr]), Expr::ArrayIndex(arr_id, _) => match &*self.representation_type(*arr_id, decl) { - crate::Type::Array(elem, _) | crate::Type::Slice(elem) | crate::Type::Reference(elem) => *elem, + crate::Type::Array(elem, _) + | crate::Type::Slice(elem) + | crate::Type::Reference(elem) => *elem, _ => decl.types[expr], }, _ => decl.types[expr], @@ -1811,9 +1815,12 @@ impl<'a, 'ctx> FunctionTranslator<'a, 'ctx> { match &decl.arena[expr] { Expr::True => self.i8_ty().const_int(1, false).into(), Expr::False => self.i8_ty().const_int(0, false).into(), - Expr::Int(n) => self.i32_ty().const_int(*n as u64, true).into(), - Expr::UInt(n) => self.i32_ty().const_int(*n, false).into(), - Expr::Real(s) => { + Expr::Int(n, _) => match &*decl.types[expr] { + crate::Type::Int8 => self.i8_ty().const_int(*n as u64, true).into(), + crate::Type::UInt32 => self.i32_ty().const_int(*n as u64, false).into(), + _ => self.i32_ty().const_int(*n as u64, true).into(), + }, + Expr::Real(s, _) => { let val: f64 = s.parse().expect("invalid float literal"); match &*decl.types[expr] { crate::Type::Float32 => self.state.f32_ty().const_float(val).into(), @@ -1831,18 +1838,29 @@ impl<'a, 'ctx> FunctionTranslator<'a, 'ctx> { .build_load(ty.llvm_basic_type(self.ctx()), alloca, &**name) .unwrap() } else { - let stored = self.builder().build_load(self.ptr_ty(), alloca, "var_ptr").unwrap(); + let stored = self + .builder() + .build_load(self.ptr_ty(), alloca, "var_ptr") + .unwrap(); if let Some(var_ty) = self.variable_types.get(name.as_str()).copied() { if var_ty.is_ptr() { stored } else { self.builder() - .build_load(ty.llvm_basic_type(self.ctx()), stored.into_pointer_value(), &**name) + .build_load( + ty.llvm_basic_type(self.ctx()), + stored.into_pointer_value(), + &**name, + ) .unwrap() } } else { self.builder() - .build_load(ty.llvm_basic_type(self.ctx()), stored.into_pointer_value(), &**name) + .build_load( + ty.llvm_basic_type(self.ctx()), + stored.into_pointer_value(), + &**name, + ) .unwrap() } } diff --git a/src/monomorph_pass.rs b/src/monomorph_pass.rs index a14b9b6..6ca31b5 100644 --- a/src/monomorph_pass.rs +++ b/src/monomorph_pass.rs @@ -430,9 +430,8 @@ impl MonomorphPass { self.process_expr(sz, fdecl, decls)?; } // True leaves - Expr::Int(_) - | Expr::UInt(_) - | Expr::Real(_) + Expr::Int(_, _) + | Expr::Real(_, _) | Expr::String(_) | Expr::Char(_) | Expr::True @@ -594,7 +593,7 @@ impl MonomorphPass { } } - // Substitute size vars in the body expressions (e.g. Expr::Id("N") → Expr::Int(3)) + // Substitute size vars in the body expressions (e.g. Expr::Id("N") → Expr::Int(3, None)) if !size_bindings.is_empty() { substitute_size_var_exprs(&mut specialized.arena, &size_bindings); } @@ -664,12 +663,12 @@ fn subst_size_vars(ty: TypeID, bindings: &HashMap) -> TypeID { } } -/// Replace `Expr::Id(N)` with `Expr::Int(n)` for each size var binding, across all arena slots. +/// Replace `Expr::Id(N)` with `Expr::Int(n, None)` for each size var binding, across all arena slots. fn substitute_size_var_exprs(arena: &mut ExprArena, bindings: &HashMap) { for slot in arena.exprs.iter_mut() { if let Expr::Id(name) = slot { if let Some(&val) = bindings.get(name) { - *slot = Expr::Int(val as i64); + *slot = Expr::Int(val as i64, None); } } } @@ -720,7 +719,7 @@ mod tests { fn mk_simple_func(name: &str, typevars: Vec<&str>) -> FuncDecl { let mut arena = ExprArena::new(); - let body_expr = arena.add(Expr::Int(0), test_loc()); + let body_expr = arena.add(Expr::Int(0, None), test_loc()); FuncDecl { name: Name::str(name), @@ -832,8 +831,8 @@ mod tests { let decls = DeclTable::new(vec![]); let mut arena = ExprArena::new(); - let expr1 = arena.add(Expr::Int(1), test_loc()); - let expr2 = arena.add(Expr::Int(2), test_loc()); + let expr1 = arena.add(Expr::Int(1, None), test_loc()); + let expr2 = arena.add(Expr::Int(2, None), test_loc()); let block = arena.add(Expr::Block(vec![expr1, expr2]), test_loc()); let mut fdecl = FuncDecl { @@ -862,8 +861,8 @@ mod tests { let decls = DeclTable::new(vec![]); let mut arena = ExprArena::new(); - let lhs = arena.add(Expr::Int(1), test_loc()); - let rhs = arena.add(Expr::Int(2), test_loc()); + let lhs = arena.add(Expr::Int(1, None), test_loc()); + let rhs = arena.add(Expr::Int(2, None), test_loc()); let binop = arena.add(Expr::Binop(Binop::Plus, lhs, rhs), test_loc()); let mut fdecl = FuncDecl { @@ -892,8 +891,8 @@ mod tests { let decls = DeclTable::new(vec![]); let mut arena = ExprArena::new(); - let elem1 = arena.add(Expr::Int(1), test_loc()); - let elem2 = arena.add(Expr::Int(2), test_loc()); + let elem1 = arena.add(Expr::Int(1, None), test_loc()); + let elem2 = arena.add(Expr::Int(2, None), test_loc()); let array = arena.add(Expr::ArrayLiteral(vec![elem1, elem2]), test_loc()); let mut fdecl = FuncDecl { @@ -1128,8 +1127,8 @@ mod tests { // Test If expression let mut arena = ExprArena::new(); let cond = arena.add(Expr::True, test_loc()); - let then_expr = arena.add(Expr::Int(1), test_loc()); - let else_expr = arena.add(Expr::Int(2), test_loc()); + let then_expr = arena.add(Expr::Int(1, None), test_loc()); + let else_expr = arena.add(Expr::Int(2, None), test_loc()); let if_expr = arena.add(Expr::If(cond, then_expr, Some(else_expr)), test_loc()); let mut fdecl = FuncDecl { @@ -1164,7 +1163,7 @@ mod tests { // Create a simple non-generic entry point function // main() { 42 } let mut arena = ExprArena::new(); - let body_expr = arena.add(Expr::Int(42), test_loc()); + let body_expr = arena.add(Expr::Int(42, None), test_loc()); let entry_func = FuncDecl { name: Name::str("main"), @@ -1225,7 +1224,7 @@ mod tests { // Create entry point function that calls id(42) // main() { id(42) } let mut main_arena = ExprArena::new(); - let arg_expr = main_arena.add(Expr::Int(42), test_loc()); + let arg_expr = main_arena.add(Expr::Int(42, None), test_loc()); let fn_expr = main_arena.add(Expr::Id(Name::str("id")), test_loc()); let call_expr = main_arena.add(Expr::Call(fn_expr, vec![arg_expr]), test_loc()); diff --git a/src/parser.rs b/src/parser.rs index 38b5e4a..5a3cd77 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -119,7 +119,10 @@ fn parse_basic_type(typevars: &[Name], cx: &mut ParseContext) -> TypeID { let r = parse_type(typevars, cx); if cx.lex.tok == Token::Semi { cx.next(); - if let Token::Integer(n) = cx.lex.tok { + if let Token::Integer(n, suffix) = cx.lex.tok { + if suffix.is_some() { + cx.err(String::from("array size integer cannot have a suffix")); + } if n == 0 { cx.err(String::from("array size must be greater than 0")); } @@ -523,7 +526,10 @@ fn parse_postfix(arena: &mut ExprArena, typevars: &[Name], cx: &mut ParseContext let dot_loc = cx.lex.loc; cx.next(); // Support both named fields (x.foo) and tuple indices (x.0, x.1) - let field = if let Token::Integer(n) = cx.lex.tok { + let field = if let Token::Integer(n, suffix) = cx.lex.tok { + if suffix.is_some() { + cx.err(String::from("tuple field index cannot have a suffix")); + } cx.next(); Name::new(n.to_string()) } else { @@ -559,7 +565,7 @@ fn parse_atom(arena: &mut ExprArena, typevars: &[Name], cx: &mut ParseContext) - expect(Token::Rmath, cx); arena.add(Expr::TypeApp(id, type_args), loc) } else if let Some(&value) = cx.consts.get(&id) { - arena.add(Expr::Int(value), loc) + arena.add(Expr::Int(value, None), loc) } else { arena.add(Expr::Id(id), loc) } @@ -585,23 +591,23 @@ fn parse_atom(arena: &mut ExprArena, typevars: &[Name], cx: &mut ParseContext) - let name = expect_id(cx); arena.add(Expr::Enum(name), cx.lex.loc) } - Token::Integer(x) => { - let e = Expr::Int(x); + Token::Integer(x, suffix) => { + let e = Expr::Int(x, suffix); let loc = cx.lex.loc; cx.next(); arena.add(e, loc) } - Token::UInteger(x) => { - let e = Expr::UInt(x); + Token::Real(x, suffix) => { + let e = Expr::Real(x.to_string(), suffix); let loc = cx.lex.loc; cx.next(); arena.add(e, loc) } - Token::Real(x) => { - let e = Expr::Real(x.to_string()); + Token::InvalidNumberSuffix(suffix) => { let loc = cx.lex.loc; + cx.err(format!("invalid numeric literal suffix '{}'", suffix)); cx.next(); - arena.add(e, loc) + arena.add(Expr::Error, loc) } Token::String(s) => { let e = Expr::String(s.to_string()); @@ -1178,15 +1184,24 @@ fn parse_decl(cx: &mut ParseContext) -> Option { let name = expect_id(cx); expect(Token::Assign, cx); let value = match cx.lex.tok { - Token::Integer(n) => { + Token::Integer(n, None) => { cx.next(); n } + Token::Integer(_, Some(_)) => { + cx.err(String::from("integer const cannot have a suffix")); + cx.next(); + 0 + } Token::Minus => { cx.next(); - if let Token::Integer(n) = cx.lex.tok { + if let Token::Integer(n, None) = cx.lex.tok { cx.next(); -n + } else if let Token::Integer(_, Some(_)) = cx.lex.tok { + cx.err(String::from("integer const cannot have a suffix")); + cx.next(); + 0 } else { cx.err(String::from("Expected integer value for const")); 0 diff --git a/src/safety_checker.rs b/src/safety_checker.rs index f5c1323..2a1716a 100644 --- a/src/safety_checker.rs +++ b/src/safety_checker.rs @@ -300,12 +300,12 @@ impl SafetyChecker { // match `x != 0` — mark x as non-zero if let Expr::Binop(Binop::NotEqual, lhs, rhs) = &decl.arena[expr] { if let Some(name) = expr_constraint_name(*lhs, &decl.arena) { - if let Expr::Int(0) | Expr::UInt(0) = &decl.arena[*rhs] { + if let Expr::Int(0, _) = &decl.arena[*rhs] { self.add_non_zero(name); } } if let Some(name) = expr_constraint_name(*rhs, &decl.arena) { - if let Expr::Int(0) | Expr::UInt(0) = &decl.arena[*lhs] { + if let Expr::Int(0, _) = &decl.arena[*lhs] { self.add_non_zero(name); } } @@ -504,16 +504,11 @@ impl SafetyChecker { fn check_expr(&mut self, expr: ExprID, decl: &FuncDecl, decls: &DeclTable) -> IndexInterval { match &decl.arena[expr] { - Expr::Int(x) => IndexInterval { + Expr::Int(x, _) => IndexInterval { min: *x, max: *x, non_zero: *x != 0, }, - Expr::UInt(x) => IndexInterval { - min: *x as i64, - max: *x as i64, - non_zero: *x != 0, - }, Expr::Block(exprs) => { let n = self.vars.len(); for e in exprs { @@ -741,9 +736,7 @@ impl SafetyChecker { if !has_len_bound && !has_var_bound { self.errors.push(SafetyError { location: decl.arena.locs[expr], - message: format!( - "couldn't prove index is less than array length" - ), + message: format!("couldn't prove index is less than array length"), }); } } @@ -1081,8 +1074,7 @@ impl SafetyChecker { if let Expr::Binop(Binop::Plus, plus_lhs, plus_rhs) = &arena[*rhs] { let lhs_is_var = matches!(&arena[*plus_lhs], Expr::Id(n) if *n == var_name); - let rhs_is_one = - matches!(&arena[*plus_rhs], Expr::Int(1) | Expr::UInt(1)); + let rhs_is_one = matches!(&arena[*plus_rhs], Expr::Int(1, _)); return lhs_is_var && rhs_is_one; } return false; // Some other assignment to var_name @@ -1291,12 +1283,10 @@ impl SafetyChecker { size_subst: &[(Name, i64)], decls: &DeclTable, ) -> bool { - let lookup = |n: &Name| -> Option { - subst.iter().find(|(p, _)| p == n).map(|(_, a)| *a) - }; - let size_lookup = |n: &Name| -> Option { - size_subst.iter().find(|(s, _)| s == n).map(|(_, v)| *v) - }; + let lookup = + |n: &Name| -> Option { subst.iter().find(|(p, _)| p == n).map(|(_, a)| *a) }; + let size_lookup = + |n: &Name| -> Option { size_subst.iter().find(|(s, _)| s == n).map(|(_, v)| *v) }; match &callee.arena[req] { Expr::True => true, @@ -1548,9 +1538,8 @@ impl SafetyChecker { fn subexprs(e: &Expr) -> Vec { match e { Expr::Id(_) - | Expr::Int(_) - | Expr::UInt(_) - | Expr::Real(_) + | Expr::Int(_, _) + | Expr::Real(_, _) | Expr::String(_) | Expr::Char(_) | Expr::True @@ -1584,7 +1573,9 @@ impl SafetyChecker { v } Expr::While(c, b) => vec![*c, *b], - Expr::For { start, end, body, .. } => vec![*start, *end, *body], + Expr::For { + start, end, body, .. + } => vec![*start, *end, *body], Expr::Block(es) | Expr::Tuple(es) => es.clone(), Expr::Return(e) | Expr::Arena(e) | Expr::Assume(e) => vec![*e], Expr::StructLit(_, fields) => fields.iter().map(|(_, e)| *e).collect(), @@ -1825,10 +1816,9 @@ impl SafetyChecker { }; match info.kind { NodeKind::TopLevel => (f.loc, format!("function `{}`", f.name)), - NodeKind::Lambda { arena_idx } => ( - f.arena.locs[arena_idx], - format!("lambda in `{}`", f.name), - ), + NodeKind::Lambda { arena_idx } => { + (f.arena.locs[arena_idx], format!("lambda in `{}`", f.name)) + } } }; diff --git a/src/stack_codegen.rs b/src/stack_codegen.rs index b8b70c1..c045b3b 100644 --- a/src/stack_codegen.rs +++ b/src/stack_codegen.rs @@ -692,15 +692,11 @@ impl<'a> FunctionTranslator<'a> { fn translate_expr_inner_body(&mut self, expr: ExprID, func: &mut StackFunction) { match &self.decl.arena.exprs[expr].clone() { - Expr::Int(n) => { + Expr::Int(n, _) => { func.emit(StackOp::I64Const(*n)); } - Expr::UInt(n) => { - func.emit(StackOp::I64Const(*n as i64)); - } - - Expr::Real(s) => { + Expr::Real(s, _) => { let ty = self.expr_type(expr); match &*ty { Type::Float32 => { @@ -3030,9 +3026,8 @@ fn collect_free_vars_rec( collect_free_vars_rec(*fval, arena, exclude, local_vars, types, result, seen); } } - Expr::Int(_) - | Expr::UInt(_) - | Expr::Real(_) + Expr::Int(_, _) + | Expr::Real(_, _) | Expr::String(_) | Expr::Char(_) | Expr::True diff --git a/src/vm_codegen.rs b/src/vm_codegen.rs index 4d9fc30..aa1b88f 100644 --- a/src/vm_codegen.rs +++ b/src/vm_codegen.rs @@ -312,9 +312,7 @@ impl VMCodegen { /// identifiers, type casts, and field access are allowed. fn is_inline_expr(id: ExprID, arena: &ExprArena) -> bool { match &arena[id] { - Expr::Int(_) | Expr::UInt(_) | Expr::Real(_) | Expr::Id(_) | Expr::True | Expr::False => { - true - } + Expr::Int(_, _) | Expr::Real(_, _) | Expr::Id(_) | Expr::True | Expr::False => true, Expr::Binop(op, lhs, rhs) => { !matches!(op, Binop::Assign) && is_inline_expr(*lhs, arena) @@ -809,22 +807,13 @@ impl<'a> FunctionTranslator<'a> { /// Translate an expression and return the register containing the result. fn translate_expr(&mut self, expr: ExprID, func: &mut VMFunction) -> Reg { match &self.decl.arena.exprs[expr] { - Expr::Int(n) => { + Expr::Int(n, _) => { let dst = self.alloc_reg(); func.emit(Opcode::LoadImm { dst, value: *n }); dst } - Expr::UInt(n) => { - let dst = self.alloc_reg(); - func.emit(Opcode::LoadImm { - dst, - value: *n as i64, - }); - dst - } - - Expr::Real(s) => { + Expr::Real(s, _) => { let dst = self.alloc_reg(); let ty = self.expr_type(expr); match &*ty { @@ -837,7 +826,6 @@ impl<'a> FunctionTranslator<'a> { func.emit(Opcode::LoadF64 { dst, value }); } _ => { - // Default to f32. let value: f32 = s.parse().unwrap_or(0.0); func.emit(Opcode::LoadF32 { dst, value }); } @@ -2075,7 +2063,11 @@ impl<'a> FunctionTranslator<'a> { Expr::Id(name) => { if let Some(®) = self.variables.get(name) { if self.reg_promoted.contains(name) { - let ty = self.variable_types.get(name).copied().unwrap_or(self.expr_type(expr)); + let ty = self + .variable_types + .get(name) + .copied() + .unwrap_or(self.expr_type(expr)); let slot = self.alloc_local(ty.size(self.decls) as u32); let addr = self.alloc_reg(); func.emit(Opcode::LocalAddr { dst: addr, slot }); @@ -3674,9 +3666,8 @@ fn collect_free_vars_rec( collect_free_vars_rec(*fval, arena, exclude, local_vars, types, result, seen); } } - Expr::Int(_) - | Expr::UInt(_) - | Expr::Real(_) + Expr::Int(_, _) + | Expr::Real(_, _) | Expr::String(_) | Expr::Char(_) | Expr::True @@ -3720,7 +3711,7 @@ mod tests { #[test] fn test_compile_simple_int() { let mut arena = ExprArena::new(); - let expr = arena.add(Expr::Int(42), crate::test_loc()); + let expr = arena.add(Expr::Int(42, None), crate::test_loc()); let func = FuncDecl { name: Name::str("main"), @@ -3751,8 +3742,8 @@ mod tests { #[test] fn test_compile_addition() { let mut arena = ExprArena::new(); - let lhs = arena.add(Expr::Int(10), crate::test_loc()); - let rhs = arena.add(Expr::Int(32), crate::test_loc()); + let lhs = arena.add(Expr::Int(10, None), crate::test_loc()); + let rhs = arena.add(Expr::Int(32, None), crate::test_loc()); let add = arena.add(Expr::Binop(Binop::Plus, lhs, rhs), crate::test_loc()); let int32 = mk_type(Type::Int32); @@ -3785,8 +3776,8 @@ mod tests { #[test] fn test_compile_float_arithmetic() { let mut arena = ExprArena::new(); - let lhs = arena.add(Expr::Real("1.5".to_string()), crate::test_loc()); - let rhs = arena.add(Expr::Real("2.5".to_string()), crate::test_loc()); + let lhs = arena.add(Expr::Real("1.5".to_string(), None), crate::test_loc()); + let rhs = arena.add(Expr::Real("2.5".to_string(), None), crate::test_loc()); let mul = arena.add(Expr::Binop(Binop::Mult, lhs, rhs), crate::test_loc()); let f32_ty = mk_type(Type::Float32); @@ -3820,8 +3811,8 @@ mod tests { fn test_compile_if_else() { let mut arena = ExprArena::new(); let cond = arena.add(Expr::True, crate::test_loc()); - let then_val = arena.add(Expr::Int(100), crate::test_loc()); - let else_val = arena.add(Expr::Int(200), crate::test_loc()); + let then_val = arena.add(Expr::Int(100, None), crate::test_loc()); + let else_val = arena.add(Expr::Int(200, None), crate::test_loc()); let if_expr = arena.add(Expr::If(cond, then_val, Some(else_val)), crate::test_loc()); let int32 = mk_type(Type::Int32); @@ -3857,9 +3848,9 @@ mod tests { // while (false) { 1 } let mut arena = ExprArena::new(); let cond = arena.add(Expr::False, crate::test_loc()); - let body = arena.add(Expr::Int(1), crate::test_loc()); + let body = arena.add(Expr::Int(1, None), crate::test_loc()); let while_expr = arena.add(Expr::While(cond, body), crate::test_loc()); - let result = arena.add(Expr::Int(42), crate::test_loc()); + let result = arena.add(Expr::Int(42, None), crate::test_loc()); let block = arena.add(Expr::Block(vec![while_expr, result]), crate::test_loc()); let int32 = mk_type(Type::Int32); diff --git a/tests/cases/checker/literal_suffix_array_type_size.lyte b/tests/cases/checker/literal_suffix_array_type_size.lyte new file mode 100644 index 0000000..0bf7957 --- /dev/null +++ b/tests/cases/checker/literal_suffix_array_type_size.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_array_type_size.lyte:6: array size integer cannot have a suffix + +main { + var a: [i32; 3u32] +} diff --git a/tests/cases/checker/literal_suffix_const.lyte b/tests/cases/checker/literal_suffix_const.lyte new file mode 100644 index 0000000..ed50df1 --- /dev/null +++ b/tests/cases/checker/literal_suffix_const.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_const.lyte:5: integer const cannot have a suffix + +const N = 3u32 +main { +} diff --git a/tests/cases/checker/literal_suffix_f32_from_f64.lyte b/tests/cases/checker/literal_suffix_f32_from_f64.lyte new file mode 100644 index 0000000..10a0946 --- /dev/null +++ b/tests/cases/checker/literal_suffix_f32_from_f64.lyte @@ -0,0 +1,10 @@ +// args: --check +// expected stdout: +// ❌ ../tests/cases/checker/literal_suffix_f32_from_f64.lyte:9:7: assignment requires matching types: f32 vs f64 +// b = 1.0f64 +// ^ + +main { + var b: f32 + b = 1.0f64 +} diff --git a/tests/cases/checker/literal_suffix_f64_from_f32.lyte b/tests/cases/checker/literal_suffix_f64_from_f32.lyte new file mode 100644 index 0000000..97d31ed --- /dev/null +++ b/tests/cases/checker/literal_suffix_f64_from_f32.lyte @@ -0,0 +1,10 @@ +// args: --check +// expected stdout: +// ❌ ../tests/cases/checker/literal_suffix_f64_from_f32.lyte:9:7: assignment requires matching types: f64 vs f32 +// c = 1.0f32 +// ^ + +main { + var c: f64 + c = 1.0f32 +} diff --git a/tests/cases/checker/literal_suffix_i32_negative_range.lyte b/tests/cases/checker/literal_suffix_i32_negative_range.lyte new file mode 100644 index 0000000..4662554 --- /dev/null +++ b/tests/cases/checker/literal_suffix_i32_negative_range.lyte @@ -0,0 +1,10 @@ +// args: --check +// expected stdout: +// ❌ ../tests/cases/checker/literal_suffix_i32_negative_range.lyte:9:10: integer literal -2147483649 is out of range for i32 +// a = -2147483649i32 +// ^ + +main { + var a: i32 + a = -2147483649i32 +} diff --git a/tests/cases/checker/literal_suffix_i32_range.lyte b/tests/cases/checker/literal_suffix_i32_range.lyte new file mode 100644 index 0000000..70d7cef --- /dev/null +++ b/tests/cases/checker/literal_suffix_i32_range.lyte @@ -0,0 +1,10 @@ +// args: --check +// expected stdout: +// ❌ ../tests/cases/checker/literal_suffix_i32_range.lyte:9:9: integer literal 2147483648 is out of range for i32 +// a = 2147483648i32 +// ^ + +main { + var a: i32 + a = 2147483648i32 +} diff --git a/tests/cases/checker/literal_suffix_invalid.lyte b/tests/cases/checker/literal_suffix_invalid.lyte new file mode 100644 index 0000000..33c6914 --- /dev/null +++ b/tests/cases/checker/literal_suffix_invalid.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_invalid.lyte:6: invalid numeric literal suffix 'i64' + +main { + 1i64 +} diff --git a/tests/cases/checker/literal_suffix_invalid_float_u.lyte b/tests/cases/checker/literal_suffix_invalid_float_u.lyte new file mode 100644 index 0000000..b387965 --- /dev/null +++ b/tests/cases/checker/literal_suffix_invalid_float_u.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_invalid_float_u.lyte:6: invalid numeric literal suffix 'u' + +main { + 1.0u +} diff --git a/tests/cases/checker/literal_suffix_invalid_i8.lyte b/tests/cases/checker/literal_suffix_invalid_i8.lyte new file mode 100644 index 0000000..4288fcc --- /dev/null +++ b/tests/cases/checker/literal_suffix_invalid_i8.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_invalid_i8.lyte:6: invalid numeric literal suffix 'i8' + +main { + 1i8 +} diff --git a/tests/cases/checker/literal_suffix_invalid_integer_float.lyte b/tests/cases/checker/literal_suffix_invalid_integer_float.lyte new file mode 100644 index 0000000..4461f2f --- /dev/null +++ b/tests/cases/checker/literal_suffix_invalid_integer_float.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_invalid_integer_float.lyte:6: invalid numeric literal suffix 'f64' + +main { + 1f64 +} diff --git a/tests/cases/checker/literal_suffix_invalid_u8.lyte b/tests/cases/checker/literal_suffix_invalid_u8.lyte new file mode 100644 index 0000000..9fee25c --- /dev/null +++ b/tests/cases/checker/literal_suffix_invalid_u8.lyte @@ -0,0 +1,7 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_invalid_u8.lyte:6: invalid numeric literal suffix 'u8' + +main { + 1u8 +} diff --git a/tests/cases/checker/literal_suffix_tuple_index.lyte b/tests/cases/checker/literal_suffix_tuple_index.lyte new file mode 100644 index 0000000..79b0b67 --- /dev/null +++ b/tests/cases/checker/literal_suffix_tuple_index.lyte @@ -0,0 +1,8 @@ +// args: --check +// expected stdout: +// ../tests/cases/checker/literal_suffix_tuple_index.lyte:7: tuple field index cannot have a suffix + +main { + var t = (1, 2) + t.0u32 +} diff --git a/tests/cases/checker/literal_suffix_u32_range.lyte b/tests/cases/checker/literal_suffix_u32_range.lyte new file mode 100644 index 0000000..bc30883 --- /dev/null +++ b/tests/cases/checker/literal_suffix_u32_range.lyte @@ -0,0 +1,10 @@ +// args: --check +// expected stdout: +// ❌ ../tests/cases/checker/literal_suffix_u32_range.lyte:9:9: integer literal 4294967296 is out of range for u32 +// a = 4294967296u32 +// ^ + +main { + var a: u32 + a = 4294967296u32 +} diff --git a/tests/cases/literal_suffixes.lyte b/tests/cases/literal_suffixes.lyte new file mode 100644 index 0000000..a1deb45 --- /dev/null +++ b/tests/cases/literal_suffixes.lyte @@ -0,0 +1,44 @@ +// expected stdout: +// compilation successful +// assert(true) +// assert(true) +// assert(true) +// assert(true) +// assert(true) +// assert(true) +// assert(true) +// assert(true) + +approx64(a: f64, b: f64) -> bool { + var d = a - b + if d < 0.0f64 { d = 0.0f64 - d } + return d < 0.0001f64 +} + +main { + var a: f64 + a = 1.0f64 + var b: f64 + b = 1.0f64 + var c: f32 + c = 1.0f32 + var d: i32 + d = 1i32 + var e: u32 + e = 1u32 + var f: u32 + f = 1u + var g: i32 + g = -2147483648i32 + var h: u32 + h = 4294967295u32 + + assert(approx64(a + b, 2.0f64)) + assert(c == 1.0f32) + assert(d == 1) + assert(e as i32 == 1) + assert(f as i32 == 1) + assert(g == -2147483648i32) + assert(h == 4294967295u32) + assert(42 == 42) +}