diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 671d7a91..d0375b02 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::{ parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef}, - semantics::{Declaration, Location, Semantics, Type}, + semantics::{Declaration, Environment, Location, Semantics, Type}, tokens::TokenKind, }; @@ -26,7 +26,11 @@ pub enum Instruction { FloatSubtract, GreaterFloat, GreaterString, + IsBool, IsClass(i64), + IsFloat, + IsNothing, + IsString, Jump(usize), JumpFalse(usize), JumpTrue(usize), // TODO: Only one of these, and use BoolNot? @@ -49,6 +53,7 @@ pub enum Instruction { StoreArgument(usize), StoreLocal(usize), StoreModule(usize), + StoreSlot(usize), StringAdd, } @@ -303,7 +308,7 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) { TreeKind::Identifier => compile_identifier_expression(c, t, tree), TreeKind::IsExpression => compile_is_expression(c, tree), TreeKind::LiteralExpression => compile_literal(c, t, tree), - TreeKind::MemberAccess => compile_member_access(c, tree), + TreeKind::MemberAccess => compile_member_access(c, t, tree), TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree), TreeKind::SelfReference => compile_self_reference(c), TreeKind::UnaryExpression => compile_unary_operator(c, t, tree), @@ -534,49 +539,68 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { let lvalue = tr.nth_tree(0)?; let ltree = &c.syntax[lvalue]; - match ltree.kind { + + #[allow(unused_assignments)] + let mut environment = Environment::error(); + + let declaration = match ltree.kind { + // TODO: Assign to list access TreeKind::Identifier => { - let ident = ltree.nth_token(0)?; - let environment = c.semantics.environment_of(lvalue); - let declaration = environment.bind(ident)?; - - let instruction = match declaration { - Declaration::Variable { - location, index, .. - } => { - let index = *index; - match location { - Location::Argument => { - compiler_assert!(c, t, index < c.function.args); - Instruction::StoreArgument(index) - } - Location::Local => { - if index >= c.function.locals { - c.function.locals = index + 1; - } - Instruction::StoreLocal(index) - } - Location::Module => { - compiler_assert!(c, t, index < c.module.globals); - Instruction::StoreModule(index) - } - Location::Slot => { - ice!(c, t, "cannot have an identifier lvalue bind to a slot"); - } - } + let id = ltree.nth_token(0)?; + environment = c.semantics.environment_of(lvalue); + environment.bind(id)? + } + TreeKind::MemberAccess => { + let id = ltree.nth_token(2)?; + let typ = c.semantics.type_of(ltree.nth_tree(0)?); + environment = match &typ { + Type::Object(ct, _) => { + let class = c.semantics.class_of(*ct); + class.env.clone() } - - Declaration::ExternFunction { .. } => inst_panic!("store ext"), - Declaration::Function { .. } => inst_panic!("store func"), - Declaration::Class { .. } => inst_panic!("store class"), + Type::Class(ct, _) => { + let class = c.semantics.class_of(*ct); + class.static_env.clone() + } + _ => return None, }; - c.push(instruction); + environment.bind(id)? + } + _ => return None, + }; + + let instruction = match declaration { + Declaration::Variable { + location, index, .. + } => { + let index = *index; + match location { + Location::Argument => { + compiler_assert!(c, t, index < c.function.args); + Instruction::StoreArgument(index) + } + Location::Local => { + if index >= c.function.locals { + c.function.locals = index + 1; + } + Instruction::StoreLocal(index) + } + Location::Module => { + compiler_assert!(c, t, index < c.module.globals); + Instruction::StoreModule(index) + } + Location::Slot => { + compile_expression(c, ltree.nth_tree(0)?); + Instruction::StoreSlot(index) + } + } } - // TODO: Member - // TODO: List element - _ => ice!(c, t, "Unsupported lvalue type"), - } + Declaration::ExternFunction { .. } => inst_panic!("store ext"), + Declaration::Function { .. } => inst_panic!("store func"), + Declaration::Class { .. } => inst_panic!("store class"), + }; + c.push(instruction); OK } _ => ice!(c, t, "Unsupported binary expression '{op}'"), @@ -754,19 +778,33 @@ fn compile_type_expr_eq(c: &mut Compiler, t: TreeRef) { fn compile_type_identifier_eq(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { let identifier = tree.nth_token(0)?; - let environment = c.semantics.environment_of(t); - match environment.bind(identifier)? { - Declaration::Class { declaration, .. } => { - // The runtime identifier of the class is the tree index of the - // class declaration sure why not. - let index = declaration.index(); - c.push(Instruction::IsClass(index.try_into().unwrap())); + match identifier.as_str() { + "f64" => { + c.push(Instruction::IsFloat); } + "string" => { + c.push(Instruction::IsString); + } + "bool" => { + c.push(Instruction::IsBool); + } + "nothing" => { + c.push(Instruction::IsNothing); + } + _ => { + let environment = c.semantics.environment_of(t); + match environment.bind(identifier)? { + Declaration::Class { declaration, .. } => { + // The runtime identifier of the class is the tree index of the + // class declaration sure why not. + let index = declaration.index(); + c.push(Instruction::IsClass(index.try_into().unwrap())); + } - // TODO: enforce that type identifier binds to class in `is` - // expresion, we don't support RTTI for other types yet. - _ => return None, - } + _ => return None, + } + } + }; OK } @@ -911,16 +949,35 @@ fn compile_field_value(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { compile_load_declaration(c, t, declaration) } -fn compile_member_access(c: &mut Compiler, tree: &Tree) -> CR { +fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { // In member access; the lhs sets up the object and in theory the rhs // binds against it. ::shrug:: // compile_expression(c, tree.nth_tree(0)?); + let typ = c.semantics.type_of(tree.nth_tree(0)?); + let ident = tree.nth_token(2)?; + + let environment = match &typ { + Type::Object(ct, _) => { + let class = c.semantics.class_of(*ct); + class.env.clone() + } + Type::Class(ct, _) => { + let class = c.semantics.class_of(*ct); + class.static_env.clone() + } + _ => { + c.push(inst_panic!("cannot get environment of {typ}")); + return None; + } + }; + let declaration = environment.bind(ident)?; + // NOTE: If this is a method call we still don't have to do anything // special here, since the load of the member function will *not* // consume the self pointer from the stack. - compile_expression(c, tree.nth_tree(2)?); + compile_load_declaration(c, t, declaration); OK } @@ -938,6 +995,7 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) { TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value), TreeKind::IfStatement => compile_if_statement(c, tree, gen_value), TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value), + TreeKind::ReturnStatement => compile_return_statement(c, tree), TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value), _ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind), @@ -1129,3 +1187,13 @@ fn compile_while_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR OK } + +fn compile_return_statement(c: &mut Compiler, tree: &Tree) -> CR { + if let Some(expr) = tree.nth_tree(1) { + compile_expression(c, expr); + } else { + c.push(Instruction::PushNothing); + } + c.push(Instruction::Return); + OK +} diff --git a/fine/src/parser.rs b/fine/src/parser.rs index cebede90..7b30b65a 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -961,7 +961,8 @@ fn infix_power(token: TokenKind) -> Option<(u8, u8)> { // // UNARY_POWER goes here. // - TokenKind::Dot => Some((18, 19)), + TokenKind::LeftParen => Some((18, 19)), + TokenKind::Dot => Some((20, 21)), _ => None, } } @@ -970,11 +971,6 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) { let Some(mut expr) = prefix_expression(p) else { return; }; - while p.at(TokenKind::LeftParen) { - let m = p.start_before(expr); - argument_list(p); - expr = p.end(m, TreeKind::CallExpression); - } loop { let token = p.peek(); @@ -986,17 +982,23 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) { } expr = match token { - TokenKind::Dot => member_access(p, expr, rp), + TokenKind::Dot => member_access(p, expr), TokenKind::Is => is_expression(p, expr, rp), + TokenKind::LeftParen => call(p, expr), _ => binary_expression(p, expr, rp), }; } } -fn member_access(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClosed { +fn member_access(p: &mut CParser, left: MarkClosed) -> MarkClosed { let m = p.start_before(left); p.advance(); // Consume the operator - expression_with_power(p, right_power); + + p.expect( + TokenKind::Identifier, + "expected an identifier after a '.' in member access", + ); + p.end(m, TreeKind::MemberAccess) } @@ -1016,6 +1018,12 @@ fn is_expression(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClos p.end(m, TreeKind::IsExpression) } +fn call(p: &mut CParser, left: MarkClosed) -> MarkClosed { + let m = p.start_before(left); + argument_list(p); + p.end(m, TreeKind::CallExpression) +} + const PATTERN_START: &[TokenKind] = &[TokenKind::Identifier, TokenKind::Underscore]; fn pattern(p: &mut CParser, right_power: u8) { diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 4823adf6..39227536 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -422,26 +422,34 @@ fn set_logical_parents( } } } - TreeKind::MemberAccess => { - // The LHS has the environment of my parent, because I set up an - // environment containing only members of the type of the LHS. The - // RHS has that one. - if let Some(lhs) = tree.nth_tree(0) { - set_logical_parents(parents, syntax_tree, lhs, parent); - } - if let Some(rhs) = tree.nth_tree(2) { - set_logical_parents(parents, syntax_tree, rhs, Some(t)); + TreeKind::ConditionalExpression => { + // Special case! The parent of the `then` clause is the + // condition, so any variable bound by the condition is valid in + // the `then` clause. The `else` clause and the condition itself + // do not have the bindings in scope, obviously. + let body_parent = if let Some(is_condition) = tree.nth_tree(1) { + Some(is_condition) + } else { + Some(t) + }; + + let then_body = tree.nth_tree(2); + for child in &tree.children { + match child { + Child::Token(_) => (), + Child::Tree(ct) => { + if Some(*ct) == then_body { + set_logical_parents(parents, syntax_tree, *ct, body_parent); + } else { + set_logical_parents(parents, syntax_tree, *ct, Some(t)); + } + } + } } } - TreeKind::ConditionalExpression => { - // Special case! If the condition expression is a `is` expression - // then it is the parent for the body of the `then` clause so - // that any variable bindings it makes remain in scope. The - // `else` clause and the condition itself do not have the - // bindings in scope, obviously. - let body_parent = if let Some(is_condition) = - tree.child_of_kind(syntax_tree, TreeKind::IsExpression) - { + TreeKind::WhileStatement => { + // Just like `if`, bindings in the condition are valid in the body. + let body_parent = if let Some(is_condition) = tree.nth_tree(1) { Some(is_condition) } else { Some(t) @@ -640,7 +648,6 @@ impl<'a> Semantics<'a> { TreeKind::IsExpression => self.environment_of_is_expression(parent, tree), TreeKind::LetStatement => self.environment_of_let(parent, tree), TreeKind::MatchArm => self.environment_of_match_arm(parent, t, tree), - TreeKind::MemberAccess => self.environment_of_member_access(tree), TreeKind::ParamList => self.environment_of_paramlist(parent, tree), _ => parent, @@ -820,35 +827,6 @@ impl<'a> Semantics<'a> { parent } - fn environment_of_member_access(&self, tree: &Tree) -> EnvironmentRef { - assert_eq!(tree.kind, TreeKind::MemberAccess); - - // Build environment out of members of type of lhs - let Some(lhs) = tree.nth_tree(0) else { - return Environment::error(); - }; - let Some(op) = tree.nth_token(1) else { - return Environment::error(); - }; - let typ = self.type_of(lhs); - match &typ { - Type::Object(ct, _) => { - let class = self.class_of(*ct); - class.env.clone() - } - Type::Class(ct, _) => { - let class = self.class_of(*ct); - class.static_env.clone() - } - Type::Error => Environment::error(), - _ => { - // TODO: This is probably wrong, yeah? - self.report_error(op.start, format!("cannot access members of '{typ}'")); - Environment::error() - } - } - } - fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { assert_eq!(tree.kind, TreeKind::IsExpression); @@ -1156,7 +1134,7 @@ impl<'a> Semantics<'a> { TreeKind::MatchArm => self.type_of_match_arm(tree), TreeKind::MatchBody => self.type_of_match_body(tree), TreeKind::MatchExpression => self.type_of_match_expression(tree), - TreeKind::MemberAccess => self.type_of_member_access(tree), + TreeKind::MemberAccess => self.type_of_member_access(t, tree), TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree), TreeKind::Parameter => self.type_of_parameter(tree), TreeKind::ReturnStatement => Some(Type::Unreachable), @@ -1293,40 +1271,69 @@ impl<'a> Semantics<'a> { op: &Token, ) -> Option { // Ensure the left tree is an lvalue - let environment = self.environment_of(left_tree); let tree = &self.syntax_tree[left_tree]; + + #[allow(unused_assignments)] + let mut environment = Environment::error(); + let declaration = match tree.kind { - // TODO: Assign to member access or list access - TreeKind::Identifier => environment.bind(tree.nth_token(0)?), - _ => None, - }; - match declaration { - Some(d) => match d { - Declaration::Variable { .. } => (), - Declaration::ExternFunction { .. } | Declaration::Function { .. } => { - self.report_error_tree_ref( - left_tree, - "cannot assign a new value to a function declaration", - ); - return Some(Type::Error); + // TODO: Assign to list access + TreeKind::Identifier => { + let id = tree.nth_token(0)?; + environment = self.environment_of(left_tree); + match environment.bind(id) { + Some(decl) => decl, + None => { + if !environment.is_error { + self.report_error_tree(tree, format!("cannot find value {id} here")); + } + return Some(Type::Error); + } } - Declaration::Class { .. } => { - self.report_error_tree_ref( - left_tree, - "cannot assign a new value to a class declaration", - ); - return Some(Type::Error); + } + TreeKind::MemberAccess => { + let id = tree.nth_token(2)?; + let typ = self.type_of(tree.nth_tree(0)?); + environment = self.member_environment(left_tree, &typ); + match environment.bind(id) { + Some(decl) => decl, + None => { + if !environment.is_error { + self.report_error_tree(tree, format!("'{typ}' has no member {id}")); + } + return Some(Type::Error); + } } - }, - None => { + } + _ => { self.report_error_tree_ref( left_tree, "cannot assign a value to this expression, it is not a place you can store things", ); return Some(Type::Error); } + }; + + match declaration { + Declaration::Variable { .. } => (), + Declaration::ExternFunction { .. } | Declaration::Function { .. } => { + self.report_error_tree_ref( + left_tree, + "cannot assign a new value to a function declaration", + ); + return Some(Type::Error); + } + Declaration::Class { .. } => { + self.report_error_tree_ref( + left_tree, + "cannot assign a new value to a class declaration", + ); + return Some(Type::Error); + } } + let _ = environment; + let left_type = match left_type { Type::Assignment(x) => *x, t => t, @@ -1629,11 +1636,43 @@ impl<'a> Semantics<'a> { Some(result) } - fn type_of_member_access(&self, tree: &Tree) -> Option { + fn type_of_member_access(&self, t: TreeRef, tree: &Tree) -> Option { assert_eq!(tree.kind, TreeKind::MemberAccess); - // Type of member access is the type of the RHS. - Some(self.type_of(tree.nth_tree(2)?)) + let lhs = tree.nth_tree(0)?; + let typ = self.type_of(lhs); + let env = self.member_environment(lhs, &typ); + let id = tree.nth_token(2)?; + if id.kind != TokenKind::Identifier { + return Some(Type::Error); + } + + let Some(declaration) = env.bind(id) else { + if !env.is_error { + self.report_error(id.start, format!("'{typ}' has no member {id}")); + } + return Some(Type::Error); + }; + + Some(self.type_of_declaration(t, declaration)) + } + + pub fn member_environment(&self, t: TreeRef, typ: &Type) -> EnvironmentRef { + match &typ { + Type::Object(ct, _) => { + let class = self.class_of(*ct); + class.env.clone() + } + Type::Class(ct, _) => { + let class = self.class_of(*ct); + class.static_env.clone() + } + Type::Error => return Environment::error(), + _ => { + self.report_error_tree_ref(t, format!("cannot access members of '{typ}'")); + return Environment::error(); + } + } } fn type_of_expression_statement(&self, tree: &Tree) -> Option { @@ -1665,17 +1704,7 @@ impl<'a> Semantics<'a> { let id = tree.nth_token(0)?; let environment = self.environment_of(t); if let Some(declaration) = environment.bind(id) { - return Some(match declaration { - Declaration::Variable { declaration, .. } => self.type_of(*declaration), - Declaration::Function { declaration, .. } => self.type_of(*declaration), - Declaration::ExternFunction { - declaration_type, .. - } => declaration_type.clone(), - Declaration::Class { declaration, .. } => match self.type_of(*declaration) { - Type::Object(cd, name) => Type::Class(cd, name.clone()), - _ => self.internal_compiler_error(Some(t), "bound to a class not understood"), - }, - }); + return Some(self.type_of_declaration(t, declaration)); } if !environment.is_error { @@ -1684,6 +1713,20 @@ impl<'a> Semantics<'a> { Some(Type::Error) } + fn type_of_declaration(&self, t: TreeRef, declaration: &Declaration) -> Type { + match declaration { + Declaration::Variable { declaration, .. } => self.type_of(*declaration), + Declaration::Function { declaration, .. } => self.type_of(*declaration), + Declaration::ExternFunction { + declaration_type, .. + } => declaration_type.clone(), + Declaration::Class { declaration, .. } => match self.type_of(*declaration) { + Type::Object(cd, name) => Type::Class(cd, name.clone()), + _ => self.internal_compiler_error(Some(t), "bound to a class not understood"), + }, + } + } + fn type_of_self_parameter(&self, tree: &Tree) -> Option { let pl = tree.parent?; let param_list = &self.syntax_tree[pl]; diff --git a/fine/src/vm.rs b/fine/src/vm.rs index 33de826d..c9295e9b 100644 --- a/fine/src/vm.rs +++ b/fine/src/vm.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::rc::Rc; use crate::compiler::{Export, Function, Instruction, Module}; @@ -48,17 +49,17 @@ type Result = std::result::Result; pub struct Object { name: Rc, class_id: i64, - values: Box<[StackValue]>, + values: RefCell>, } impl Object { pub fn get_slot(&self, index: usize) -> Result { - match self.values.get(index) { + match self.values.borrow().get(index) { Some(v) => Ok(v.clone()), None => Err(VMErrorCode::SlotOutOfRange( index, self.name.clone(), - self.values.len(), + self.values.borrow().len(), )), } } @@ -76,6 +77,43 @@ pub enum StackValue { Object(Rc), } +impl StackValue { + pub fn is_object(&self, id: i64) -> bool { + match self { + StackValue::Object(o) => o.class_id == id, + _ => false, + } + } + + pub fn is_float(&self) -> bool { + match self { + StackValue::Float(_) => true, + _ => false, + } + } + + pub fn is_nothing(&self) -> bool { + match self { + StackValue::Nothing => true, + _ => false, + } + } + + pub fn is_bool(&self) -> bool { + match self { + StackValue::Bool(_) => true, + _ => false, + } + } + + pub fn is_string(&self) -> bool { + match self { + StackValue::String(_) => true, + _ => false, + } + } +} + enum FuncValue { Function(Rc), ExternFunction(usize), @@ -387,6 +425,11 @@ fn eval_one( let v = f.pop_value()?; c.set_global(i, v)?; } + Instruction::StoreSlot(i) => { + let o = f.pop_object()?; + let v = f.pop_value()?; + o.values.borrow_mut()[i] = v; + } Instruction::LoadFunction(i) => { let v = c.get_function(i)?; f.push_function(v); @@ -477,7 +520,7 @@ fn eval_one( let object = Object { name, class_id, - values: values.into(), + values: RefCell::new(values.into()), }; f.push_object(object.into()); @@ -488,16 +531,27 @@ fn eval_one( } Instruction::IsClass(id) => { let value = f.pop_value()?; - match value { - StackValue::Object(o) => { - f.push_bool(o.class_id == id); - } - _ => f.push_bool(false), - } + f.push_bool(value.is_object(id)); } Instruction::PushInt(v) => { f.push_int(v); } + Instruction::IsBool => { + let v = f.pop_value()?; + f.push_bool(v.is_bool()); + } + Instruction::IsFloat => { + let v = f.pop_value()?; + f.push_bool(v.is_float()); + } + Instruction::IsString => { + let v = f.pop_value()?; + f.push_bool(v.is_string()); + } + Instruction::IsNothing => { + let v = f.pop_value()?; + f.push_bool(v.is_nothing()); + } } Ok(Flow::Continue) diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index 955aa879..01989feb 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -42,7 +42,7 @@ fun test() -> f64 { // | 0: LoadArgument(1) // | 1: LoadArgument(0) // | 2: PushString(0) -// | 3: PushInt(37) +// | 3: PushInt(33) // | 4: NewObject(2) // | 5: Return // | function Line (4 args, 0 locals): @@ -52,7 +52,7 @@ fun test() -> f64 { // | 0: LoadArgument(1) // | 1: LoadArgument(0) // | 2: PushString(0) -// | 3: PushInt(44) +// | 3: PushInt(40) // | 4: NewObject(2) // | 5: Return // | function test (0 args, 3 locals): diff --git a/fine/tests/expression/errors/wild_member_access.fine b/fine/tests/expression/errors/wild_member_access.fine index 45778ad5..ae5600f5 100644 --- a/fine/tests/expression/errors/wild_member_access.fine +++ b/fine/tests/expression/errors/wild_member_access.fine @@ -7,15 +7,6 @@ fun test() { let z = f.{let y = 222; foo }; } -// NOTE: The AST allows for generic expressions to the right of the dot. -// We need to make sure things are parsed correctly. -// -// TODO: Better parser recovery will improve the specifics of the errors. -// -// TODO: This is parsed wrong; the `{` is consumed after the '.' and it -// should instead be ignored. This is the "greedy" expression parsing that -// matklad talks about in his resilient parser article. -// // @expect-errors: -// | 7:12: Error at '{': expect ';' to end a let statement +// | 7:12: Error at '{': expected an identifier after a '.' in member access // | 7:26: cannot find value foo here diff --git a/fine/tests/expression/loops.fine b/fine/tests/expression/loops.fine index d0b67c55..848254ab 100644 --- a/fine/tests/expression/loops.fine +++ b/fine/tests/expression/loops.fine @@ -1,3 +1,21 @@ +class Finished {} +let FINISHED = new Finished {}; + +class Iterator { + current: f64; + + fun next(self) -> f64 or Finished { + if self.current < 10 { + let result = self.current; + self.current = self.current + 1; + return result; + } + + FINISHED + } +} + + fun test() -> f64 { let result = 1; let i = 0; @@ -5,8 +23,15 @@ fun test() -> f64 { result = result * 2; i = i + 1; } - result + + let sum = 0; + let it = new Iterator { current: 0 }; + while it.next() is v: f64 { + sum = sum + v; + } + + result + sum } // @no-errors -// @eval: Float(1024.0) \ No newline at end of file +// @eval: Float(1069.0) \ No newline at end of file