lrpar

lrpar provides a Yacc-compatible parser (where grammars can be generated at compile-time or run-time). It can take in traditional .y files and convert them into an idiomatic Rust parser. More details can be found in the grmtools book; the quickstart guide is a good place to start.

Example

Let's assume we want to statically generate a parser for a simple calculator language (and let's also assume we are able to use lrlex for the lexer). We need to add a build.rs file to our project which tells lrpar to statically compile the lexer and parser files:

```rust use cfgrammar::yacc::YaccKind; use lrlex::LexerBuilder; use lrpar::CTParserBuilder;

fn main() -> Result<(), Box> { let lexruleidsmap = CTParserBuilder::new() .yacckind(YaccKind::Grmtools) .processfileinsrc("calc.y")?; LexerBuilder::new() .ruleidsmap(lexruleidsmap) .processfileinsrc("calc.l")?; Ok(()) } ```

where src/calc.l is as follows:

%% [0-9]+ "INT" \+ "+" \* "*" \( "(" \) ")" [\t ]+ ;

and src/calc.y is as follows:

``` %start Expr %avoid_insert "INT" %% Expr -> Result

Term -> Result

Factor -> Resulterr(|| ())?; parseint($lexer.lexemestr(&v)) } ; %% // Any functions here are in scope for all the grammar actions above.

fn parseint(s: &str) -> Result() { Ok(val) => Ok(val as u64), Err() => { eprintln!("{} cannot be represented as a u64", s); Err(()) } } } ```

Because we specified that our Yacc file is in Grmtools format, each Rule has a separate Rust type to which all its functions conform (in this case, all the rules have the same type, but that's not a requirement).

A simple src/main.rs is as follows:

```rust

use std::io::{self, BufRead, Write};

use lrlex::lrlexmod; use lrpar::lrparmod;

// Using lrlex_mod! brings the lexer for calc.l into scope. lrlexmod!(calcl); // Using lrpar_mod! brings the lexer for calc.l into scope. lrparmod!(calcy);

fn main() { // We need to get a LexerDef for the calc language in order that we can lex input. let lexerdef = calcl::lexerdef(); let stdin = io::stdin(); loop { print!(">>> "); io::stdout().flush().ok(); match stdin.lock().lines().next() { Some(Ok(ref l)) => { if l.trim().isempty() { continue; } // Now we create a lexer with the lexer method with which we can lex an input. let mut lexer = lexerdef.lexer(l); // Pass the lexer to the parser and lex and parse the input. let (res, errs) = calcy::parse(&mut lexer); for e in errs { println!("{}", e.pp(&lexer, &calcy::token_epp)); } match res { Some(Ok(r)) => println!("Result: {}", r), _ => eprintln!("Unable to evaluate expression.") } } _ => break } } } ```

We can now cargo run our project and evaluate simple expressions:

```

2 + 3 Result: 5 2 + 3 * 4 Result: 14 (2 + 3) * 4 Result: 20 ```

lrpar also comes with advanced error recovery built-in:

```

2 + + 3 Parsing error at line 1 column 5. Repair sequences found: 1: Delete + 2: Insert INT Result: 5 2 + 3 3 Parsing error at line 1 column 7. Repair sequences found: 1: Insert * 2: Insert + 3: Delete 3 Result: 11 2 + 3 4 5 Parsing error at line 1 column 7. Repair sequences found: 1: Insert *, Delete 4 2: Insert +, Delete 4 3: Delete 4, Delete 5 4: Insert +, Shift 4, Delete 5 5: Insert +, Shift 4, Insert + 6: Insert *, Shift 4, Delete 5 7: Insert *, Shift 4, Insert * 8: Insert *, Shift 4, Insert + 9: Insert +, Shift 4, Insert * Result: 17 ```