add support for if statements

This commit is contained in:
anna 2022-07-28 19:52:15 +02:00
parent 05e73aeb21
commit cb98c78b37
Signed by: fef
GPG key ID: EC22E476DC2D3D84
5 changed files with 119 additions and 19 deletions

View file

@ -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<tree::Node, Error> {
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<tree::Node, Error> {
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<tree::Node, Error> {
// 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(

View file

@ -14,6 +14,7 @@ pub enum Node {
Int(i128),
String(String),
Array(Vec<Node>),
Block(Vec<Node>),
ArrayExpr {
// array access
array: Box<Node>,
@ -24,6 +25,11 @@ pub enum Node {
func: Box<Node>,
params: Vec<Node>,
},
IfStmt {
condition: Box<Node>,
then_block: Box<Node>,
else_block: Option<Box<Node>>,
},
UnaryExpr {
op: Operator,
node: Box<Node>,
@ -40,7 +46,7 @@ pub enum Node {
},
Target {
name: Box<Node>,
content: Vec<Node>,
content: Box<Node>,
},
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(_) => "<block>",
Node::DepList(_) => "depend",
Node::SourceList(_) => "source",
Node::Array(_) => "<array>",
Node::ArrayExpr { array, index } => "<array-access>",
Node::CallExpr { func, params } => "<call>",
Node::IfStmt { condition, then_block, else_block } => "<if>",
Node::UnaryExpr { op, node } => op.raw(),
Node::BinaryExpr { op, lhs, rhs } => op.raw(),
Node::TypeExpr(_) => "type",

View file

@ -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])

View file

@ -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",

View file

@ -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;