This crate provides a binary to compile grammars into Rust code and a library implementing Earley's parsing algorithm to parse the grammars specified.
This crate is gramatica
. To use it you should install it in order to acquire the gramatica_compiler
binary and also add gramatica
to your dependencies in your project's Cargo.toml
.
toml
[dependencies]
gramatica = "0.1"
Then, if you have made a grammar file example.rsg
execute gramatica_compiler example.rsg > example.rs
. Afterwards you may use the generated file example.rs
as a source Rust file.
The classical example is to implement a calculator.
```rust extern crate gramatica; use std::cmp::Ordering; use std::io::BufRead; use gramatica::{Associativity,EarleyKind,State,Parser,ParsingTablesTrait,AmbiguityInfo};
reterminal!(Num(f64),"[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?"); reterminal!(Plus,"\+"); reterminal!(Minus,"-"); reterminal!(Star,"\*"); reterminal!(Slash,"/"); reterminal!(Caret,"\^"); reterminal!(LPar,"\("); reterminal!(RPar,"\)"); reterminal!(NewLine,"\n"); reterminal!(_,"\s+");//Otherwise skip spaces
nonterminal Input(()) { () => (), (Input,Line) => (), }
nonterminal Line(()) { (NewLine) => (), (Expression(value), NewLine) => { println!("{}",value); }, }
nonterminal Expression(f64) { (Num(value)) => value, #[priority(addition)] #[associativity(left)] (Expression(l),Plus,Expression(r)) => l+r, #[priority(addition)] #[associativity(left)] (Expression(l),Minus,Expression(r)) => l-r, #[priority(multiplication)] #[associativity(left)] (Expression(l),Star,Expression(r)) => l*r, #[priority(multiplication)] #[associativity(left)] (Expression(l),Slash,Expression(r)) => l/r, #[priority(addition)] #[associativity(left)] (Minus,Expression(value)) => -value, #[priority(exponentiation)] #[associativity(right)] (Expression(l),Caret,Expression(r)) => l.powf(r), (LPar,Expression(value),RPar) => value, }
ordering!(exponentiation,multiplication,addition);
fn main()
{
let stdin=std::io::stdin();
for rline in stdin.lock().lines()
{
let line=rline.unwrap()+"\n";
println!("line={}",line);
match Parser::
To define terminal tokens not expressable with regular expressions you may use the following.
rust
terminal LitChar(char)
{
fn _match(parser: &mut Parser<Token,ParsingTables>, source:&str) -> Option<(usize,char)>
{
let mut characters=source.chars();
if (characters.next())==(Some('\''))
{
let mut c=characters.next().unwrap();
let mut size=3;
if c=='\\'
{
c=(characters.next().unwrap());
size=4;
}
if characters.next().unwrap()=='\''
{
Some((size,c))
}
else
{
None
}
}
else
{
None
}
}
}
Since version 0.1.0 there is also a keyword_terminal!
macro:
rust
keyword_terminal!(Const,"const");
Each rule is written as a match clause, whose ending expression is the value that the nonterminal token gets after being parsed. For example:
rust
nonterminal Stmts(Vec<StmtKind>)
{
(Stmt(ref stmt)) => vec![stmt.clone()],
(Stmts(ref stmts),Stmt(ref stmt)) =>
{
let mut new=(stmts.clone());
new.push(stmt.clone());
new
},
}
Reductions only execute if they are part of the final syntactic tree.
To avoid ambiguities you have two options: to ensure the grammar does not contain them or to priorize rules by introducing annotations. In the example of the calculator we have seen two kinds:
- #[priority(p_name)]
to declare a rule with priority p_name
. Later there should be a ordering!(p_0,p_1,p_2,...)
macro-like to indicate that p_0
should reduce before p_1
.
- #[associativity(left/right)]
to decide how to proceed when nesting the same rule.