Skip to content

Commit

Permalink
Rework and add Non-associative operators
Browse files Browse the repository at this point in the history
  • Loading branch information
segeljakt committed Oct 11, 2020
1 parent 7f255bf commit af8a03c
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 69 deletions.
2 changes: 2 additions & 0 deletions example/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Infix: TokenTree = {
"-" => TokenTree::Infix('-'),
"*" => TokenTree::Infix('*'),
"/" => TokenTree::Infix('/'),
"=" => TokenTree::Infix('='),
"^" => TokenTree::Infix('^'),
}

Prefix: TokenTree = {
Expand Down
142 changes: 112 additions & 30 deletions example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@ use pratt::{Affix, Associativity, PrattParser, Precedence};

lalrpop_mod!(pub grammar);

#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub enum Expr {
BinOp(Box<Expr>, BinOp, Box<Expr>),
UnOp(UnOp, Box<Expr>),
BinOp(Box<Expr>, BinOpKind, Box<Expr>),
UnOp(UnOpKind, Box<Expr>),
Int(i32),
Unknown(String),
}

#[derive(Debug)]
pub enum BinOp {
#[derive(Debug, Eq, PartialEq)]
pub enum BinOpKind {
Add,
Sub,
Mul,
Div,
Pow,
Eq,
}

#[derive(Debug)]
pub enum UnOp {
#[derive(Debug, Eq, PartialEq)]
pub enum UnOpKind {
Not,
Neg,
Try,
}

#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub enum TokenTree {
Prefix(char),
Postfix(char),
Expand All @@ -48,13 +49,15 @@ where
// Query information about an operator (Affix, Precedence, Associativity)
fn query(&mut self, tree: &TokenTree) -> Option<Affix> {
let affix = match tree {
TokenTree::Postfix('?') => Affix::Postfix(Precedence(1)),
TokenTree::Infix('+') => Affix::Infix(Precedence(2), Associativity::Left),
TokenTree::Infix('-') => Affix::Infix(Precedence(2), Associativity::Left),
TokenTree::Infix('*') => Affix::Infix(Precedence(2), Associativity::Right),
TokenTree::Infix('/') => Affix::Infix(Precedence(2), Associativity::Right),
TokenTree::Prefix('-') => Affix::Prefix(Precedence(3)),
TokenTree::Prefix('!') => Affix::Prefix(Precedence(3)),
TokenTree::Infix('=') => Affix::Infix(Precedence(2), Associativity::Neither),
TokenTree::Infix('+') => Affix::Infix(Precedence(3), Associativity::Left),
TokenTree::Infix('-') => Affix::Infix(Precedence(3), Associativity::Left),
TokenTree::Infix('*') => Affix::Infix(Precedence(4), Associativity::Left),
TokenTree::Infix('/') => Affix::Infix(Precedence(4), Associativity::Left),
TokenTree::Postfix('?') => Affix::Postfix(Precedence(5)),
TokenTree::Prefix('-') => Affix::Prefix(Precedence(6)),
TokenTree::Prefix('!') => Affix::Prefix(Precedence(6)),
TokenTree::Infix('^') => Affix::Infix(Precedence(7), Associativity::Right),
_ => None?,
};
Some(affix)
Expand All @@ -72,10 +75,12 @@ where
// Construct an binary infix expression, e.g. 1+1
fn infix(&mut self, lhs: Expr, tree: TokenTree, rhs: Expr) -> Result<Expr, ()> {
let op = match tree {
TokenTree::Infix('+') => BinOp::Add,
TokenTree::Infix('-') => BinOp::Sub,
TokenTree::Infix('*') => BinOp::Mul,
TokenTree::Infix('/') => BinOp::Div,
TokenTree::Infix('+') => BinOpKind::Add,
TokenTree::Infix('-') => BinOpKind::Sub,
TokenTree::Infix('*') => BinOpKind::Mul,
TokenTree::Infix('/') => BinOpKind::Div,
TokenTree::Infix('^') => BinOpKind::Pow,
TokenTree::Infix('=') => BinOpKind::Eq,
_ => Err(())?,
};
Ok(Expr::BinOp(Box::new(lhs), op, Box::new(rhs)))
Expand All @@ -84,8 +89,8 @@ where
// Construct an unary prefix expression, e.g. !1
fn prefix(&mut self, tree: TokenTree, rhs: Expr) -> Result<Expr, ()> {
let op = match tree {
TokenTree::Prefix('!') => UnOp::Not,
TokenTree::Prefix('-') => UnOp::Neg,
TokenTree::Prefix('!') => UnOpKind::Not,
TokenTree::Prefix('-') => UnOpKind::Neg,
_ => Err(())?,
};
Ok(Expr::UnOp(op, Box::new(rhs)))
Expand All @@ -94,20 +99,97 @@ where
// Construct an unary postfix expression, e.g. 1?
fn postfix(&mut self, lhs: Expr, tree: TokenTree) -> Result<Expr, ()> {
let op = match tree {
TokenTree::Postfix('?') => UnOp::Try,
TokenTree::Postfix('?') => UnOpKind::Try,
_ => Err(())?,
};
Ok(Expr::UnOp(op, Box::new(lhs)))
}
}

fn main() {
let tt = grammar::TokenTreeParser::new()
.parse("-1?+1-!-1?")
.unwrap();
let expr = ExprParser
.parse(&mut tt.into_iter())
.unwrap();
println!("{:#?}", expr);
let mut args = std::env::args();
let _ = args.next();
let input = args.next().expect("Expected input string");
println!("Code: {}", input);
let tt = grammar::TokenTreeParser::new().parse(&input).unwrap();
println!("TokenTree: {:?}", tt);
let expr = ExprParser.parse(&mut tt.into_iter()).unwrap();
println!("Expression: {:?}", expr);
}

#[cfg(test)]
mod test {
fn parse(input: &str) -> Expr {
let tt = grammar::TokenTreeParser::new()
.parse(input)
.unwrap()
.into_iter();
ExprParser.parse(&mut tt.into_iter()).unwrap()
}
use super::BinOpKind::*;
use super::Expr::*;
use super::UnOpKind::*;
use super::*;

#[test]
fn test1() {
assert_eq!(
parse("1=2=3"),
BinOp(Box::new(Int(1)), Eq, Box::new(Int(2)))
);
}

#[test]
fn test2() {
assert_eq!(
parse("1*2+3"),
BinOp(
Box::new(BinOp(Box::new(Int(1)), Mul, Box::new(Int(2)))),
Add,
Box::new(Int(3))
)
);
}

#[test]
fn test3() {
assert_eq!(
parse("1*!2^3"),
BinOp(
Box::new(Int(1)),
Mul,
Box::new(UnOp(
Not,
Box::new(BinOp(Box::new(Int(2)), Pow, Box::new(Int(3))))
))
)
);
}

#[test]
fn test4() {
assert_eq!(
parse("-1?*!2^3+3/2?-1"),
BinOp(
Box::new(BinOp(
Box::new(BinOp(
Box::new(UnOp(Try, Box::new(UnOp(Neg, Box::new(Int(1)))))),
Mul,
Box::new(UnOp(
Not,
Box::new(BinOp(Box::new(Int(2)), Pow, Box::new(Int(3))))
))
)),
Add,
Box::new(BinOp(
Box::new(Int(3)),
Div,
Box::new(UnOp(Try, Box::new(Int(2))))
))
)),
Sub,
Box::new(Int(1))
)
);
}
}
133 changes: 94 additions & 39 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
use std::iter::Peekable;

#[derive(Copy, Clone)]
pub enum Associativity {
Left,
Right,
Null,
Neither,
}

#[derive(PartialEq, PartialOrd)]
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub struct Precedence(pub u32);

impl Precedence {
fn lower(mut self) -> Precedence {
const fn raise(mut self) -> Precedence {
self.0 += 1;
self
}
const fn lower(mut self) -> Precedence {
self.0 -= 1;
self
}
const fn normalize(mut self) -> Precedence {
self.0 *= 10;
self
}
const fn min() -> Precedence {
Precedence(std::u32::MIN)
}
const fn max() -> Precedence {
Precedence(std::u32::MAX)
}
}

#[derive(Copy, Clone)]
pub enum Affix {
Infix(Precedence, Associativity),
Prefix(Precedence),
Postfix(Precedence),
}

use std::iter::Peekable;

pub trait PrattParser<Inputs>
where
Inputs: Iterator<Item = Self::Input>,
Expand Down Expand Up @@ -51,69 +67,108 @@ where

fn parse_input(
&mut self,
inputs: &mut Peekable<&mut Inputs>,
tail: &mut Peekable<&mut Inputs>,
rbp: Precedence,
) -> Result<Self::Output, Self::Error> {
let mut lhs = self.nud(inputs); // Parse the prefix
while rbp < self.lbp(inputs) {
lhs = self.led(inputs, lhs?);
if let Some(head) = tail.next() {
let info = self.query(&head);
let mut nbp = self.nbp(info)?;
let mut node = self.nud(head, tail, info);
loop {
if let Some(head) = tail.peek() {
let info = self.query(head);
let lbp = self.lbp(info)?;
if rbp < lbp && lbp < nbp {
let head = tail.next().unwrap();
nbp = self.nbp(info)?;
node = self.led(head, tail, info, node?);
} else {
break node;
}
} else {
break node;
}
}
} else {
panic!()
}
lhs
}

/// Null-Denotation
fn nud(&mut self, inputs: &mut Peekable<&mut Inputs>) -> Result<Self::Output, Self::Error> {
let input = inputs
.next()
.expect("Pratt parsing expects non-empty inputs");
match self.query(&input) {
fn nud(
&mut self,
head: Self::Input,
tail: &mut Peekable<&mut Inputs>,
info: Option<Affix>,
) -> Result<Self::Output, Self::Error> {
match info {
Some(Affix::Prefix(precedence)) => {
let rhs = self.parse_input(inputs, precedence.lower());
self.prefix(input, rhs?)
let rhs = self.parse_input(tail, precedence.normalize().lower());
self.prefix(head, rhs?)
}
None => self.primary(input),
None => self.primary(head),
_ => panic!(
"Expected unary-prefix or primary expression, found {:?}",
input
head
),
}
}

/// Left-Denotation
fn led(
&mut self,
inputs: &mut Peekable<&mut Inputs>,
head: Self::Input,
tail: &mut Peekable<&mut Inputs>,
info: Option<Affix>,
lhs: Self::Output,
) -> Result<Self::Output, Self::Error> {
let input = inputs
.next()
.expect("Pratt parsing expects non-empty inputs");
match self.query(&input) {
match info {
Some(Affix::Infix(precedence, associativity)) => {
let precedence = precedence.normalize();
let rhs = match associativity {
Associativity::Left => self.parse_input(inputs, precedence),
Associativity::Right => self.parse_input(inputs, precedence.lower()),
Associativity::Left => self.parse_input(tail, precedence),
Associativity::Right => self.parse_input(tail, precedence.lower()),
Associativity::Neither => self.parse_input(tail, precedence.raise()),
};
self.infix(lhs, input, rhs?)
self.infix(lhs, head, rhs?)
}
Some(Affix::Postfix(_)) => self.postfix(lhs, input),
Some(Affix::Postfix(_)) => self.postfix(lhs, head),
_ => panic!(
"Expected unary-postfix or binary-infix expression, found {:?}",
input
head
),
}
}

// <lbp> <rbp> <nbp> <kind>
// Nilfix: MIN | MIN | MAX | nud
// Prefix: MIN | bp | MAX | nud
// Postfix: bp | MIN | MAX | led
// InfixL: bp | bp | bp+1 | led
// InfixR: bp | bp-1 | bp+1 | led
// InfixN: bp | bp | bp | led

/// Left-Binding-Power
fn lbp(&mut self, inputs: &mut Peekable<&mut Inputs>) -> Precedence {
match inputs.peek() {
Some(input) => match self.query(input) {
Some(Affix::Infix(precedence, _))
| Some(Affix::Prefix(precedence))
| Some(Affix::Postfix(precedence)) => precedence,
None => panic!("Expected operator, found {:?}", input),
},
None => Precedence(0),
}
fn lbp(&mut self, info: Option<Affix>) -> Result<Precedence, Self::Error> {
let lbp = match info {
None => panic!("Expected operator"),
Some(Affix::Prefix(_)) => Precedence::min(),
Some(Affix::Postfix(precedence)) => precedence.normalize(),
Some(Affix::Infix(precedence, _)) => precedence.normalize(),
};
Ok(lbp)
}

/// Next-Binding-Power
fn nbp(&mut self, info: Option<Affix>) -> Result<Precedence, Self::Error> {
let nbp = match info {
None => Precedence::max(),
Some(Affix::Prefix(_)) => Precedence::max(),
Some(Affix::Postfix(_)) => Precedence::max(),
Some(Affix::Infix(precedence, Associativity::Left)) => precedence.normalize().raise(),
Some(Affix::Infix(precedence, Associativity::Right)) => precedence.normalize().raise(),
Some(Affix::Infix(precedence, Associativity::Neither)) => precedence.normalize(),
};
Ok(nbp)
}
}

0 comments on commit af8a03c

Please sign in to comment.