diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b20d751..dce9eb0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -79,6 +79,7 @@ impl Parser { let token = self.lexer.peek_or_err()?; match token.kind { token::Kind::DependKeyword => self.parse_depend_stmt(), + token::Kind::IfKeyword => self.parse_if_stmt(), token::Kind::SetKeyword => self.parse_set_stmt(), token::Kind::SourceKeyword => self.parse_source_stmt(), token::Kind::TargetKeyword => self.parse_target_stmt(), @@ -88,6 +89,28 @@ impl Parser { } } + /// ```notrust + /// BlockStatement + /// : "{" [ StatementList ] "}" + /// + /// StatementList + /// : Statement [ StatementList ] + /// ``` + fn parse_block_stmt(&mut self) -> Result { + let mut nodes = Vec::new(); + self.lexer.expect_kind(token::Kind::OBrace)?; + while let Some(result) = self.lexer.peek() { + match result?.kind { + token::Kind::CBrace => { + self.lexer.next(); + break; + } + _ => nodes.push(self.parse_stmt()?), + } + } + Ok(tree::Node::Block(nodes)) + } + /// ```notrust /// TargetStatement /// : "target" Expression BlockStatement @@ -99,24 +122,13 @@ impl Parser { self.lexer.expect_kind(token::Kind::TargetKeyword)?; let name_token = self.lexer.expect_kind(token::Kind::Ident)?; - self.lexer.expect_kind(token::Kind::OBrace)?; - let mut children = Vec::new(); - - while let Some(result) = self.lexer.peek() { - match result?.kind { - token::Kind::CBrace => { - self.lexer.next(); - break; - } - _ => children.push(self.parse_stmt()?), - } - } + let children = self.parse_block_stmt()?; self.scope.pop(); Ok(tree::Node::Target { name: Box::new(tree::Node::Ident(name_token.raw)), - content: children, + content: Box::new(children), }) } @@ -133,6 +145,29 @@ impl Parser { Ok(tree::Node::DepList(Box::new(rvalue))) } + /// ```notrust + /// IfStatement + /// : "if" "(" Expression ")" BlockStatement [ "else" BlockStatement ] + /// ``` + fn parse_if_stmt(&mut self) -> Result { + self.lexer.expect_kind(token::Kind::IfKeyword)?; + self.lexer.expect_kind(token::Kind::OParen)?; + let condition = self.parse_expr(&[token::Kind::CParen])?; + let then_block = self.parse_block_stmt()?; + let mut else_block = None; + if let Some(Ok(token)) = self.lexer.peek() { + if token.kind == token::Kind::ElseKeyword { + self.lexer.next(); + else_block = Some(Box::new(self.parse_block_stmt()?)); + } + } + Ok(tree::Node::IfStmt { + condition: Box::new(condition), + then_block: Box::new(then_block), + else_block, + }) + } + /// ```notrust /// SetStatement /// : "set" AssignmentExpression ";" @@ -216,6 +251,13 @@ impl Parser { expr } + /// Parse an assignment expression. + /// This is no different to parsing any other binary expression, with the + /// difference being that assignment operators are right associative. + /// Therefore, we need a separate function for this. Other than that, + /// this method is no different to `parse_binary_expr_or_higher()` (it even + /// returns the same kind of tree node). + /// /// ```notrust /// AssignmentExpression /// : PrimaryExpression AssignmentOperator Expression @@ -228,6 +270,11 @@ impl Parser { &mut self, terminators: &[token::Kind], ) -> Result { + // we speculate on this being an assignment expression, so we need to + // be able to undo our work in case this speculation doesn't hold true + // so parse_binary_expr_or_higher() can do its thing + let bookmark = self.lexer.save(); + let lhs = self.parse_primary_expr()?; if let Some(Ok(token)) = self.lexer.peek() { if token.kind.is_assignment_op() { @@ -239,6 +286,10 @@ impl Parser { lhs: Box::new(lhs), rhs: Box::new(rhs), }); + } else if token.kind.binary_op_precedence().is_some() { + // shoot, this wasn't an assignment, all of our work was useless + self.lexer.restore(bookmark); + return self.parse_binary_expr_or_higher(terminators); } else { self.lexer.expect_kinds(terminators)?; } @@ -246,7 +297,8 @@ impl Parser { Ok(lhs) } - /// Binary expressions are generally left associative. + /// Binary expressions are generally left associative (except for assignments, + /// which are handled separately in `parse_assignment_expr_or_higher()`). /// However, things get a little more tricky when taking the fact that there /// are 9 different levels of precedence into account. /// @@ -255,6 +307,7 @@ impl Parser { /// : Expression BinaryOperator Expression /// /// BinaryOperator + /// : "||" | "&&" | "==" | "!=" | "<" | "<=" | ">" | ">=" /// : "|" | "^" | "&" | "<<" | ">>" | "+" | "-" | "*" | "/" | "%" /// ``` fn parse_binary_expr_or_higher( diff --git a/src/ast/tree.rs b/src/ast/tree.rs index 33e9961..12d2054 100644 --- a/src/ast/tree.rs +++ b/src/ast/tree.rs @@ -14,6 +14,7 @@ pub enum Node { Int(i128), String(String), Array(Vec), + Block(Vec), ArrayExpr { // array access array: Box, @@ -24,6 +25,11 @@ pub enum Node { func: Box, params: Vec, }, + IfStmt { + condition: Box, + then_block: Box, + else_block: Option>, + }, UnaryExpr { op: Operator, node: Box, @@ -40,7 +46,7 @@ pub enum Node { }, Target { name: Box, - content: Vec, + content: Box, }, File { name: String, @@ -100,6 +106,11 @@ impl Node { node.visit(cb, depth); } } + Node::Block(statements) => { + for node in statements { + node.visit(cb, depth); + } + } Node::ArrayExpr { array, index } => { array.visit(cb, depth); index.visit(cb, depth); @@ -110,6 +121,13 @@ impl Node { p.visit(cb, depth); } } + Node::IfStmt { condition, then_block, else_block} => { + condition.visit(cb, depth); + then_block.visit(cb, depth); + if let Some(eb) = else_block { + eb.visit(cb, depth); + } + } Node::UnaryExpr { op, node } => node.visit(cb, depth), Node::BinaryExpr { op, lhs, rhs } => { lhs.visit(cb, depth); @@ -122,9 +140,7 @@ impl Node { } Node::Target { name, content } => { name.visit(cb, depth); - for n in content { - n.visit(cb, depth); - } + content.visit(cb, depth); } Node::File { name, content } => { for n in content { @@ -149,11 +165,13 @@ impl fmt::Display for Node { tmp.as_str() } Node::String(s) => s.as_str(), + Node::Block(_) => "", Node::DepList(_) => "depend", Node::SourceList(_) => "source", Node::Array(_) => "", Node::ArrayExpr { array, index } => "", Node::CallExpr { func, params } => "", + Node::IfStmt { condition, then_block, else_block } => "", Node::UnaryExpr { op, node } => op.raw(), Node::BinaryExpr { op, lhs, rhs } => op.raw(), Node::TypeExpr(_) => "type", diff --git a/src/lex/mod.rs b/src/lex/mod.rs index a39991f..b764f55 100644 --- a/src/lex/mod.rs +++ b/src/lex/mod.rs @@ -15,6 +15,12 @@ pub struct Lexer { token_col: usize, } +/// A simple bookmark for restoring the lexer's state in case of a failed lookahead. +pub struct Bookmark { + /// Index within `Lexer.history` + index: usize, +} + static NUMERALS: [char; 16] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', ]; @@ -29,8 +35,10 @@ const fn kw(raw: &'static str, kind: token::Kind) -> KeywordMap { KeywordMap { raw, kind } } -static KEYWORDS: [KeywordMap; 6] = [ +static KEYWORDS: [KeywordMap; 8] = [ + kw("else", token::Kind::ElseKeyword), kw("depend", token::Kind::DependKeyword), + kw("if", token::Kind::IfKeyword), kw("include", token::Kind::IncludeKeyword), kw("set", token::Kind::SetKeyword), kw("source", token::Kind::SourceKeyword), @@ -132,6 +140,18 @@ impl Lexer { } } + /// Save the lexer's state to perform a speculative lookahead. + pub fn save(&mut self) -> Bookmark { + Bookmark { + index: self.history.len() - self.offset, + } + } + + /// Restore the lexer's state in case of a failed lookahead. + pub fn restore(&mut self, bookmark: Bookmark) { + self.offset = self.history.len() - bookmark.index; + } + pub fn current(&self) -> Option<&Token> { if self.history.len() > 0 { Some(&self.history[self.history.len() - self.offset - 1]) diff --git a/src/lex/token.rs b/src/lex/token.rs index 5028be4..1a95714 100644 --- a/src/lex/token.rs +++ b/src/lex/token.rs @@ -70,7 +70,9 @@ pub enum Kind { Percent, PercentEq, + ElseKeyword, DependKeyword, + IfKeyword, IncludeKeyword, TargetKeyword, SetKeyword, @@ -212,7 +214,9 @@ impl fmt::Display for Kind { Kind::Percent => "percent", Kind::PercentEq => "percenteq", + Kind::ElseKeyword => "keyword", Kind::DependKeyword => "keyword", + Kind::IfKeyword => "keyword", Kind::IncludeKeyword => "keyword", Kind::SetKeyword => "keyword", Kind::SourceKeyword => "keyword", diff --git a/test.gaybuild b/test.gaybuild index ade8328..18f05c2 100644 --- a/test.gaybuild +++ b/test.gaybuild @@ -9,6 +9,11 @@ set BUILD_PREFIX = "build"; # a target is a single component. # targets can depend on other targets. target kern { + if (2 > 3) { + print("2 > 3"); + } else { + print("2 <= 3"); + } owo = 1 + 2 * 3 * 4 - 5;