Skip to content
aaceboPublic

About

A modular Rust syntax toolkit for tokenization, parsing, formatting, diagnostics, and quasi-quoting.

Topics

Resources

Stars

40 stars

Watchers

1 watching

Forks

Repository files navigation

moxy

Bencher

Rust syntax tools for procedural macros: tokens, typed syntax trees, templates, formatting, and diagnostics.

Quick Start

cargo add moxy --features template,fmt
use moxy::ast::Item;

let name = "Widget";
let tokens = moxy::template! {
    pub struct {{ name }};
};

let item: Item = moxy::parse!(tokens).unwrap();

assert_eq!(item.as_struct().unwrap().ident.text(), "Widget");
assert_eq!(moxy::fmt!(&item).unwrap(), "pub struct Widget;");

Features

Default features are token and ast.

Feature Default Enables
token yes Token streams, spans, parsing, and token construction
ast yes Typed Rust syntax trees; implies token
template no template! and paste!; implies token
fmt no AST formatting with fmt!; implies ast
diagnostic no Span-aware error, warning, note, and help diagnostics
build no Cargo build-script and rustc-version helpers
macros no #[derive(ToTokens)], #[derive(FromMeta)], and macro-authoring attributes
derives no Standard trait derives for supported AST and template types
serde no Serialization for supported token, AST, and formatting types
proc-macro2 no Conversions between moxy and proc_macro2 tokens
full no Every feature above

Choose only the layers you need:

cargo add moxy --no-default-features --features token,ast

Feature guide

Tokens

The token feature is the foundation, similar in role to proc-macro2.

use moxy::Token;
use moxy::token::ident;

let name = ident!(Generated, "_", Item);
let comma: Token![,] = Default::default();

assert_eq!(name.to_string(), "Generated_Item");
assert_eq!(comma.as_str(), ",");

Abstract Syntax Tree

The ast feature provides typed entry points such as Item, Expr, and Type, following the same parse-at-the-level-you-need style as syn.

use moxy::ast::{Expr, Item, Type};

let item: Item = moxy::parse!("pub struct User { id: u64 }").unwrap();
let ty: Type = moxy::parse!("Option<Result<T, E>>").unwrap();
let expr: Expr = moxy::parse!("items.next()?").unwrap();

assert!(item.is_struct());
assert!(ty.is_path());
assert!(expr.is_unary());

Templates

The template feature builds token streams with interpolation and control flow in the style of quote!.

let fields = ["id", "name"];

let tokens = moxy::template! {
    struct User {
        @for field in fields {
            {{ field }}: String,
        }
    }
};

assert!(tokens.to_string().contains("struct User"));

paste! creates identifiers at expansion time:

moxy::paste! {
    fn {{ read_ value }}() -> u32 { 7 }
}

assert_eq!(read_value(), 7);

Template control flow accepts ordinary Rust bindings and may omit the header parentheses. @if let and destructuring @for bindings work directly, while @match arms accept Rust patterns and guards:

let value = Some("enabled");
let fields = [("name", "String")];

let tokens = moxy::template! {
    @if let Some(value) = value { const ENABLED: &str = {{ value }}; }
    struct Config { @for (name, ty) in &fields { {{ name }}: {{ ty }}, } }
};

Interpolations can also use a Rust statement block when their final expression is the value to emit:

let tokens = moxy::template! {
    {{
        let value = "generated".to_string();
        value
    }}
};

Formatting

The fmt feature formats parsed syntax trees with configurable width, indentation, and newlines.

use moxy::ast::Item;
use moxy::fmt::{FmtConfig, Indent};

let item: Item = moxy::parse!("struct User { id: u64, name: String }").unwrap();
let config = FmtConfig::default().with_indent(Indent::space(2));
let output = moxy::fmt!(&item, config).unwrap();

assert_eq!(output, "struct User {\n  id: u64,\n  name: String,\n}");

Diagnostics

The diagnostic feature builds span-aware diagnostics with a stable compile_error! fallback.

let tokens = moxy::error!(
    "missing template",
    [moxy::help!("add #[template { ... }]")],
)
.emit();

assert!(tokens.to_string().contains("compile_error"));

Derive

ToTokens

Enable macros to implement ToTokens from an inline template.

cargo add moxy --features macros
use moxy::token::ToTokenStream;

#[derive(moxy::ToTokens)]
#[moxy(template { const VALUE: &str = {{ self.value }}; })]
struct Generated {
    value: String,
}

let tokens = Generated { value: "seven".into() }.to_token_stream();
assert!(tokens.to_string().contains("VALUE"));

On nightly Rust, add #[moxy(debug)] beside #[moxy(template { ... })] to print the parsed declaration and generated implementation as compiler notes. Stable Rust does not emit these debug notes.

FromMeta

#[derive(moxy::FromMeta)] converts structured attributes into a named struct or enum. Call Attributed::parse_meta on an AST node to parse the first matching attribute. The derive adds FromMeta bounds for generic type parameters.

use moxy::ast::Attributed;

#[derive(moxy::FromMeta)]
struct BuildArgs {
    // Field names are meta-item names unless renamed.
    // `default` uses `None` when `rename` is absent.
    #[meta(default)]
    rename: Option<String>,

    // Parse `default` into `is_default`; use `default = expr` for a custom fallback.
    #[meta(rename = "default", default)]
    is_default: bool,

    // Customize the error for this required value.
    #[meta(message = "missing build name")]
    name: String,
}

fn build_args(field: &moxy::ast::Field) -> Result<Option<BuildArgs>, moxy::ast::ParseError> {
    // Parse the first `#[build(...)]` attribute on this field.
    field.parse_meta("build")
}

Custom derive macros

Use #[moxy::derive(Name)] to turn a public function that accepts one parseable moxy AST type into a custom derive macro. The derive name is the identifier in the attribute; the function name remains an implementation detail.

use moxy::ast::{ItemStruct, ParseError};
use moxy::token::TokenStream;

#[moxy::derive(Builder)]
pub fn builder(item: ItemStruct) -> Result<TokenStream, ParseError> {
    Ok(moxy::template! {
        impl {{ item.ident }} {
            pub fn builder() -> Self { todo!() }
        }
    })
}

Downstream users invoke the generated derive normally:

#[derive(Builder)]
struct Config;

The annotated function must be public, take exactly one parameter, and return Result<TokenStream, ParseError>. Its parameter may be any moxy type that implements Parse: ItemStruct limits the derive to structs, ItemEnum limits it to enums, and other syntax types work the same way. Use moxy::ast::Declaration when the derive accepts any declaration and chooses the supported variants itself.

#[moxy::derive(EnumName)]
pub fn enum_name(item: moxy::ast::ItemEnum) -> Result<moxy::token::TokenStream, moxy::ast::ParseError> {
    Ok(moxy::template! {
        impl {{ item.ident }} {
            pub const NAME: &'static str = stringify!({{ item.ident }});
        }
    })
}

See the complete examples/builder implementation and its consumer test for a builder derive that accepts Declaration and explicitly checks for a struct.

Function

Enable macros to turn a public token-to-token function into a function-like procedural macro with #[moxy::function]. The function accepts one TokenStream argument and returns Result<TokenStream, ParseError>.

use moxy::ast::ParseError;
use moxy::token::TokenStream;

#[moxy::function(name = "world_hello")]
pub fn render(_tokens: TokenStream) -> Result<TokenStream, ParseError> {
    Ok(moxy::template! {
        println!("world, hello")
    })
}

world_hello!();

Omit name = "…" to export a macro with the annotated function's name. On nightly Rust, add debug (for example, #[moxy::function(debug)]) to emit the generated wrapper as a compiler note. Stable Rust does not emit this note.

Attribute

Enable macros to turn a public function taking the attribute arguments and annotated item into an attribute procedural macro with #[moxy::attribute].

use moxy::ast::ParseError;
use moxy::token::TokenStream;

#[moxy::attribute(name = "passthrough")]
pub fn expand(_meta: TokenStream, item: TokenStream) -> Result<TokenStream, ParseError> {
    Ok(item)
}

#[passthrough]
fn generated() {}

The function receives the attribute arguments first and the annotated item second. Omit name = "…" to export an attribute with the function's name. On nightly Rust, add debug (for example, #[moxy::attribute(debug)]) to emit the generated wrapper as a compiler note. Stable Rust does not emit this note.

Integrations

serde adds serialization for supported token, AST, and formatter types. proc-macro2 adds token conversions for interoperability with the wider procedural-macro ecosystem.

About

A modular Rust syntax toolkit for tokenization, parsing, formatting, diagnostics, and quasi-quoting.

Topics

Resources

Stars

40 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages