quirky_binder_codegen_macro/
lib.rs

1use std::borrow::Cow;
2
3use annotate_snippets::display_list::DisplayList;
4use proc_macro2::Ident;
5use proc_macro_error::{abort_if_dirty, emit_error, proc_macro_error};
6use quirky_binder_codegen::{
7    parse_and_generate_glob_modules, parse_and_generate_module, CodegenError, ErrorEmitter,
8};
9use quirky_binder_lang::snippet::snippet_for_input_and_part;
10use quote::format_ident;
11use syn::{
12    parenthesized,
13    parse::{ParseStream, Parser},
14    token, Error, LitStr,
15};
16
17mod decorators;
18
19struct ProcMacroErrorEmitter<'a> {
20    span: &'a Ident,
21    dirty: bool,
22}
23
24impl<'a> ProcMacroErrorEmitter<'a> {
25    fn handle_codegen_result<T>(&mut self, result: Result<T, CodegenError>) -> T {
26        match result {
27            Ok(res) => {
28                assert!(!self.dirty);
29                res
30            }
31            Err(CodegenError::ErrorEmitted) => {
32                assert!(self.dirty);
33                abort_if_dirty();
34                unreachable!("should be aborted");
35            }
36            Err(CodegenError::Error(error)) => {
37                self.emit_error(error.into());
38                assert!(self.dirty);
39                abort_if_dirty();
40                unreachable!("should be aborted");
41            }
42        }
43    }
44}
45
46impl<'a> ErrorEmitter for ProcMacroErrorEmitter<'a> {
47    fn emit_error(&mut self, error: Cow<str>) -> CodegenError {
48        emit_error!(self.span, "{}", error);
49        self.dirty = true;
50        CodegenError::ErrorEmitted
51    }
52
53    fn emit_quirky_binder_error(&mut self, src: &str, part: &str, error: Cow<str>) {
54        emit_error!(
55            self.span,
56            "{}",
57            DisplayList::from(snippet_for_input_and_part(&error, src, part))
58        );
59        self.dirty = true;
60    }
61
62    fn error(&mut self) -> Result<(), quirky_binder_codegen::CodegenError> {
63        if self.dirty {
64            Err(CodegenError::ErrorEmitted)
65        } else {
66            Ok(())
67        }
68    }
69}
70
71#[derive(PartialEq, Eq, Clone, Debug)]
72enum Definition {
73    Inline {
74        qb: String,
75    },
76    IncludeGlob {
77        src: String,
78        pattern: String,
79        test: bool,
80    },
81}
82
83const INLINE_DEFINITION: &str = "inline";
84const INCLUDE_GLOB_DEFINITION: &str = "include_glob";
85const INCLUDE_GLOB_TEST_DEFINITION: &str = "include_glob_test";
86
87fn parse_def(input: ParseStream) -> Result<(Definition, Ident), Error> {
88    let def_type: Ident = input.parse()?;
89    let def = match def_type.to_string().as_str() {
90        INLINE_DEFINITION => {
91            let content;
92            parenthesized!(content in input);
93            let qb: LitStr = content.parse()?;
94            Definition::Inline { qb: qb.value() }
95        }
96        INCLUDE_GLOB_DEFINITION => {
97            let content;
98            parenthesized!(content in input);
99            Definition::parse_include_glob(&content, false)?
100        }
101        INCLUDE_GLOB_TEST_DEFINITION => {
102            let content;
103            parenthesized!(content in input);
104            Definition::parse_include_glob(&content, true)?
105        }
106        _ => {
107            return Err(Error::new(
108                def_type.span(),
109                format!(
110                    "expected [{}]",
111                    [INLINE_DEFINITION, INCLUDE_GLOB_DEFINITION].join(", ")
112                ),
113            ));
114        }
115    };
116    Ok((def, def_type))
117}
118
119impl Definition {
120    fn parse_include_glob(content: ParseStream, test: bool) -> Result<Definition, Error> {
121        let src: LitStr = content.parse()?;
122        content.parse::<token::Comma>()?;
123        let pattern: LitStr = content.parse()?;
124        Ok(Definition::IncludeGlob {
125            src: src.value(),
126            pattern: pattern.value(),
127            test,
128        })
129    }
130}
131
132fn quirky_binder_1(
133    input: proc_macro::TokenStream,
134    quirky_binder_crate: &Ident,
135) -> proc_macro::TokenStream {
136    let (def, span) = match Parser::parse(parse_def, input) {
137        Ok(res) => res,
138        Err(err) => {
139            abort_if_dirty();
140            return err.into_compile_error().into();
141        }
142    };
143
144    let mut error_emitter = ProcMacroErrorEmitter {
145        span: &span,
146        dirty: false,
147    };
148
149    match def {
150        Definition::Inline { qb } => {
151            let result =
152                parse_and_generate_module(&qb, None, quirky_binder_crate, &mut error_emitter);
153            error_emitter.handle_codegen_result(result).into()
154        }
155        Definition::IncludeGlob { src, pattern, test } => {
156            let result = parse_and_generate_glob_modules(
157                &src,
158                &pattern,
159                quirky_binder_crate,
160                test,
161                &mut error_emitter,
162            );
163            error_emitter.handle_codegen_result(result).into()
164        }
165    }
166}
167
168#[proc_macro]
169#[proc_macro_error]
170pub fn quirky_binder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
171    quirky_binder_1(input, &format_ident!("quirky_binder"))
172}
173
174#[proc_macro]
175#[proc_macro_error]
176pub fn quirky_binder_internal(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
177    quirky_binder_1(input, &format_ident!("crate"))
178}
179
180#[proc_macro]
181pub fn tracking_allocator_static(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182    decorators::tracking_allocator_static(input)
183}
184
185#[proc_macro_attribute]
186pub fn tracking_allocator_main(
187    attr: proc_macro::TokenStream,
188    item: proc_macro::TokenStream,
189) -> proc_macro::TokenStream {
190    decorators::tracking_allocator_main(attr, item)
191}