From ac3c158a81910d2b63f966f08a22e5057627eedd Mon Sep 17 00:00:00 2001 From: John Doty Date: Mon, 5 Feb 2024 06:11:37 -0800 Subject: [PATCH] [fine] While loops, nothing --- fine/src/compiler.rs | 87 +++++++++++++++---- fine/src/parser.rs | 32 +++++-- fine/src/semantics.rs | 39 +++++++-- fine/src/vm.rs | 46 +++++++--- fine/tests/expression/alternates.fine | 6 +- fine/tests/expression/block.fine | 2 +- .../expression/errors/if_requires_else.fine | 2 +- 7 files changed, 166 insertions(+), 48 deletions(-) diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index ba4300a8..671d7a91 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -15,18 +15,23 @@ pub enum Instruction { BoolNot, Call(usize), - CompareBool, - CompareFloat, - CompareString, Discard, Dup, + EqBool, + EqFloat, + EqString, FloatAdd, FloatDivide, FloatMultiply, FloatSubtract, + GreaterFloat, + GreaterString, + IsClass(i64), Jump(usize), JumpFalse(usize), JumpTrue(usize), // TODO: Only one of these, and use BoolNot? + LessFloat, + LessString, LoadArgument(usize), LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index. LoadFunction(usize), @@ -36,6 +41,7 @@ pub enum Instruction { NewObject(usize), PushFalse, PushFloat(f64), + PushInt(i64), PushNothing, PushString(usize), PushTrue, @@ -44,9 +50,6 @@ pub enum Instruction { StoreLocal(usize), StoreModule(usize), StringAdd, - - IsClass(i64), - PushInt(i64), } pub enum Export { @@ -411,7 +414,8 @@ where } fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { - match tr.nth_token(1)?.kind { + let op = tr.nth_token(1)?; + match op.kind { TokenKind::Plus => compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::FloatAdd, Type::String => Instruction::StringAdd, @@ -426,6 +430,36 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { TokenKind::Slash => { compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatDivide) } + + TokenKind::Less => compile_simple_binary_expression(c, tr, |_, t| match t { + Type::F64 => Instruction::LessFloat, + Type::String => Instruction::LessString, + _ => inst_panic!("panic less {}", t), + }), + TokenKind::LessEqual => { + compile_simple_binary_expression(c, tr, |_, t| match t { + Type::F64 => Instruction::GreaterFloat, + Type::String => Instruction::GreaterString, + _ => inst_panic!("panic less equal {}", t), + }); + c.push(Instruction::BoolNot); + OK + } + TokenKind::Greater => compile_simple_binary_expression(c, tr, |_, t| match t { + Type::F64 => Instruction::GreaterFloat, + Type::String => Instruction::GreaterString, + _ => inst_panic!("panic greater {}", t), + }), + TokenKind::GreaterEqual => { + compile_simple_binary_expression(c, tr, |_, t| match t { + Type::F64 => Instruction::LessFloat, + Type::String => Instruction::LessString, + _ => inst_panic!("panic greater equal {}", t), + }); + c.push(Instruction::BoolNot); + OK + } + TokenKind::And => { // Compile the left hand side, it leaves a bool on the stack compile_expression(c, tr.nth_tree(0)?); @@ -486,9 +520,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { Instruction::PushTrue } else { match arg_type { - Type::F64 => Instruction::CompareFloat, - Type::String => Instruction::CompareString, - Type::Bool => Instruction::CompareBool, // ? + Type::F64 => Instruction::EqFloat, + Type::String => Instruction::EqString, + Type::Bool => Instruction::EqBool, // ? _ => inst_panic!("panic comparing {}", arg_type), } } @@ -545,7 +579,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { } OK } - _ => ice!(c, t, "Unsupported binary expression"), + _ => ice!(c, t, "Unsupported binary expression '{op}'"), } } @@ -898,13 +932,15 @@ fn compile_self_reference(c: &mut Compiler) -> CR { fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) { let tree = &c.semantics.tree()[t]; let cr = match tree.kind { - TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value), - TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value), - TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value), - TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), - TreeKind::IfStatement => compile_if_statement(c, tree, gen_value), TreeKind::Block => compile_block_statement(c, t, gen_value), - _ => ice!(c, t, "unsupported tree kind {:?}", tree.kind), + TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value), + TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), + 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::WhileStatement => compile_while_statement(c, tree, gen_value), + + _ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind), }; if matches!(cr, None) { c.push(inst_panic!("stat {:?}", tree)); @@ -1076,3 +1112,20 @@ fn compile_block_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) -> CR OK } + +fn compile_while_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { + let start_index = c.function.instructions.len(); + compile_expression(c, tree.nth_tree(1)?); + + let jump_end_index = c.push(Instruction::JumpFalse(0)); + + compile_block_statement(c, tree.nth_tree(2)?, false); + c.push(Instruction::Jump(start_index)); + + c.patch(jump_end_index, |i| Instruction::JumpFalse(i)); + if gen_value { + c.push(Instruction::PushNothing); + } + + OK +} diff --git a/fine/src/parser.rs b/fine/src/parser.rs index fd0619b0..cebede90 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -143,10 +143,14 @@ pub enum TreeKind { ListConstructor, ListConstructorElement, LiteralExpression, + MatchArm, + MatchBody, + MatchExpression, MemberAccess, NewObjectExpression, ParamList, Parameter, + Pattern, ReturnStatement, ReturnType, SelfParameter, @@ -156,13 +160,8 @@ pub enum TreeKind { TypeParameter, TypeParameterList, UnaryExpression, - - Pattern, VariableBinding, - - MatchExpression, - MatchBody, - MatchArm, + WhileStatement, WildcardPattern, } @@ -781,6 +780,7 @@ const STATEMENT_RECOVERY: &[TokenKind] = &[ TokenKind::Return, TokenKind::For, TokenKind::Class, + TokenKind::While, ]; fn block(p: &mut CParser) { @@ -813,6 +813,8 @@ fn statement(p: &mut CParser) -> bool { // require a semicolon at the end if it's all by itself. TokenKind::If => statement_if(p), + TokenKind::While => statement_while(p), + _ => { if p.at(TokenKind::Semicolon) || p.at_any(EXPRESSION_FIRST) { statement_expression(p) @@ -834,6 +836,24 @@ fn statement_if(p: &mut CParser) { p.end(m, TreeKind::IfStatement); } +fn statement_while(p: &mut CParser) { + let m = p.start(); + + p.expect_start(TokenKind::While); + if p.at_any(EXPRESSION_FIRST) { + expression(p); + } else { + p.error("expected an expression for the loop condition"); + } + if p.at(TokenKind::LeftBrace) { + block(p); + } else { + p.error("expected a block for the loop body"); + } + + p.end(m, TreeKind::WhileStatement); +} + fn statement_let(p: &mut CParser) { let m = p.start(); diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 1450eec8..4823adf6 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -171,7 +171,7 @@ impl fmt::Display for Type { Error => write!(f, "<< INTERNAL ERROR >>"), Unreachable => write!(f, "<< UNREACHABLE >>"), Assignment(_) => write!(f, "assignment"), - Nothing => write!(f, "()"), + Nothing => write!(f, "nothing"), F64 => write!(f, "f64"), I64 => write!(f, "i64"), String => write!(f, "string"), @@ -1139,9 +1139,9 @@ impl<'a> Semantics<'a> { TreeKind::Block => self.type_of_block(tree), TreeKind::CallExpression => self.type_of_call(tree), TreeKind::ClassDecl => self.type_of_class_decl(t, tree), - TreeKind::FieldDecl => self.type_of_field_decl(tree), TreeKind::ConditionalExpression => self.type_of_conditional(tree), TreeKind::ExpressionStatement => self.type_of_expression_statement(tree), + TreeKind::FieldDecl => self.type_of_field_decl(tree), TreeKind::FieldValue => self.type_of_field_value(t, tree), TreeKind::ForStatement => Some(Type::Nothing), TreeKind::FunctionDecl => self.type_of_function_decl(tree), @@ -1153,6 +1153,9 @@ impl<'a> Semantics<'a> { TreeKind::ListConstructor => self.type_of_list_constructor(t, tree), TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree), TreeKind::LiteralExpression => self.type_of_literal(tree), + 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::NewObjectExpression => self.type_of_new_object_expression(tree), TreeKind::Parameter => self.type_of_parameter(tree), @@ -1164,10 +1167,7 @@ impl<'a> Semantics<'a> { TreeKind::TypeIdentifier => self.type_of_type_identifier(t, tree), TreeKind::TypeParameter => self.type_of_type_parameter(tree), TreeKind::UnaryExpression => self.type_of_unary(tree), - - TreeKind::MatchExpression => self.type_of_match_expression(tree), - TreeKind::MatchBody => self.type_of_match_body(tree), - TreeKind::MatchArm => self.type_of_match_arm(tree), + TreeKind::WhileStatement => Some(Type::Nothing), _ => self.internal_compiler_error(Some(t), "asking for a nonsense type"), }; @@ -1241,6 +1241,16 @@ impl<'a> Semantics<'a> { (TokenKind::EqualEqual, Type::Bool, Type::Bool) => Some(Type::Bool), (TokenKind::EqualEqual, Type::Nothing, Type::Nothing) => Some(Type::Bool), + (TokenKind::Less, Type::F64, Type::F64) => Some(Type::Bool), + (TokenKind::LessEqual, Type::F64, Type::F64) => Some(Type::Bool), + (TokenKind::Greater, Type::F64, Type::F64) => Some(Type::Bool), + (TokenKind::GreaterEqual, Type::F64, Type::F64) => Some(Type::Bool), + + (TokenKind::Less, Type::String, Type::String) => Some(Type::Bool), + (TokenKind::LessEqual, Type::String, Type::String) => Some(Type::Bool), + (TokenKind::Greater, Type::String, Type::String) => Some(Type::Bool), + (TokenKind::GreaterEqual, Type::String, Type::String) => Some(Type::Bool), + // This is dumb and should be punished, probably. (_, _, Type::Unreachable) => { self.report_error( @@ -1353,7 +1363,7 @@ impl<'a> Semantics<'a> { "f64" => Some(Type::F64), "string" => Some(Type::String), "bool" => Some(Type::Bool), - "()" => Some(Type::Nothing), + "nothing" => Some(Type::Nothing), "list" => { let args = tree.child_tree_of_kind(self.syntax_tree, TreeKind::TypeParameterList)?; @@ -2041,6 +2051,8 @@ pub fn check(s: &Semantics) { TreeKind::MatchArm => {} TreeKind::MatchBody => check_match_body(s, t, tree), TreeKind::MatchExpression => {} + + TreeKind::WhileStatement => check_while_statement(s, tree), } } } @@ -2240,6 +2252,7 @@ fn check_match_body(s: &Semantics, t: TreeRef, _tree: &Tree) { let _ = s.type_of(t); // Checks arm count and compatibility. // TODO: completeness checks + // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/usefulness/index.html // let arms: Vec<_> = tree // .children_of_kind(s.syntax_tree, TreeKind::MatchArm) @@ -2253,6 +2266,18 @@ fn check_match_body(s: &Semantics, t: TreeRef, _tree: &Tree) { // } } +fn check_while_statement(s: &Semantics, tree: &Tree) { + if let Some(expr) = tree.nth_tree(1) { + let expr_type = s.type_of(expr); + if !s.can_convert(&expr_type, &Type::Bool) { + s.report_error_tree_ref( + expr, + "the condition of the while loop must produce a boolean", + ); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/fine/src/vm.rs b/fine/src/vm.rs index 96895258..33de826d 100644 --- a/fine/src/vm.rs +++ b/fine/src/vm.rs @@ -430,21 +430,41 @@ fn eval_one( f.push_string(new_string.into()); } - Instruction::CompareBool => { + Instruction::EqBool => { let x = f.pop_bool()?; let y = f.pop_bool()?; f.push_bool(x == y); } - Instruction::CompareFloat => { + Instruction::EqFloat => { let x = f.pop_float()?; let y = f.pop_float()?; f.push_bool(x == y); } - Instruction::CompareString => { + Instruction::EqString => { let x = f.pop_string()?; let y = f.pop_string()?; f.push_bool(x == y); } + Instruction::GreaterFloat => { + let x = f.pop_float()?; + let y = f.pop_float()?; + f.push_bool(y > x); + } + Instruction::GreaterString => { + let x = f.pop_string()?; + let y = f.pop_string()?; + f.push_bool(y > x); + } + Instruction::LessFloat => { + let x = f.pop_float()?; + let y = f.pop_float()?; + f.push_bool(y < x); + } + Instruction::LessString => { + let x = f.pop_string()?; + let y = f.pop_string()?; + f.push_bool(y < x); + } Instruction::NewObject(slots) => { let class_id = f.pop_int()?; @@ -496,16 +516,16 @@ pub fn eval( let instructions = f.func.instructions(); let instruction = instructions[index]; - { - eprint!("{index}: {instruction:?} ["); - for val in f.stack.iter().rev().take(3) { - eprint!("{val:?} "); - } - if f.stack.len() > 3 { - eprint!("..."); - } - eprintln!("]"); - } + // { + // eprint!("{index}: {instruction:?} ["); + // for val in f.stack.iter().rev().take(3) { + // eprint!("{val:?} "); + // } + // if f.stack.len() > 3 { + // eprint!("..."); + // } + // eprintln!("]"); + // } index += 1; diff --git a/fine/tests/expression/alternates.fine b/fine/tests/expression/alternates.fine index 97bb3007..8b922480 100644 --- a/fine/tests/expression/alternates.fine +++ b/fine/tests/expression/alternates.fine @@ -46,7 +46,7 @@ fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) // from the `is` binding in scope. if weapon is MeleeWeapon and distance > 1 or weapon is w : RangedWeapon and (distance < w.minRange or distance > w.maxRange) { - print("You are out of range") + print("You are out of range"); return } @@ -72,7 +72,7 @@ fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) } } -fun more_examples(weapon: MeleeWeapon or RangedWeapon) -> f64 or () { +fun more_examples(weapon: MeleeWeapon or RangedWeapon) -> f64 or nothing { if weapon is w: RangedWeapon and w.maxRange > 10 { // w is still in scope here; the `and` is bound into a predicate expression // and breaks exhaustivity @@ -115,7 +115,7 @@ fun test() -> f64 { // Unroll by hand... let it = new Iterator { current: 0 }; - loop { + while true { if it.next() is v: f64 { sum = sum + v; } else { diff --git a/fine/tests/expression/block.fine b/fine/tests/expression/block.fine index 6b9f348d..c6785fa2 100644 --- a/fine/tests/expression/block.fine +++ b/fine/tests/expression/block.fine @@ -16,7 +16,7 @@ fun test() { // | 1: Return // | // @eval: Nothing -// @type: 15 () +// @type: 15 nothing // @concrete: // | File // | FunctionDecl diff --git a/fine/tests/expression/errors/if_requires_else.fine b/fine/tests/expression/errors/if_requires_else.fine index bdc5a229..153134b8 100644 --- a/fine/tests/expression/errors/if_requires_else.fine +++ b/fine/tests/expression/errors/if_requires_else.fine @@ -1,4 +1,4 @@ if (if false { true }) { 32 } else { 23 } // @expect-errors: -// | 1:4: the type of the 'then' branch ('bool') must match the type of the 'else' branch ('()') +// | 1:4: the type of the 'then' branch ('bool') must match the type of the 'else' branch ('nothing')