luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use std::rc::Rc;

use crate::ast::{Ass, Block, Exp, For, Func, If, Local, Parser, PrefixExp, Repeat, While};
use crate::error::{LuaError, Result};
use crate::lexer::{Pos, Spanned, Token};
use crate::vm::Literal;

/// ```text
/// stat ::=  ‘;’ |
///      varlist ‘=’ explist |
///      functioncall |
///      label |
///      break |
///      goto Name |
///      do block end |
///      while exp do block end |
///      repeat block until exp |
///      if exp then block {elseif exp then block} [else block] end |
///      for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end |
///      for namelist in explist do block end |
///      function funcname funcbody |
///      local function Name funcbody |
///      local attnamelist [‘=’ explist]
/// label ::= ‘::’ Name ‘::’
/// funcname ::= Name {‘.’ Name} [‘:’ Name]
/// ```
pub enum Stat {
    Nop,
    Ass(Ass),
    Call(Pos, PrefixExp, Vec<Exp>),
    CallMethod(Pos, Box<PrefixExp>, String, Vec<Exp>),
    Func(Option<PrefixExp>, String, Func),
    LocalFunc(String, Func),
    Local(Local),
    Do(Block),
    If(If),
    Break(Pos),
    While(While),
    Repeat(Repeat),
    For(For),
    Goto(String, Pos),
    Label(String, Pos),
}

impl<'a> Parser<'a> {
    pub(super) fn parse_stat(&mut self) -> Result<Stat> {
        Ok(match self.tok_peek()?.into() {
            (_, Token::Semi) => {
                self.tok_next()?;
                Stat::Nop
            }
            (_, Token::Ident(_) | Token::ParO) => {
                let prefix_exp = self.parse_prefix_exp()?;

                match prefix_exp {
                    PrefixExp::Var(..) | PrefixExp::Index(..) => {
                        Stat::Ass(self.parse_ass(prefix_exp)?)
                    }
                    PrefixExp::Call(pos, func, args) => Stat::Call(pos, *func, args),
                    PrefixExp::CallMethod(pos, table, name, args) => {
                        Stat::CallMethod(pos, table, name, args)
                    }
                    PrefixExp::Par(_, pos) => {
                        return err!(self.source, pos, LuaError::UnexpectedToken(Token::ParO))
                    }
                }
            }
            (_, Token::Local) => {
                self.tok_next()?;
                match self.tok_peek_opt()?.map(Into::into) {
                    Some((_, Token::Function)) => {
                        self.tok_next()?;
                        let (_, name) = self.tok_ident()?;
                        let func = self.parse_func()?;
                        Stat::LocalFunc(name, func)
                    }
                    _ => Stat::Local(self.parse_local()?),
                }
            }
            (_, Token::Function) => {
                self.tok_next()?;
                // Use prefix expression for this, makes compilation easier
                // Prefix expressions are however complexer than need be for parsing this, but whatever
                let mut prefix = None;
                let (pos_name, mut name) = self.tok_ident()?;
                let mut is_method = false;
                // Can only have a single method assignment at end, stop looping when one was found
                while !is_method {
                    match self.tok_peek_opt()?.map(Spanned::inner) {
                        Some(Token::Point) => {}
                        Some(Token::Colon) => {
                            is_method = true;
                        }
                        _ => break,
                    }
                    self.tok_next()?;
                    let (pos_ind, index) = self.tok_ident()?;
                    match prefix {
                        Some(pfx) => {
                            prefix = Some(PrefixExp::Index(
                                Box::new(pfx),
                                Box::new(Exp::Lit(
                                    Literal::String(Rc::new(name.into_bytes())),
                                    pos_name,
                                )),
                                pos_ind,
                            ))
                        }
                        None => prefix = Some(PrefixExp::Var(name, pos_name)),
                    }
                    name = index;
                }
                let mut func = self.parse_func()?;
                // Method assignment is syntactic sugar for regular assignment,
                // but with an additional parameter 'self' in the first position
                if is_method {
                    func.params.insert(0, "self".to_string());
                }
                Stat::Func(prefix, name, func)
            }
            (_, Token::Do) => {
                self.tok_next()?;
                let block = self.parse_block()?;
                tok_expect!(self, Token::End);
                Stat::Do(block)
            }
            (_, Token::If) => Stat::If(self.parse_if()?),
            (pos, Token::Break) => {
                self.tok_next()?;
                Stat::Break(pos)
            }
            (_, Token::While) => Stat::While(self.parse_while()?),
            (_, Token::Repeat) => Stat::Repeat(self.parse_repeat()?),
            (_, Token::For) => Stat::For(self.parse_for()?),
            (pos, Token::Goto) => {
                self.tok_next()?;
                let (_, label) = self.tok_ident()?;
                Stat::Goto(label, pos)
            }
            (pos, Token::Ass) => {
                self.tok_next()?;
                let (_, label) = self.tok_ident()?;
                tok_expect!(self, Token::Ass);
                Stat::Label(label, pos)
            }
            (pos, tok) => return err!(&*self.source, pos, LuaError::UnexpectedToken(tok.clone())),
        })
    }
}