1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
extern crate proc_macro;

use expry::memorypool::*;
use expry::*;

use litrs::StringLit;

use quote::*;

fn first_string(tokens: proc_macro2::TokenStream) -> String {
    for i in tokens {
        if let Ok(lit) = StringLit::try_from(i) {
            return lit.value().to_string();
        }
    }
    String::new()
}

#[proc_macro]
pub fn expry(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = proc_macro2::TokenStream::from(input);
    let expr = first_string(input);

    let mut allocator = MemoryPool::new();
    let mut scope = allocator.rewind();
    // FIXME: support different number of inputs
    let compile_result = expry_compile_expr(&expr, None, None, &[], &mut scope);
    match compile_result {
        Ok((bytecode,_)) => {
            let lit = proc_macro2::Literal::byte_string(bytecode.get());
            quote!{ ::expry::BytecodeRef(#lit) }.into()
        },
        Err(err) => {
            let msg = expry_compile_error_format_short(&err);
            compile_error_as_expr(&msg)
        },
    }
}

struct Type<'a>(&'a ExpryType);

impl<'a> ToTokens for Type<'a> {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match &self.0 {
            ExpryType::Any => tokens.extend(quote!{ ::expry::ExpryType::Any }),
            ExpryType::Null => tokens.extend(quote!{ ::expry::ExpryType::Null }),
            ExpryType::Bool => tokens.extend(quote!{ ::expry::ExpryType::Bool }),
            ExpryType::Int => tokens.extend(quote!{ ::expry::ExpryType::Int }),
            ExpryType::Float => tokens.extend(quote!{ ::expry::ExpryType::Float }),
            ExpryType::Double => tokens.extend(quote!{ ::expry::ExpryType::Double }),
            ExpryType::String => tokens.extend(quote!{ ::expry::ExpryType::String }),
            ExpryType::Object(v) => {
                let mut spec = proc_macro2::TokenStream::new();
                for (k,(required,v)) in v {
                    let k = String::from_utf8_lossy(k).to_string();
                    let v = Type(v);
                    spec.extend(quote!{ ((#k).as_bytes().to_vec(), (#required,#v)), });
                }
                tokens.extend(quote!{ ::expry::ExpryType::Object(::std::collections::BTreeMap::from([#spec])) });
            },
            ExpryType::UniformObject(v) => {
                let v = Type(v);
                tokens.extend(quote!{ ::expry::ExpryType::UniformObject(::std::boxed::Box::new(#v)) });
            },
            ExpryType::Array(v) => {
                let v = Type(v);
                tokens.extend(quote!{ ::expry::ExpryType::Array(::std::boxed::Box::new(#v)) });
            },
            ExpryType::StaticArray(v) => {
                let mut spec = proc_macro2::TokenStream::new();
                for v in v { 
                    let v = Type(v);
                    spec.extend(quote!{ #v, });
                }
                tokens.extend(quote!{ ::expry::ExpryType::StaticArray(::std::vec::Vec::from([#spec])) });
            },
            ExpryType::EmptyArray => tokens.extend(quote!{ ::expry::ExpryType::EmptyArray }),
            ExpryType::Nullable(v) => {
                let v = Type(v);
                tokens.extend(quote!{ ::expry::ExpryType::Nullable(::std::boxed::Box::new(#v)) });
            },
        }
    }
}

#[proc_macro]
pub fn expry_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = proc_macro2::TokenStream::from(input);
    let expr = first_string(input);

    let mut allocator = MemoryPool::new();
    let mut scope = allocator.rewind();
    let types = expry_parse_type(&expr, &mut scope);
    match types {
        Ok(types) => {
            let types = Type(&types);
            quote!{ #types }.into()
        },
        Err(err) => {
            let msg = expry_compile_error_format_short(&err);
            compile_error_as_expr(&msg)
        },
    }
}

// warning is not supported yet: https://github.com/rust-lang/rust/issues/54140
#[allow(dead_code)]
fn compile_error(string: &str) -> proc_macro::TokenStream {
    let expanded = quote!(
        compile_error!(#string);
        );
    proc_macro::TokenStream::from(expanded)
}

fn compile_error_as_expr(string: &str) -> proc_macro::TokenStream {
    let expanded = quote!(
        compile_error!(#string)
        );
    proc_macro::TokenStream::from(expanded)
}

#[cfg(nightly_build)]
#[allow(dead_code)]
fn compile_warning(span: proc_macro2::Span, message: &str) {
    Diagnostic::spanned(span.unwrap(), proc_macro::Level::Warning, message)
    .emit();
}
#[cfg(not(nightly_build))]
#[allow(dead_code)]
fn compile_warning(_span: proc_macro2::Span, _message: &str) {
}

#[cfg(nightly_build)]
#[allow(dead_code)]
fn compile_error_at(span: proc_macro2::Span, message: &str) {
    Diagnostic::spanned(span.unwrap(), proc_macro::Level::Error, message)
    .emit();
}
#[cfg(not(nightly_build))]
#[allow(dead_code)]
fn compile_error_at(_span: proc_macro2::Span, _message: &str) {
}


#[cfg(test)]
mod tests {
}