diff --git a/build.rs b/build.rs index 46ee1e4..beaf7ac 100644 --- a/build.rs +++ b/build.rs @@ -64,6 +64,10 @@ fn do_ast_codegen(mut output: W) { ("Block", "statements: Vec"), ("Print", "expression: Expr"), ("Var", "name: Token, initializer: Option"), + ( + "If", + "condition: Expr, then_branch: Box, else_branch: Option>", + ), ]); define_ast(&mut output, "Stmt", &statement_types).expect("failed to generate ast values"); @@ -156,11 +160,16 @@ fn define_visitor_trait_method( let field_name = field_components.next().unwrap(); let field_raw_type = field_components.next().unwrap(); // Filthy hack for box types, but we don't use any other non-referential type right now - let stripped_field_type = field_raw_type - .strip_prefix("Box<") - .and_then(|val| val.strip_suffix('>')) - .unwrap_or(field_raw_type); - let arg_type = format!("&{stripped_field_type}"); + let arg_type = if field_raw_type.starts_with("Box<") { + format!( + "&{}", + field_raw_type.replacen("Box<", "", 1).replacen('>', "", 1) + ) + } else if field_raw_type.starts_with("Option', "", 1) + } else { + format!("&{}", field_raw_type) + }; if i == fields.len() - 1 { write!(w, "{field_name}: {arg_type}")?; @@ -241,6 +250,8 @@ fn write_comma_separated_field_names_with_ref( // Filthy box hack again let reference_name = if field_type.starts_with("Box") { format!("{field_name}.as_ref()") + } else if field_type.starts_with("Option> for InterpreterRunner<'_> { result } + + fn visit_if( + &mut self, + condition: &Expr, + then_branch: &Stmt, + else_branch: Option<&Stmt>, + ) -> Result<(), ScriptError> { + let evaluated_cond = self.visit_expr(condition)?; + if evaluated_cond.is_truthy() { + self.visit_stmt(then_branch) + } else if let Some(branch) = else_branch { + self.visit_stmt(branch) + } else { + Ok(()) + } + } } fn convert_arithmetic_operands( diff --git a/src/parse.rs b/src/parse.rs index a4602f8..e5e3c56 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -168,7 +168,9 @@ fn parse_var_declaration>( fn parse_statement>( iter: &mut Peekable, ) -> Result { - if match_token_kind!(iter, TokenKind::Print).is_ok() { + if let Ok(token) = match_token_kind!(iter, TokenKind::If) { + parse_if_statement(iter, &token) + } else if match_token_kind!(iter, TokenKind::Print).is_ok() { parse_statement_containing_expression( iter, |expression| Stmt::Print { expression }, @@ -186,6 +188,37 @@ fn parse_statement>( } } +fn parse_if_statement>( + iter: &mut Peekable, + if_token: &Token, +) -> Result { + match_token_kind!(iter, TokenKind::LeftParen).map_err(|_| ParseError { + message: "Expected '(' after 'if'".to_string(), + line: if_token.line().into(), + })?; + + let condition = parse_expression(iter)?; + + match_token_kind!(iter, TokenKind::RightParen).map_err(|_| ParseError { + message: "Expected ')' after 'if'".to_string(), + line: if_token.line().into(), + })?; + + let then_branch = parse_statement(iter).and_then(ensure_explicit_statement)?; + let else_branch = match match_token_kind!(iter, TokenKind::Else) { + Ok(_) => Some(parse_statement(iter).and_then(ensure_explicit_statement)?), + Err(_) => None, + }; + + let if_stmt = Stmt::If { + condition, + then_branch: Box::new(then_branch), + else_branch: else_branch.map(Box::new), + }; + + Ok(ParsedStatement::Stmt { stmt: if_stmt }) +} + fn parse_statement_containing_expression< I: Iterator, F: Fn(Expr) -> Stmt,