evegfx_macros/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::quote;
7use syn::parse::{Parse, ParseStream, Result};
8use syn::parse_macro_input;
9use syn::punctuated::Punctuated;
10use syn::{Expr, ExprLit, ExprPath, Lit, LitStr, Token};
11
12mod parsers;
13
14/// Prepare a format string and associated arguments for use with an EVE
15/// coprocessor widget which supports the `OPT_FORMAT` option.
16#[proc_macro]
17pub fn eve_format(input: TokenStream) -> TokenStream {
18    let call = parse_macro_input!(input as EVEFormat);
19    let fmt = call.fmt;
20    let args = call.args;
21
22    let fmt_src = &fmt.value().into_bytes()[..];
23    let mut format_chars: Vec<u8> = Vec::with_capacity(fmt_src.len());
24    let mut arg_elems: Punctuated<Expr, syn::Token![,]> = Punctuated::new();
25
26    let int_variant_path: ExprPath =
27        syn::parse_str("::evegfx::commands::strfmt::Argument::Int").unwrap();
28    let uint_variant_path: ExprPath =
29        syn::parse_str("::evegfx::commands::strfmt::Argument::UInt").unwrap();
30    let char_variant_path: ExprPath =
31        syn::parse_str("::evegfx::commands::strfmt::Argument::Char").unwrap();
32    let string_variant_path: ExprPath =
33        syn::parse_str("::evegfx::commands::strfmt::Argument::String").unwrap();
34
35    let mut remain = fmt_src.clone();
36    let mut next_arg = 0;
37    let mut needs_fmt = false;
38    while remain.len() > 0 {
39        use parsers::Token::*;
40        let (token, next) = parsers::next_token(remain);
41        remain = next;
42        match token {
43            Literal(bytes) => {
44                format_chars.extend(bytes);
45            }
46            Verb(bytes) => {
47                needs_fmt = true;
48                if next_arg >= args.len() {
49                    let err = syn::Error::new(
50                        fmt.span(),
51                        format!("not enough arguments to populate {} verbs", next_arg + 1),
52                    );
53                    return err.into_compile_error().into();
54                }
55                let given_expr = args[next_arg].clone();
56                next_arg += 1;
57
58                format_chars.extend(bytes);
59                // Our parser ensures that a format verb always includes at
60                // least two bytes: the % and the verb letter. There might
61                // be other stuff in between but we don't need to worry
62                // about those because they'll be interpreted by EVE's
63                // coprocessor, not by us. Our only goal here is to figure
64                // out which enum variant to select for the argument.
65                let mode = *bytes.last().unwrap();
66                match mode {
67                    b'd' | b'i' => {
68                        let arg_expr = enum_variant_expr(int_variant_path.clone(), given_expr);
69                        arg_elems.push(arg_expr);
70                    }
71                    b'u' | b'o' | b'x' | b'X' => {
72                        let arg_expr = enum_variant_expr(uint_variant_path.clone(), given_expr);
73                        arg_elems.push(arg_expr);
74                    }
75                    b'c' => {
76                        let arg_expr = enum_variant_expr(char_variant_path.clone(), given_expr);
77                        arg_elems.push(arg_expr);
78                    }
79                    b's' => {
80                        let arg_expr = enum_variant_expr(string_variant_path.clone(), given_expr);
81                        arg_elems.push(arg_expr);
82                    }
83                    // TODO: string pointers (%s) too
84                    _ => {
85                        // This is safe because our parser only allows ASCII
86                        // letters as format strings.
87                        use std::convert::TryInto;
88                        let letter: char = mode.try_into().unwrap();
89
90                        let err = syn::Error::new(
91                            fmt.span(),
92                            format!("unsupported format verb '%{}'", letter),
93                        );
94                        return err.into_compile_error().into();
95                    }
96                }
97            }
98            Percent(bytes) => {
99                needs_fmt = true;
100                format_chars.extend(bytes);
101            }
102            Null(_) => {
103                // EVE's coprocessor considers a literal null to be a string
104                // terminator, so we'll encode it instead as a format sequence
105                // that inserts the null character in order to avoid producing
106                // an invalid message.
107                needs_fmt = true;
108                format_chars.extend(b"%c");
109                let arg_expr = enum_variant_expr(
110                    uint_variant_path.clone(),
111                    Expr::Lit(ExprLit {
112                        attrs: Vec::new(),
113                        lit: Lit::Int(syn::LitInt::new("0", fmt.span())),
114                    }),
115                );
116                arg_elems.push(arg_expr);
117            }
118            Unterminated(_) => {
119                let err = syn::Error::new(fmt.span(), "unterminated format sequence");
120                return err.into_compile_error().into();
121            }
122            Invalid(_) => {
123                let err = syn::Error::new(fmt.span(), "invalid format sequence");
124                return err.into_compile_error().into();
125            }
126        };
127    }
128
129    if next_arg < args.len() {
130        use syn::spanned::Spanned;
131        let error_span = args[next_arg].span();
132        let err = syn::Error::new(error_span, "too many arguments for format string");
133        return err.into_compile_error().into();
134    }
135
136    // EVE expects format strings to be null-terminated.
137    format_chars.push(0);
138
139    let mut args: Punctuated<Expr, syn::Token![,]> = Punctuated::new();
140    args.push(byte_string_expr(&format_chars, fmt.span()));
141    if needs_fmt {
142        args.push(array_slice_expr(arg_elems));
143    }
144
145    if needs_fmt {
146        quote!(
147            ::evegfx::commands::strfmt::Message::new(#args)
148        )
149    } else {
150        quote!(
151            ::evegfx::commands::strfmt::Message::new_literal(#args)
152        )
153    }
154    .into()
155}
156
157fn enum_variant_expr(path: ExprPath, val: Expr) -> syn::Expr {
158    let mut args: Punctuated<Expr, syn::Token![,]> = Punctuated::new();
159    args.push(val);
160    syn::Expr::Call(syn::ExprCall {
161        attrs: Vec::new(),
162        func: Box::new(Expr::Path(path)),
163        args: args,
164        paren_token: syn::token::Paren {
165            span: Span::call_site(),
166        },
167    })
168}
169
170fn byte_string_expr(bytes: &[u8], span: proc_macro2::Span) -> syn::Expr {
171    syn::Expr::Lit(syn::ExprLit {
172        attrs: Vec::new(),
173        lit: syn::LitByteStr::new(bytes, span).into(),
174    })
175}
176
177fn array_slice_expr(elems: Punctuated<syn::Expr, syn::Token![,]>) -> syn::Expr {
178    syn::Expr::Reference(syn::ExprReference {
179        attrs: Vec::new(),
180        and_token: syn::token::And {
181            spans: [Span::call_site()],
182        },
183        mutability: None,
184        expr: Box::new(syn::Expr::Array(syn::ExprArray {
185            attrs: Vec::new(),
186            bracket_token: syn::token::Bracket {
187                span: Span::call_site(),
188            },
189            elems: elems,
190        })),
191        raw: std::default::Default::default(),
192    })
193}
194
195struct EVEFormat {
196    fmt: LitStr,
197    args: Punctuated<Expr, syn::Token![,]>,
198}
199
200impl Parse for EVEFormat {
201    fn parse(input: ParseStream) -> Result<Self> {
202        let fmt: LitStr = input.parse()?;
203        let args: Punctuated<Expr, syn::Token![,]> = if let Ok(_) = input.parse::<Token![,]>() {
204            Punctuated::parse_terminated(input)?
205        } else {
206            Punctuated::new()
207        };
208
209        Ok(EVEFormat {
210            fmt: fmt,
211            args: args,
212        })
213    }
214}