Skip to content

Commit

Permalink
Version 0.4.0:
Browse files Browse the repository at this point in the history
* Update API: Pass Input instead of &mut Input
* Add example of how to use pratt with pest
  • Loading branch information
segeljakt committed Oct 2, 2022
1 parent 56e194b commit 4a00d5e
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pratt"
version = "0.3.0"
version = "0.4.0"
description = "A Pratt parser for Rust"
authors = ["Klas Segeljakt <[email protected]>"]
edition = "2018"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ In other words, you can use a Pratt parser to parse trees of expressions that mi

Assume we want to parse an expression `!1?*-3+3/!2^4?-1` into `(((((!(1))?)*(-(3)))+((3)/((!((2)^(4)))?)))-(1))`.

Our strategy is to implement a parser which parses source code into token trees, and then token-trees into an expression tree. The full implementation can be viewed [here](https://github.com/segeljakt/pratt/tree/master/example).
Our strategy is to implement a parser which parses source code into token trees, and then token-trees into an expression tree. The full implementation can be viewed [here](https://github.com/segeljakt/pratt/tree/master/examples/lalrpop-pratt). This example uses [LALRPOP](https://github.com/lalrpop/lalrpop). A full implementation that instead uses the [pest](https://github.com/pest-parser/pest) parser is available [here](https://github.com/segeljakt/pratt/tree/master/examples/lalrpop-pratt).

```rust
// From this
Expand Down
6 changes: 3 additions & 3 deletions example/Cargo.toml → examples/lalrpop-pratt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "example"
version = "0.3.0"
name = "lalrpop-pratt"
version = "0.4.0"
authors = ["Klas Segeljakt <[email protected]>"]
edition = "2018"
build = "build.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
pratt = { version = "0.3.0", path = "../" }
pratt = { version = "0.4.0", path = "../../" }
regex = "1.3.6"
lalrpop-util = "0.18.1"

Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions example/src/main.rs → examples/lalrpop-pratt/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn main() {
let tt = grammar::TokenTreeParser::new().parse(&input).unwrap();
println!("TokenTree: {:?}", tt);

let expr = ExprParser.parse(&mut tt.into_iter()).unwrap();
let expr = ExprParser.parse(tt.into_iter()).unwrap();
println!("Expression: {:?}", expr);
}

Expand All @@ -130,7 +130,7 @@ mod test {
.parse(input)
.unwrap()
.into_iter();
ExprParser.parse(&mut tt.into_iter()).unwrap()
ExprParser.parse(tt.into_iter()).unwrap()
}
use super::BinOpKind::*;
use super::Expr::*;
Expand Down
11 changes: 11 additions & 0 deletions examples/pest-pratt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "pest-pratt"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
pest = "2.4.0"
pest_derive = "2.4.0"
pratt = { version = "0.4.0", path = "../../" }
8 changes: 8 additions & 0 deletions examples/pest-pratt/grammar.pest
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
WHITESPACE = _{ " " | "\t" | NEWLINE }

group = { prefix* ~ primary ~ postfix* ~ (infix ~ prefix* ~ primary ~ postfix* )* }
infix = { "+" | "-" | "*" | "/" | "=" | "^" }
prefix = { "-" | "!" }
postfix = { "?" }
primary = _{ num | "(" ~ group ~ ")" }
num = @{ (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT+ | ASCII_DIGIT) }
199 changes: 199 additions & 0 deletions examples/pest-pratt/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#[macro_use]
extern crate pest_derive;

use pest::iterators::Pair;
use pest::Parser;

#[derive(Parser)]
#[grammar = "grammar.pest"]
struct TokenTreeParser;

use pratt::{Affix, Associativity, PrattParser, Precedence, Result};

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

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

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

struct ExprParser;

impl<'i, I> PrattParser<I> for ExprParser
where
I: Iterator<Item = Pair<'i, Rule>>,
{
type Error = pratt::NoError;
type Input = Pair<'i, Rule>;
type Output = Expr;

// Query information about an operator (Affix, Precedence, Associativity)
fn query(&mut self, tree: &Self::Input) -> Result<Affix> {
let affix = match (tree.as_rule(), tree.as_str()) {
(Rule::infix, "=") => Affix::Infix(Precedence(2), Associativity::Neither),
(Rule::infix, "+") => Affix::Infix(Precedence(3), Associativity::Left),
(Rule::infix, "-") => Affix::Infix(Precedence(3), Associativity::Left),
(Rule::infix, "*") => Affix::Infix(Precedence(4), Associativity::Left),
(Rule::infix, "/") => Affix::Infix(Precedence(4), Associativity::Left),
(Rule::postfix, "?") => Affix::Postfix(Precedence(5)),
(Rule::prefix, "-") => Affix::Prefix(Precedence(6)),
(Rule::prefix, "!") => Affix::Prefix(Precedence(6)),
(Rule::infix, "^") => Affix::Infix(Precedence(7), Associativity::Right),
(Rule::group, _) => Affix::Nilfix,
(Rule::primary, _) => Affix::Nilfix,
(Rule::num, _) => Affix::Nilfix,
_ => unreachable!(),
};
Ok(affix)
}

// Construct a primary expression, e.g. a number
fn primary(&mut self, tree: Self::Input) -> Result<Expr> {
let expr = match tree.as_rule() {
Rule::num => Expr::Int(tree.as_str().parse().unwrap()),
Rule::group => self.parse(&mut tree.into_inner()).unwrap(),
_ => unreachable!(),
};
Ok(expr)
}

// Construct a binary infix expression, e.g. 1+1
fn infix(&mut self, lhs: Expr, tree: Self::Input, rhs: Expr) -> Result<Expr> {
let op = match tree.as_str() {
"+" => BinOpKind::Add,
"-" => BinOpKind::Sub,
"*" => BinOpKind::Mul,
"/" => BinOpKind::Div,
"^" => BinOpKind::Pow,
"=" => BinOpKind::Eq,
_ => unreachable!(),
};
Ok(Expr::BinOp(Box::new(lhs), op, Box::new(rhs)))
}

// Construct a unary prefix expression, e.g. !1
fn prefix(&mut self, tree: Self::Input, rhs: Expr) -> Result<Expr> {
let op = match tree.as_str() {
"!" => UnOpKind::Not,
"-" => UnOpKind::Neg,
_ => unreachable!(),
};
Ok(Expr::UnOp(op, Box::new(rhs)))
}

// Construct a unary postfix expression, e.g. 1?
fn postfix(&mut self, lhs: Expr, tree: Self::Input) -> Result<Expr> {
let op = match tree.as_str() {
"?" => UnOpKind::Try,
_ => unreachable!(),
};
Ok(Expr::UnOp(op, Box::new(lhs)))
}
}

fn main() {
let mut args = std::env::args();
let _ = args.next();

let input = args.next().expect("Expected input string");
println!("Code: {}", input);

let tt = TokenTreeParser::parse(Rule::group, &input).unwrap_or_else(|e| panic!("{}", e));
println!("TokenTree: {:?}", tt);

let expr = ExprParser.parse(tt.into_iter()).unwrap();
println!("Expression: {:?}", expr);
}

#[cfg(test)]
mod test {
fn parse(input: &str) -> Expr {
let tt = TokenTreeParser::parse(Rule::group, &input)
.unwrap()
.into_iter();
ExprParser.parse(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))
)
);
}
}
19 changes: 13 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub enum Associativity {
Neither,
}

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

impl Precedence {
Expand Down Expand Up @@ -116,14 +116,21 @@ where

fn parse(
&mut self,
inputs: &mut Inputs,
inputs: Inputs,
) -> result::Result<Self::Output, PrattError<Self::Input, Self::Error>> {
self.parse_input(&mut inputs.peekable(), Precedence(0))
self.parse_input(&mut inputs.peekable(), Precedence::min())
}

fn parse_peekable(
&mut self,
inputs: &mut Peekable<Inputs>,
) -> result::Result<Self::Output, PrattError<Self::Input, Self::Error>> {
self.parse_input(inputs, Precedence::min())
}

fn parse_input(
&mut self,
tail: &mut Peekable<&mut Inputs>,
tail: &mut Peekable<Inputs>,
rbp: Precedence,
) -> result::Result<Self::Output, PrattError<Self::Input, Self::Error>> {
if let Some(head) = tail.next() {
Expand Down Expand Up @@ -151,7 +158,7 @@ where
fn nud(
&mut self,
head: Self::Input,
tail: &mut Peekable<&mut Inputs>,
tail: &mut Peekable<Inputs>,
info: Affix,
) -> result::Result<Self::Output, PrattError<Self::Input, Self::Error>> {
match info {
Expand All @@ -169,7 +176,7 @@ where
fn led(
&mut self,
head: Self::Input,
tail: &mut Peekable<&mut Inputs>,
tail: &mut Peekable<Inputs>,
info: Affix,
lhs: Self::Output,
) -> result::Result<Self::Output, PrattError<Self::Input, Self::Error>> {
Expand Down

0 comments on commit 4a00d5e

Please sign in to comment.