luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use crate::ast::{Exp, Parser, Stat};
use crate::error::{LuaError, Result};
use crate::lexer::{Pos, Spanned, Token};

use super::MAX_SCOPE;

/// ```text
/// block ::= {stat} [retstat]
/// retstat ::= return [explist] [‘;’]
/// ```
pub struct Block {
    pub stats: Vec<Stat>,
    pub ret: Option<(Vec<Exp>, Pos)>,
}

impl<'a> Parser<'a> {
    pub(super) fn parse_block(&mut self) -> Result<Block> {
        let mut stats = Vec::new();
        let mut ret = None;

        self.scope_depth += 1;
        if self.scope_depth > MAX_SCOPE {
            return err!(LuaError::SyntaxLimit("nested scopes", None));
        }

        loop {
            match self.tok_peek_opt()?.map(Into::into) {
                None | Some((_, Token::Elseif | Token::Else | Token::Until | Token::End)) => break,
                Some((pos, Token::Return)) => {
                    self.tok_next()?;
                    ret = Some((vec![], pos));

                    // Skip parsing expressions if there are none
                    match self.tok_peek_opt()?.map(Spanned::inner) {
                        Some(Token::Elseif | Token::Else | Token::Until | Token::End) => break,
                        Some(Token::Semi) => {
                            self.tok_next()?;
                            break;
                        }
                        None => break, // Eof could be valid here
                        _ => {}
                    }

                    let mut exps = vec![self.parse_exp()?];
                    while let Some(Token::Comma) = self.tok_peek_opt()?.map(Spanned::inner) {
                        self.tok_next()?;
                        exps.push(self.parse_exp()?);
                    }
                    ret = Some((exps, pos));

                    // Parse optional semicolon
                    if let Some(Token::Semi) = self.tok_peek_opt()?.map(Spanned::inner) {
                        self.tok_next()?;
                    }
                    break;
                }
                _ => stats.push(self.parse_stat()?),
            }
        }

        self.scope_depth -= 1;
        Ok(Block { stats, ret })
    }
}