Skip to main content

xdoc_macros/
lib.rs

1//! Procedural macros for `xdoc`.
2//!
3//! This crate intentionally does not depend on `xdoc`; generated code resolves
4//! the caller's `xdoc-rs` dependency name so the runtime crate remains the
5//! single XML engine.
6
7#[allow(dead_code)]
8mod ast;
9#[allow(dead_code)]
10mod codegen;
11#[allow(dead_code)]
12mod parser;
13
14use proc_macro::TokenStream;
15use proc_macro2::Ident;
16use proc_macro_crate::{crate_name, FoundCrate};
17use quote::quote;
18
19/// Declarative XML macro entry point.
20///
21/// Parses the XML-like template and generates builder calls.
22#[proc_macro]
23pub fn xml(input: TokenStream) -> TokenStream {
24    let input = proc_macro2::TokenStream::from(input);
25
26    match parser::parse_template(input).and_then(|template| {
27        let runtime_path = xdoc_runtime_path().map_err(parser::ParseError::from_message)?;
28        codegen::generate_template(&template, runtime_path).map_err(|error| {
29            parser::ParseError::from_message(format!("code generation failed: {}", error.message()))
30        })
31    }) {
32        Ok(tokens) => tokens.into(),
33        Err(error) => compile_error(error.message()).into(),
34    }
35}
36
37fn xdoc_runtime_path() -> Result<proc_macro2::TokenStream, String> {
38    match crate_name("xdoc-rs").map_err(|error| error.to_string())? {
39        FoundCrate::Itself => Ok(quote! { ::xdoc }),
40        FoundCrate::Name(name) => {
41            if name == "xdoc_rs" {
42                return Ok(quote! { ::xdoc });
43            }
44            let ident = Ident::new(&name, proc_macro2::Span::call_site());
45            Ok(quote! { ::#ident })
46        }
47    }
48}
49
50fn compile_error(message: &str) -> proc_macro2::TokenStream {
51    let message = proc_macro2::Literal::string(message);
52    quote! {
53        compile_error!(#message)
54    }
55}