12 releases

new 0.0.12 May 15, 2024
0.0.11 Apr 23, 2024
0.0.9 Mar 15, 2024
0.0.4 Feb 24, 2024

#456 in Rust patterns

Download history 178/week @ 2024-02-23 128/week @ 2024-03-01 292/week @ 2024-03-08 201/week @ 2024-03-15 9/week @ 2024-03-22 22/week @ 2024-03-29 43/week @ 2024-04-05 296/week @ 2024-04-12 167/week @ 2024-04-19 58/week @ 2024-04-26 45/week @ 2024-05-03 245/week @ 2024-05-10

1,415 downloads per month
Used in 3 crates

MIT license

11KB
183 lines

The Simplest Parser Library (TSPL)

TSPL is the The Simplest Parser Library that works in Rust.

Concept

In pure functional languages like Haskell, a Parser can be represented as a function:

Parser<A> ::= String -> Reply<A, Error>

This allows us to implement a Monad instance for Parser<A>, letting us use the do-notation to create simple and elegant parsers for our own types. Sadly, Rust doesn't have an equivalent. Yet, we can easily emulate it by:

  1. Using structs and impl to manage the cursor state internally.

  2. Returning a Result, which allows us to use Rust's ? to emulate monadic blocks.

This library merely exposes some functions to implement parsers that way, and nothing else.

Example

As an example, let's create a λ-Term parser using TSPL.

  1. Implement the type you want to create a parser for.
enum Term {
  Lam { name: String, body: Box<Term> },
  App { func: Box<Term>, argm: Box<Term> },
  Var { name: String },
}
  1. Define your grammar. We'll use the following:
<term> ::= <lam> | <app> | <var>
<lam>  ::= "λ" <name> " " <term>
<app>  ::= "(" <term> " " <term> ")"
<var>  ::= alphanumeric_string
  1. Create a new Parser with the new_parser()! macro.
TSPL::new_parser!(TermParser);
  1. Create an impl TermParser, with your grammar:
impl<'i> TermParser<'i> {
  fn parse(&mut self) -> Result<Term, String> {
    self.skip_trivia();
    match self.peek_one() {
      Some('λ') => {
        self.consume("λ")?;
        let name = self.parse_name()?;
        let body = Box::new(self.parse()?);
        Ok(Term::Lam { name, body })
      }
      Some('(') => {
        self.consume("(")?;
        let func = Box::new(self.parse()?);
        let argm = Box::new(self.parse()?);
        self.consume(")")?;
        Ok(Term::App { func, argm })
      }
      _ => {
        let name = self.parse_name()?;
        Ok(Term::Var { name })
      }
    }
  }
}
  1. Use your parser!
fn main() {
  let mut parser = TermParser::new("λx(λy(x y) λz z)");
  match parser.parse() {
    Ok(term) => println!("{:?}", term),
    Err(err) => eprintln!("{}", err),
  }
}

The complete example is available in ./examples/lambda_term.rs. Run it with:

cargo run --example lambda_term

Credit

This design is based on T6's new parser for HVM-Core, and is much cleaner than the old HOPA approach.

Dependencies

~16KB