A procedural macro for defining nom combinators in simple DSL. Requires nom
v5.0+.
toml
[dependencies]
nom = "7"
nom-rule = "0.1"
The procedural macro rule!
provided by this crate is designed for the ease of writing grammar spec as well as to improve maintainability, it follows these simple rules:
TOKEN
: match the token by token kind. You should provide a parser to eat the next token if the token kind matched. it will get expanded into match_token(TOKEN)
.";"
: match the token by token text. You should provide a parser to eat the next token if the token text matched. it will get expanded into match_text(";")
in this example.#fn_name
: an external nom parser function. In the example above, ident
is a predefined parser for identifiers.a ~ b ~ c
: a sequence of parsers to take one by one. It'll get expanded into nom::sequence::tuple
.(...)+
: one or more repeated patterns. It'll get expanded into nom::multi::many1
.(...)*
: zero or more repeated patterns. It'll get expanded into nom::multi::many0
.(...)?
: Optional parser. It'll get expanded into nom::combinator::opt
.a | b | c
: Choices between a, b, and c. It'll get expanded into nom::branch::alt
.Define match_text
parser and match_token
parser for your custom token type. You can use nom::combinator::fail
as match_token
if your parser use &str
or &[u8]
as input because you won't match on token kinds.
```rust
struct Token<'a> { kind: TokenKind, text: &'a str, span: Span, }
enum TokenKind { Whitespace,
// Keywords
CREATE,
TABLE,
// Symbols
LParen,
RParen,
Semicolon,
Comma,
Ident,
}
fn match_text<'a, Error: ParseError>>( text: &'a str, ) -> impl FnMut(Input<'a>) -> IResult, &'a Token<'a>, Error> { move |i| satisfy(|token: &Token<'a>| token.text == text)(i) }
fn match_token<'a, Error: ParseError>>( kind: TokenKind, ) -> impl FnMut(Input<'a>) -> IResult, &'a Token<'a>, Error> { move |i| satisfy(|token: &Token<'a>| token.kind == kind)(i) } ```
Then give the two parser to nom_rule::rule!
by wrapping it into a custom macro:
rust
macro_rules! rule {
($($tt:tt)*) => { nom_rule::rule!(($crate::match_text), ($crate::match_token), $($tt)*) }
}
To define a parser for the SQL of creating table:
rust
let mut rule = rule!(
CREATE ~ TABLE ~ #ident ~ "(" ~ (#ident ~ #ident ~ ","?)* ~ ")" ~ ";"
);
It will get translated into:
rust
let mut rule = nom::sequence::tuple((
(match_token)(CREATE),
(match_token)(TABLE),
ident,
(match_text)("("),
nom::multi::many0(nom::sequence::tuple((
ident,
ident,
nom::combinator::opt((match_text)(",")),
))),
(match_text)(")"),
(match_text)(";"),
));