Skip to content

Instantly share code, notes, and snippets.

@shanemikel
Created April 13, 2019 01:57
Show Gist options
  • Save shanemikel/7d2d05059a99ed6573805818162c1b3f to your computer and use it in GitHub Desktop.
Save shanemikel/7d2d05059a99ed6573805818162c1b3f to your computer and use it in GitHub Desktop.
Lua parsing in Rust with Pest (operator precedence)
#[derive(Parser)]
#[grammar = "lua.pest"]
pub struct LuaParser;
lazy_static! {
static ref PREC_CLIMBER: PrecClimber<Rule> = {
use Rule::*;
use Operator as O;
use Assoc::Left as L;
PrecClimber::new(vec![
O::new(Or, L),
O::new(And, L),
O::new(Eq, L) | O::new(Neq, L),
O::new(Lt, L) | O::new(Leq, L) | O::new(Gt, L) | O::new(Geq, L),
O::new(Add, L) | O::new(Sub, L),
O::new(Mul, L) | O::new(Div, L),
])
};
}
#[derive(Debug)]
pub enum Exp {
Val(Val),
Var(Var),
BinOp(Box<Exp>, Op, Box<Exp>),
}
impl<'a> FromPest<'a> for Exp {
type Rule = Rule;
type FatalError = ErrorContext;
fn from_pest(mut pairs: &mut Pairs<'a, Rule>) -> ParseResult<Self> {
fn map(pair: Pair<Rule>) -> ParseResult<Exp> {
let rule = pair.as_rule();
let body = pair.as_str();
log::debug!("`Exp::from_pest::map` called on `{:?}`: {:?}", rule, body);
let mut pairs = pair.into_inner();
match rule {
Rule::Val => Val::from_pest(&mut pairs).map(Exp::Val),
Rule::Var => Var::from_pest(&mut pairs).map(Exp::Var),
Rule::Exp => Exp::from_pest(&mut pairs),
_rule => unreachable!(),
}
}
fn reduce(lhs: ParseResult<Exp>, op: Pair<Rule>, rhs: ParseResult<Exp>) ->
ParseResult<Exp>
{
let lhs = lhs?;
let rhs = rhs?;
let rule = op.as_rule();
let body = op.as_str();
let args = format!("({:?}, {:?}: \"{}\", {:?}", lhs, rule, body, rhs);
log::debug!("`Exp::from_pest::reduce` called with {}", args);
let mut pairs = op.into_inner();
let op = Op::from_pest(&mut pairs)?;
Ok(Exp::BinOp(Box::new(lhs), op, Box::new(rhs)))
}
let pair = pairs.peek().ok_or(NoMatch)?;
let rule = pair.as_rule();
let body = pair.as_str();
log::debug!("`Exp::from_pest` called on `{:?}`: {:?}", rule, body);
match rule {
Rule::Val => Val::from_pest(&mut pairs).map(Exp::Val),
Rule::Var => Var::from_pest(&mut pairs).map(Exp::Var),
Rule::Exp => {
let pairs = pairs.next().unwrap().into_inner();
PREC_CLIMBER.climb(pairs, map, reduce)
},
_rule => Err(NoMatch),
}
}
}
Script = { SOI ~ Block ~ EOI }
Block = { (Stat ~ ";"?)* }
Stat = { Assignment | Do | While }
Assignment = { VarList1 ~ "=" ~ ExpList1 }
Do = { "do" ~ Block ~ "end" }
While = { "while" ~ Exp ~ "do" ~ Block ~ "end" }
VarList1 = _{ Var ~ ("," ~ Var)* }
ExpList1 = _{ Exp ~ ("," ~ Exp)* }
Exp = { Term ~ (Op ~ Term)* }
Term = _{ Atom | "(" ~ Exp ~ ")" }
Atom = _{ Val | Var }
Var = { Name }
Val = { Nil | Bool | Num }
Nil = @{ "nil" }
Bool = { True | False }
Num = _{ Float | Int }
True = @{ "true" }
False = @{ "false" }
Op = { OpArith | OpComp | OpLogic }
OpArith = _{ Pow | Mul | Div | Add | Sub }
OpComp = _{ Lt | Leq | Gt | Geq | Eq | Neq }
OpLogic = _{ Not | And | Or }
Not = { "not" }
Pow = { "^" }
Mul = { "*" }
Div = { "/" }
Add = { "+" }
Sub = { "-" }
Lt = { "<" }
Leq = { "<=" }
Gt = { ">" }
Geq = { ">=" }
Eq = { "==" }
Neq = { "~=" }
And = { "and" }
Or = { "or" }
Name = @{ !Keyword ~ NameSeq }
Keyword = _{ "do" | "while" | "end" }
NameSeq = _{ (Lower | "_") ~ (Alpha | Digit | "_")* }
Float = @{ NumSign? ~ Int ~ "." ~ Digit+ }
Int = @{ NumSign? ~ Digit+ }
NumSign = _{ ("+" | "-") }
Lower = _{ 'a' .. 'z' }
Upper = _{ 'A' .. 'Z' }
Alpha = _{ Lower | Upper }
Digit = _{ '0' .. '9' }
WHITESPACE = _{ " " | "\t" | Newline }
Newline = _{ "\n" | "\r\n" }
COMMENT = _{ BlockComment | ("--" ~ (!Newline ~ ANY)*) }
BlockComment = _{ "--[[" ~ (BlockComment | !"]]" ~ ANY)* ~ "]]" }
@shanemikel
Copy link
Author

shanemikel commented Apr 13, 2019

For some reason it's not getting past the first terminal rule

[TRACE elua] script contents:
(1 + 2)

[TRACE elua] parse tree:
- Exp > Exp
  - Val > Int: "1"
  - Op > Add: "+"
  - Val > Int: "2"
[DEBUG elua::ast::pest] `Exp::from_pest` called on `Exp`: "(1 + 2)\n"
[DEBUG elua::ast::pest] `Exp::from_pest::map` called on `Exp`: "(1 + 2)\n"
[DEBUG elua::ast::pest] `Exp::from_pest` called on `Exp`: "1 + 2"
[DEBUG elua::ast::pest] `Exp::from_pest::map` called on `Exp`: "1 + 2"
[DEBUG elua::ast::pest] `Exp::from_pest` called on `Val`: "1"
[DEBUG elua::ast::pest] `Val::from_pest` called on `Val`: "1"
[DEBUG elua::ast::pest] `Val::from_pest` called on `Int`: "1"
[TRACE elua] syntax tree:
Val(
    Int(
        1
    )
)

EDIT in case it isn't clear, that output syntax tree at the end is supposed to be the final result of parsing the entire Exp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment