[][src]Crate combine_proc_macro

A library that allows proc_macro function-like macros to be parsed using the combine parser combinator crate.

Motivation

When writing a #[proc_macro_derive] the input is Rust source code which is well supported by the syn crate. However, when writing a #[proc_macro] macro, it is common to want to define a custom domain specific language.

This crate allows you to write a parser for your DSL using the combine parser combinator library. It also preserves the source span information in the parsed result such that rustc can provide correct source locations for identifiers and literals that are re-used in the output.

Implementing a parser

This is a basic example using base combine parsers.

use combine::{ParseError, Parser, Stream};
use combine_proc_macro::{Token, Literal};
use combine_proc_macro::parser::{delim, keyword, literal, punct};

/// Parses expressions like `{ hello "world"! }`.
fn hello_grammar<I>() -> impl Parser<Input = I, Output = Literal>
where
    I: Stream<Item = Token>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    ( delim('{')
    , keyword("hello")
    , literal()
    , punct('!')
    , delim('}')
    ).map(|(_, _, greeting, _, _)| greeting)
}

Using the parser! macro can help remove boilerplate.

use combine::Parser;
use combine_proc_macro::Literal;
use combine_proc_macro::parser;
use combine_proc_macro::parser::{delim, keyword, literal, punct};

parser!(fn hello_grammar() -> Literal {
    ( delim('{')
    , keyword("hello")
    , literal()
    , punct('!')
    , delim('}')
    ).map(|(_, _, greeting, _, _)| greeting)
});

Implementing a macro

A proc macro must be defined at the crate root within the lib.rs file.

This example is not tested
extern crate proc_macro;

use combine::parser::Parser;
use combine_proc_macro::{Input, Incomplete};
use proc_macro::TokenStream;

#[proc_macro]
pub fn hello_macro(input: TokenStream) -> TokenStream {
    let input = Input::from(input).with_lookahead(1);
    let result = hello_grammar().easy_parse(input);
    let (ast, trailing) = match result {
        Ok(ok) => ok,
        Err(err) => panic!("error parsing in `hello_macro` macro: {:#?}", err),
    };
    if let Some(diagnostic) = Incomplete::from_stream(trailing) {
        panic!("unexpected tokens at end of input:\n\n{}", diagnostic);
    }

    impl_hello_macro(&ast)  // generate rust output; e.g. using the `quote` crate
}

Re-exports

pub use diagnostic::Incomplete;
pub use input::Input;
pub use input::Token;

Modules

diagnostic

Utilities to generate diagnostic error messages.

input

Wrappers and transforms to around proc_macro types to implement combine traits.

parser

A collection of parsers for Tokens (similar to combine::parser::{char, byte, item}).

Macros

parser

A macro to remove the generics boilerplate when defining parsers.

Structs

Ident

A word of Rust code, which may be a keyword or legal variable name.

Literal

A literal string ("hello"), byte string (b"hello"), character ('a'), byte character (b'a'), an integer or floating point number with or without a suffix (1, 1u8, 2.3, 2.3f32).

Punct

An Punct is an single punctuation character like +, - or #.