Skip to content
forked from dtolnay/syn

Nom parser for Rust source code

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

utkarshkukreti/syn

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nom parser for Rust source code

Build Status Latest Version Rust Documentation

Parse Rust structs and enums without a Syntex dependency, intended for use with Macros 1.1.

Designed for fast compile time.

  • Compile time for syn (from scratch including all dependencies): 4 seconds
  • Compile time for the syntex/quasi/aster stack: 60+ seconds

If you get stuck with Macros 1.1 I am happy to provide help even if the issue is not related to syn. Please file a ticket in this repo.

Usage with Macros 1.1

[dependencies]
syn = "0.10"
quote = "0.3"

[lib]
proc-macro = true
#![feature(proc_macro, proc_macro_lib)]

extern crate proc_macro;
use proc_macro::TokenStream;

extern crate syn;

#[macro_use]
extern crate quote;

#[proc_macro_derive(MyMacro)]
pub fn my_macro(input: TokenStream) -> TokenStream {
    let source = input.to_string();

    // Parse the string representation into a syntax tree
    let ast = syn::parse_macro_input(&source).unwrap();

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
        // ...
    };

    // Parse back to a token stream and return it
    expanded.parse().unwrap()
}

Complete example

Suppose we have the following simple trait which returns the number of fields in a struct:

trait NumFields {
    fn num_fields() -> usize;
}

A complete Macros 1.1 implementation of #[derive(NumFields)] based on syn and quote looks like this:

#![feature(proc_macro, proc_macro_lib)]

extern crate proc_macro;
use proc_macro::TokenStream;

extern crate syn;

#[macro_use]
extern crate quote;

#[proc_macro_derive(NumFields)]
pub fn num_fields(input: TokenStream) -> TokenStream {
    let source = input.to_string();

    // Parse the string representation into a syntax tree
    let ast = syn::parse_macro_input(&source).unwrap();

    // Build the output
    let expanded = expand_num_fields(&ast);

    // Return the generated impl as a TokenStream
    expanded.parse().unwrap()
}

fn expand_num_fields(ast: &syn::MacroInput) -> quote::Tokens {
    let n = match ast.body {
        syn::Body::Struct(ref data) => data.fields().len(),
        syn::Body::Enum(_) => panic!("#[derive(NumFields)] can only be used with structs"),
    };

    // Used in the quasi-quotation below as `#name`
    let name = &ast.ident;

    // Helper is provided for handling complex generic types correctly and effortlessly
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    quote! {
        // The generated impl
        impl #impl_generics ::mycrate::NumFields for #name #ty_generics #where_clause {
            fn num_fields() -> usize {
                #n
            }
        }
    }
}

Testing

Macros 1.1 has a restriction that your proc-macro crate must export nothing but proc_macro_derive functions, and also proc_macro_derive procedural macros cannot be used from the same crate in which they are defined. These restrictions may be lifted in the future but for now they make writing tests a bit trickier than for other types of code.

In particular, you will not be able to write test functions like #[test] fn it_works() { ... } in line with your code. Instead, either put tests in a tests directory or in a separate crate entirely.

Additionally, if your procedural macro implements a particular trait, that trait must be defined in a separate crate from the procedural macro.

As a concrete example, suppose your procedural macro crate is called my_derive and it implements a trait called my_crate::MyTrait. Your unit tests for the procedural macro can go in my_derive/tests/test.rs or into a separate crate my_tests/tests/test.rs. Either way the test would look something like this:

#![feature(proc_macro)]

#[macro_use]
extern crate my_derive;

extern crate my_crate;
use my_crate::MyTrait;

#[test]
fn it_works() {
    #[derive(MyTrait)]
    struct S { /* ... */ }

    /* test the thing */
}

Debugging

When developing a procedural macro it can be helpful to look at what the generated code looks like. Use cargo rustc -- -Zunstable-options --pretty=expanded or the cargo expand subcommand.

To show the expanded code for some crate that uses your procedural macro, run cargo expand from that crate. To show the expanded code for one of your own test cases, run cargo expand --test the_test_case where the last argument is the name of the test file without the .rs extension.

Optional features

Syn puts a lot of functionality behind optional features in order to optimize compile time for the most common use cases. These are the available features and their effect on compile time. Dependencies are included in the compile times.

Features Compile time Functionality
(none) 1 sec The data structures representing the AST of Rust structs, enums, and types.
parsing 4 sec Parsing Rust source code containing structs and enums into an AST.
printing 2 sec Printing an AST of structs and enums as Rust source code.
parsing, printing 4 sec This is the default. Parsing and printing of Rust structs and enums. This is typically what you want for implementing Macros 1.1 custom derives.
full 2 sec The data structures representing the full AST of all possible Rust code.
full, parsing 7 sec Parsing any valid Rust source code to an AST.
full, printing 4 sec Turning an AST into Rust source code.
full, parsing, printing 8 sec Parsing and printing any Rust syntax.
full, parsing, printing, expand 9 sec Expansion of custom derives in a file of Rust code. This is typically what you want for expanding custom derives on stable Rust using a build script.
full, parsing, printing, expand, pretty 60 sec Expansion of custom derives with pretty-printed output. This is what you want when iterating on or debugging a custom derive, but the pretty printing should be disabled once you get everything working.

Custom derives on stable Rust

Syn supports a way of expanding custom derives from a build script, similar to what Serde is able to do with serde_codegen. The advantage of using Syn for this purpose rather than Syntex is much faster compile time.

Continuing with the NumFields example from above, it can be extended to support stable Rust like this. One or more custom derives are added to a Registry, which is then able to expand those derives in a source file at a particular path and write the expanded output to a different path. A custom derive is represented by the CustomDerive trait which takes a MacroInput (either a struct or an enum) and expands it into zero or more new items and maybe a modified or unmodified instance of the original input.

pub fn expand_file<S, D>(src: S, dst: D) -> Result<(), String>
    where S: AsRef<Path>,
          D: AsRef<Path>
{
    let mut registry = syn::Registry::new();
    registry.add_derive("NumFields", |input| {
        let tokens = expand_num_fields(&input);
        let items = syn::parse_items(&tokens.to_string()).unwrap();
        Ok(syn::Expanded {
            new_items: items,
            original: Some(input),
        })
    });
    registry.expand_file(src, dst)
}

The codegen can be invoked from a build script as follows.

extern crate your_codegen;

use std::env;
use std::path::Path;

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();

    let src = Path::new("src/codegen_types.in.rs");
    let dst = Path::new(&out_dir).join("codegen_types.rs");

    your_codegen::expand_file(&src, &dst).unwrap();
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

Nom parser for Rust source code

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Rust 100.0%