Parser - Loop control statements
* Store a loop parsing state when entering a loop. The parser uses this to cause an error when break/continue are found outside a loop. * Parse break and continue statements w/ optional identifier
This commit is contained in:
parent
f3b2552679
commit
bf8eee7ecb
1 changed files with 106 additions and 6 deletions
112
src/parser.rs
112
src/parser.rs
|
@ -9,6 +9,16 @@ use crate::{
|
|||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
current: usize,
|
||||
loop_state: Vec<LoopParsingState>,
|
||||
}
|
||||
|
||||
/// The state of the parser regarding loops. We may be parsing an unnamed or
|
||||
/// named loop, or we might not be parsing a loop at all.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LoopParsingState {
|
||||
NoLoop,
|
||||
UnnamedLoop,
|
||||
NamedLoop(String),
|
||||
}
|
||||
|
||||
/// The result of one of the parser's functions.
|
||||
|
@ -17,13 +27,20 @@ type ParserResult<T> = Result<T, ParserError>;
|
|||
impl Parser {
|
||||
/// Initialize the parser.
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, current: 0 }
|
||||
Self {
|
||||
tokens,
|
||||
current: 0,
|
||||
loop_state: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the tokens into an AST and return it, or return nothing if a
|
||||
/// parser error occurs.
|
||||
pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> Option<ast::ProgramNode> {
|
||||
self.parse_program(err_hdl)
|
||||
self.loop_state.push(LoopParsingState::NoLoop);
|
||||
let result = self.parse_program(err_hdl);
|
||||
self.loop_state.pop();
|
||||
result
|
||||
}
|
||||
|
||||
/// Synchronize the parser after an error.
|
||||
|
@ -83,6 +100,7 @@ impl Parser {
|
|||
/// statement := if_statement
|
||||
/// statement := while_statement
|
||||
/// statement := for_statement
|
||||
/// statement := loop_control_statement
|
||||
/// ```
|
||||
fn parse_statement(&mut self) -> ParserResult<ast::StmtNode> {
|
||||
if self.expect(&[TokenType::Var]).is_some() {
|
||||
|
@ -92,9 +110,14 @@ impl Parser {
|
|||
} else if self.expect(&[TokenType::If]).is_some() {
|
||||
self.parse_if_statement()
|
||||
} else if self.expect(&[TokenType::While]).is_some() {
|
||||
self.parse_while_statement()
|
||||
self.loop_state.push(LoopParsingState::UnnamedLoop);
|
||||
let result = self.parse_while_statement();
|
||||
self.loop_state.pop();
|
||||
result
|
||||
} else if self.expect(&[TokenType::For]).is_some() {
|
||||
self.parse_for_statement()
|
||||
self.parse_for_statement(LoopParsingState::UnnamedLoop)
|
||||
} else if let Some(lcs) = self.expect(&[TokenType::Break, TokenType::Continue]) {
|
||||
self.parse_loop_control_statement(&lcs)
|
||||
} else if self.expect(&[TokenType::Print]).is_some() {
|
||||
let expression = self.parse_expression()?;
|
||||
self.consume(&TokenType::Semicolon, "expected ';' after value")?;
|
||||
|
@ -195,7 +218,7 @@ impl Parser {
|
|||
/// for_initializer := expression
|
||||
/// for_initializer :=
|
||||
/// ```
|
||||
fn parse_for_statement(&mut self) -> ParserResult<ast::StmtNode> {
|
||||
fn parse_for_statement(&mut self, loop_state: LoopParsingState) -> ParserResult<ast::StmtNode> {
|
||||
self.consume(&TokenType::LeftParen, "expected '(' after 'for'")?;
|
||||
|
||||
let initializer = if self.expect(&[TokenType::Semicolon]).is_some() {
|
||||
|
@ -234,7 +257,12 @@ impl Parser {
|
|||
|
||||
// Generate a while loop, with an optional initializer which may be
|
||||
// inside a specific block if the initializer declares a variable.
|
||||
let body_stmt = self.parse_statement()?;
|
||||
let body_stmt = {
|
||||
self.loop_state.push(loop_state);
|
||||
let result = self.parse_statement();
|
||||
self.loop_state.pop();
|
||||
result?
|
||||
};
|
||||
let body_with_incr = if let Some(incr) = increment {
|
||||
let incr_stmt = Box::new(ast::StmtNode::Expression(incr));
|
||||
let body_block = if let ast::StmtNode::Block(mut body_block) = body_stmt {
|
||||
|
@ -261,6 +289,45 @@ impl Parser {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse the following rule:
|
||||
/// ```
|
||||
/// loop_control_statement := "break" ( IDENTIFIER )? ";"
|
||||
/// loop_control_statement := "continue" ( IDENTIFIER )? ";"
|
||||
/// ```
|
||||
fn parse_loop_control_statement(&mut self, stmt_token: &Token) -> ParserResult<ast::StmtNode> {
|
||||
if self.loop_state() == &LoopParsingState::NoLoop {
|
||||
return Err(ParserError::new(
|
||||
stmt_token,
|
||||
&format!(
|
||||
"'{}' statement found outside of loop body",
|
||||
stmt_token.lexeme
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let loop_name = if let TokenType::Identifier(_) = self.peek().token_type {
|
||||
let name_token = self.advance().clone();
|
||||
if !self.find_named_loop(&name_token.lexeme) {
|
||||
return Err(ParserError::new(
|
||||
&name_token,
|
||||
&format!("no reachable loop named '{}'", name_token.lexeme),
|
||||
));
|
||||
}
|
||||
Some(name_token)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.consume(
|
||||
&TokenType::Semicolon,
|
||||
"';' expected after loop control statement",
|
||||
)?;
|
||||
Ok(ast::StmtNode::LoopControlStmt {
|
||||
is_break: stmt_token.token_type == TokenType::Break,
|
||||
loop_name,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the following rule:
|
||||
/// ```
|
||||
/// expression := assignment
|
||||
|
@ -509,4 +576,37 @@ impl Parser {
|
|||
fn previous(&self) -> &Token {
|
||||
&self.tokens[self.current - 1]
|
||||
}
|
||||
|
||||
/// Execute some code while setting the loop state. Once the function
|
||||
/// is done running, remove the loop state.
|
||||
fn with_loop_state<F, T>(&mut self, loop_state: LoopParsingState, mut function: F) -> T
|
||||
where
|
||||
F: FnMut() -> T,
|
||||
{
|
||||
self.loop_state.push(loop_state);
|
||||
let result = function();
|
||||
self.loop_state.pop();
|
||||
result
|
||||
}
|
||||
|
||||
/// Take a peek at the current loop state.
|
||||
fn loop_state(&self) -> &LoopParsingState {
|
||||
&self.loop_state[self.loop_state.len() - 1]
|
||||
}
|
||||
|
||||
/// Find a loop with a given name in the loop state. Stops looking when
|
||||
/// the first non-loop entry is reached.
|
||||
fn find_named_loop(&self, name: &str) -> bool {
|
||||
let mut pos = self.loop_state.len() - 1;
|
||||
loop {
|
||||
match &self.loop_state[pos] {
|
||||
LoopParsingState::NoLoop => break,
|
||||
LoopParsingState::UnnamedLoop => (),
|
||||
LoopParsingState::NamedLoop(n) if n == name => return true,
|
||||
LoopParsingState::NamedLoop(_) => (),
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue