luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use crate::ast::{Func, Parser, PrefixExp, TableField};
use crate::error::{LuaError, Result};
use crate::lexer::{Pos, Spanned, Token};
use crate::vm::{BinOp, Literal, UnOp};

use super::MAX_EXP;

/// ```text
/// exp ::=  nil | false | true | Numeral | LiteralString | ‘...’ | functiondef |
///          prefixexp | tableconstructor | exp binop exp | unop exp
/// binop ::=  ‘+’ | ‘-’ | ‘*’ | ‘/’ | ‘//’ | ‘^’ | ‘%’ |
///            ‘&’ | ‘~’ | ‘|’ | ‘>>’ | ‘<<’ | ‘..’ |
///            ‘<’ | ‘<=’ | ‘>’ | ‘>=’ | ‘==’ | ‘~=’ |
///            and | or
/// unop ::= ‘-’ | not | ‘#’ | ‘~’
/// ```
pub enum Exp {
    BinOp(Box<Exp>, BinOp, Box<Exp>, Pos),
    UnOp(UnOp, Box<Exp>, Pos),
    Lit(Literal, Pos),
    Table(Vec<TableField>, Pos),
    Prefix(PrefixExp),
    Func(Func),
    Varargs(Pos),
}

// Indicating what was previously encountered
#[derive(PartialEq)]
enum ExpParseState {
    Init,
    Operand,
    Operator,
    Done,
}

enum Op {
    BinOp((BinOp, Pos)),
    UnOp((UnOp, Pos)),
}

struct ExpParser<'a, 'b> {
    parser: &'a mut Parser<'b>,
    state: ExpParseState,
    out: Vec<Exp>,
    ops: Vec<Op>,
}

impl<'a> Parser<'a> {
    pub(super) fn parse_exp(&mut self) -> Result<Exp> {
        ExpParser {
            parser: self,
            state: ExpParseState::Init,
            out: Vec::new(),
            ops: Vec::new(),
        }
        .parse()
    }
}

impl Op {
    fn precedence(&self) -> usize {
        match self {
            Op::BinOp((op, _)) => op.precedence(),
            Op::UnOp((op, _)) => op.precedence(),
        }
    }
}

impl<'a, 'b> ExpParser<'a, 'b> {
    fn parse(mut self) -> Result<Exp> {
        self.parser.exp_depth += 1;
        if self.parser.exp_depth > MAX_EXP {
            return err!(LuaError::SyntaxLimit("nested expressions", None));
        }
        while self.state != ExpParseState::Done {
            self.transition()?;
        }
        self.parser.exp_depth -= 1;

        self.finalize()
    }

    fn transition(&mut self) -> Result<()> {
        if self.ops.len() > MAX_EXP {
            // XXX: Does not need to be enforced, but Lua test suite does
            return err!(LuaError::SyntaxLimit("expression operands", None));
        }

        self.state = match self.state {
            ExpParseState::Init => {
                // Expecting operand or unary operator
                if let Some(op) = self.peek_un_op() {
                    self.parser.tok_next()?;
                    self.ops.push(Op::UnOp(op));

                    ExpParseState::Operator
                } else {
                    let op = self.parse_operand()?;
                    self.out.push(op);

                    ExpParseState::Operand
                }
            }
            ExpParseState::Operand => {
                // Expecting binary operator or end
                if let Some((op, pos)) = self.peek_bin_op() {
                    while let Some(pop) = self.ops.last() {
                        if pop.precedence() > op.precedence()
                            || (op.is_left_ass() && pop.precedence() == op.precedence())
                        {
                            self.pop_op()?;
                        } else {
                            break;
                        }
                    }

                    self.parser.tok_next()?;
                    self.ops.push(Op::BinOp((op, pos)));

                    ExpParseState::Operator
                } else {
                    ExpParseState::Done
                }
            }
            ExpParseState::Operator => {
                // Expecting operand or unary operator
                if let Some(op) = self.peek_un_op() {
                    self.parser.tok_next()?;
                    self.ops.push(Op::UnOp(op));

                    ExpParseState::Operator
                } else {
                    let op = self.parse_operand()?;
                    self.out.push(op);

                    ExpParseState::Operand
                }
            }
            ExpParseState::Done => unreachable!(),
        };

        Ok(())
    }

    fn finalize(mut self) -> Result<Exp> {
        while !self.ops.is_empty() {
            self.pop_op()?;
        }

        if self.out.len() == 1 {
            Ok(self.out.pop().unwrap())
        } else {
            panic!()
        }
    }

    fn parse_operand(&mut self) -> Result<Exp> {
        if self.peek_prefix_exp() {
            Ok(Exp::Prefix(self.parser.parse_prefix_exp()?))
        } else if let Some(lit) = self.peek_literal() {
            let (pos, _) = self.parser.tok_next()?.into();
            Ok(Exp::Lit(lit, pos))
        } else if self.peek_table() {
            let (tbl, pos) = self.parser.parse_table_constructor()?;
            Ok(Exp::Table(tbl, pos))
        } else if self.peek_function() {
            self.parser.tok_next()?;
            Ok(Exp::Func(self.parser.parse_func()?))
        } else if let Some((pos, &Token::Dots)) = self.parser.tok_peek_opt()?.map(Into::into) {
            self.parser.tok_next()?;
            Ok(Exp::Varargs(pos))
        } else {
            let (pos, tok) = self.parser.tok_next()?.into();
            return err!(&*self.parser.source, pos, LuaError::UnexpectedToken(tok));
        }
    }

    fn peek_prefix_exp(&mut self) -> bool {
        matches!(
            self.parser.tok_peek().ok().map(Spanned::inner),
            Some(Token::Ident(..) | Token::ParO)
        )
    }

    fn peek_table(&mut self) -> bool {
        matches!(
            self.parser.tok_peek().ok().map(Spanned::inner),
            Some(Token::CurlO)
        )
    }

    fn peek_function(&mut self) -> bool {
        matches!(
            self.parser.tok_peek().ok().map(Spanned::inner),
            Some(Token::Function)
        )
    }

    fn peek_literal(&mut self) -> Option<Literal> {
        match self.parser.tok_peek().ok()?.inner() {
            Token::Nil => Some(Literal::Nil),
            Token::True => Some(Literal::Bool(true)),
            Token::False => Some(Literal::Bool(false)),
            Token::Integer(n, _) => Some(Literal::Int(*n)),
            Token::Float(f, _) => Some(Literal::Float(*f)),
            Token::String(s, _) => Some(Literal::String(s.clone())),
            _ => None,
        }
    }

    fn peek_bin_op(&mut self) -> Option<(BinOp, Pos)> {
        let (pos, token) = self.parser.tok_peek().ok()?.into();
        match token {
            // Mathematical
            Token::Add => Some(BinOp::Add),
            Token::Sub => Some(BinOp::Sub),
            Token::Mul => Some(BinOp::Mul),
            Token::Div => Some(BinOp::Div),
            Token::FlDiv => Some(BinOp::FlDiv),
            Token::Pow => Some(BinOp::Pow),
            Token::Mod => Some(BinOp::Mod),
            // Bitwise
            Token::Amp => Some(BinOp::BitAnd),
            Token::Tilde => Some(BinOp::BitXor),
            Token::Bar => Some(BinOp::BitOr),
            Token::Shl => Some(BinOp::Shl),
            Token::Shr => Some(BinOp::Shr),
            // String
            Token::Conc => Some(BinOp::Conc),
            // Comparison
            Token::Eq => Some(BinOp::Eq),
            Token::Neq => Some(BinOp::Neq),
            Token::Lt => Some(BinOp::Lt),
            Token::Leq => Some(BinOp::Leq),
            Token::Gt => Some(BinOp::Gt),
            Token::Geq => Some(BinOp::Geq),
            Token::And => Some(BinOp::And),
            Token::Or => Some(BinOp::Or),
            _ => None,
        }
        .map(|op| (op, pos))
    }

    fn peek_un_op(&mut self) -> Option<(UnOp, Pos)> {
        let (pos, token) = self.parser.tok_peek().ok()?.into();
        match token {
            Token::Sub => Some(UnOp::Minus),
            Token::Tilde => Some(UnOp::BitNot),
            Token::Not => Some(UnOp::Not),
            Token::Hash => Some(UnOp::Len),
            _ => None,
        }
        .map(|op| (op, pos))
    }

    fn pop_op(&mut self) -> Result<()> {
        let op = match self.ops.pop() {
            Some(o) => o,
            None => panic!(),
        };

        let out = match op {
            Op::BinOp((op, pos)) => {
                let op2 = match self.out.pop() {
                    Some(v) => v,
                    None => panic!(),
                };
                let op1 = match self.out.pop() {
                    Some(v) => v,
                    None => panic!(),
                };

                Exp::BinOp(Box::new(op1), op, Box::new(op2), pos)
            }
            Op::UnOp((op, pos)) => {
                let op1 = match self.out.pop() {
                    Some(v) => v,
                    None => panic!(),
                };

                Exp::UnOp(op, Box::new(op1), pos)
            }
        };
        self.out.push(out);

        Ok(())
    }
}