tinc_build/codegen/cel/compiler/
mod.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use quote::{ToTokens, quote};
5use syn::parse_quote;
6use tinc_cel::{CelEnum, CelError, CelValue, CelValueConv};
7
8use super::functions::{Function, add_to_compiler};
9use super::types::CelType;
10use crate::types::{ProtoPath, ProtoTypeRegistry};
11
12mod helpers;
13mod resolve;
14
15#[derive(Clone, Debug)]
16#[allow(clippy::large_enum_variant)]
17pub(crate) enum CompiledExpr {
18    Runtime(RuntimeCompiledExpr),
19    Constant(ConstantCompiledExpr),
20}
21
22impl CompiledExpr {
23    pub(crate) fn constant(value: impl CelValueConv<'static>) -> Self {
24        Self::Constant(ConstantCompiledExpr { value: value.conv() })
25    }
26
27    pub(crate) fn runtime(ty: CelType, expr: syn::Expr) -> Self {
28        Self::Runtime(RuntimeCompiledExpr { expr, ty })
29    }
30}
31
32#[derive(Clone)]
33pub(crate) struct RuntimeCompiledExpr {
34    pub expr: syn::Expr,
35    pub ty: CelType,
36}
37
38#[derive(Debug, Clone)]
39pub(crate) struct ConstantCompiledExpr {
40    pub value: tinc_cel::CelValue<'static>,
41}
42
43impl std::fmt::Debug for RuntimeCompiledExpr {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("RuntimeCompiledExpr")
46            .field("ty", &self.ty)
47            .field(
48                "expr",
49                &fmtools::fmt(|fmt| {
50                    let expr = &self.expr;
51                    let tokens = parse_quote! {
52                        const _: Debug = #expr;
53                    };
54                    let pretty = prettyplease::unparse(&tokens);
55                    let pretty = pretty.trim();
56                    let pretty = pretty.strip_prefix("const _: Debug =").unwrap_or(pretty);
57                    let pretty = pretty.strip_suffix(';').unwrap_or(pretty);
58                    fmt.write_str(pretty.trim())
59                }),
60            )
61            .finish()
62    }
63}
64
65impl ToTokens for RuntimeCompiledExpr {
66    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
67        self.expr.to_tokens(tokens);
68    }
69}
70
71impl ToTokens for ConstantCompiledExpr {
72    fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
73        fn value_to_tokens(value: &CelValue) -> proc_macro2::TokenStream {
74            match value {
75                CelValue::Bool(b) => quote! {
76                    ::tinc::__private::cel::CelValue::Bool(#b)
77                },
78                CelValue::Bytes(b) => {
79                    let b = syn::LitByteStr::new(b.as_ref(), proc_macro2::Span::call_site());
80                    quote! {
81                        ::tinc::__private::cel::CelValue::Bytes(
82                            ::tinc::__private::cel::CelBytes::Borrowed(
83                                #b
84                            )
85                        )
86                    }
87                }
88                CelValue::Enum(CelEnum { tag, value }) => {
89                    let tag = tag.as_ref();
90                    quote! {
91                        ::tinc::__private::cel::CelValue::Enum(
92                            ::tinc::__private::cel::CelValue::CelEnum {
93                                tag: ::tinc::__private::cel::CelValue::CelString::Borrowed(#tag),
94                                value: #value,
95                            }
96                        )
97                    }
98                }
99                CelValue::List(list) => {
100                    let list = list.iter().map(value_to_tokens);
101                    quote! {
102                        ::tinc::__private::cel::CelValue::List([
103                            #(#list),*
104                        ].into_iter().collect())
105                    }
106                }
107                CelValue::Map(map) => {
108                    let map = map
109                        .iter()
110                        .map(|(key, value)| (value_to_tokens(key), value_to_tokens(value)))
111                        .map(|(key, value)| quote!((#key, #value)));
112                    quote! {
113                        ::tinc::__private::cel::CelValue::List([
114                            #(#map),*
115                        ].into_iter().collect())
116                    }
117                }
118                CelValue::Null => quote!(::tinc::__private::cel::CelValue::Null),
119                CelValue::Number(tinc_cel::NumberTy::F64(f)) => {
120                    quote!(::tinc::__private::cel::CelValue::Number(::tinc::__private::cel::NumberTy::F64(#f)))
121                }
122                CelValue::Number(tinc_cel::NumberTy::I64(i)) => {
123                    quote!(::tinc::__private::cel::CelValue::Number(::tinc::__private::cel::NumberTy::I64(#i)))
124                }
125                CelValue::Number(tinc_cel::NumberTy::U64(u)) => {
126                    quote!(::tinc::__private::cel::CelValue::Number(::tinc::__private::cel::NumberTy::U64(#u)))
127                }
128                CelValue::String(s) => {
129                    let s = s.as_ref();
130                    quote!(::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed(#s)))
131                }
132                CelValue::Duration(b) => {
133                    let secs = b.num_seconds();
134                    let nanos = b.subsec_nanos();
135                    quote! {
136                        ::tinc::__private::cel::CelValue::Duration(
137                            ::tinc::reexports::chrono::Duration::new(
138                                #secs,
139                                #nanos,
140                            ).expect("duration was valid at build")
141                        )
142                    }
143                }
144                CelValue::Timestamp(ts) => {
145                    let tz_offset = ts.offset().local_minus_utc();
146                    let utc = ts.to_utc();
147                    let ts_secs = utc.timestamp();
148                    let ts_nanos = utc.timestamp_subsec_nanos();
149                    quote! {
150                        ::tinc::__private::cel::CelValue::Timestamp(
151                            ::tinc::reexports::chrono::TimeZone::timestamp_opt(
152                                &::tinc::reexports::chrono::offset::FixedOffset::east_opt(#tz_offset)
153                                    .expect("codegen from build"),
154                                #ts_secs,
155                                #ts_nanos,
156                            ).unwrap()
157                        )
158                    }
159                }
160            }
161        }
162
163        value_to_tokens(&self.value).to_tokens(stream);
164    }
165}
166
167impl ToTokens for CompiledExpr {
168    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
169        match self {
170            Self::Constant(c) => c.to_tokens(tokens),
171            Self::Runtime(r) => r.to_tokens(tokens),
172        }
173    }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub(crate) enum CompilerTarget {
178    Serde,
179    #[allow(dead_code)]
180    Proto,
181}
182
183#[derive(Clone, Debug)]
184pub(crate) struct Compiler<'a> {
185    parent: Option<&'a Compiler<'a>>,
186    registry: &'a ProtoTypeRegistry,
187    target: Option<CompilerTarget>,
188    variables: BTreeMap<String, CompiledExpr>,
189    functions: BTreeMap<&'static str, DebugFunc>,
190}
191
192#[derive(Clone)]
193struct DebugFunc(Arc<dyn Function + Send + Sync + 'static>);
194impl std::fmt::Debug for DebugFunc {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.write_str(self.0.name())
197    }
198}
199
200impl<'a> Compiler<'a> {
201    pub(crate) fn empty(registry: &'a ProtoTypeRegistry) -> Self {
202        Self {
203            parent: None,
204            registry,
205            target: None,
206            variables: BTreeMap::new(),
207            functions: BTreeMap::new(),
208        }
209    }
210
211    pub(crate) fn new(registry: &'a ProtoTypeRegistry) -> Self {
212        let mut compiler = Self::empty(registry);
213
214        add_to_compiler(&mut compiler);
215
216        compiler
217    }
218
219    pub(crate) fn set_target(&mut self, target: impl Into<Option<CompilerTarget>>) {
220        self.target = target.into()
221    }
222
223    pub(crate) fn target(&self) -> Option<CompilerTarget> {
224        self.target
225    }
226
227    pub(crate) fn child(&self) -> Compiler<'_> {
228        Compiler {
229            parent: Some(self),
230            registry: self.registry,
231            target: self.target,
232            variables: BTreeMap::new(),
233            functions: BTreeMap::new(),
234        }
235    }
236}
237
238#[derive(Debug, Clone)]
239pub(crate) struct CompilerCtx<'a> {
240    pub this: Option<CompiledExpr>,
241    pub args: &'a [cel_parser::Expression],
242    compiler: Compiler<'a>,
243}
244
245impl<'a> CompilerCtx<'a> {
246    pub(crate) fn new(compiler: Compiler<'a>, this: Option<CompiledExpr>, args: &'a [cel_parser::Expression]) -> Self {
247        Self { this, args, compiler }
248    }
249}
250
251impl<'a> std::ops::Deref for CompilerCtx<'a> {
252    type Target = Compiler<'a>;
253
254    fn deref(&self) -> &Self::Target {
255        &self.compiler
256    }
257}
258
259impl std::ops::DerefMut for CompilerCtx<'_> {
260    fn deref_mut(&mut self) -> &mut Self::Target {
261        &mut self.compiler
262    }
263}
264
265impl<'a> Compiler<'a> {
266    pub(crate) fn add_variable(&mut self, name: &str, expr: CompiledExpr) {
267        self.variables.insert(name.to_owned(), expr.clone());
268    }
269
270    pub(crate) fn register_function(&mut self, f: impl Function) {
271        let name = f.name();
272        self.functions.insert(name, DebugFunc(Arc::new(f)));
273    }
274
275    pub(crate) fn resolve(&self, expr: &cel_parser::Expression) -> Result<CompiledExpr, CompileError> {
276        resolve::resolve(self, expr)
277    }
278
279    pub(crate) fn get_variable(&self, name: &str) -> Option<&CompiledExpr> {
280        match self.variables.get(name) {
281            Some(expr) => Some(expr),
282            None => match self.parent {
283                Some(parent) => parent.get_variable(name),
284                None => None,
285            },
286        }
287    }
288
289    pub(crate) fn get_function(&self, name: &str) -> Option<&Arc<dyn Function + Send + Sync + 'static>> {
290        match self.functions.get(name) {
291            Some(func) => Some(&func.0),
292            None => match self.parent {
293                Some(parent) => parent.get_function(name),
294                None => None,
295            },
296        }
297    }
298
299    pub(crate) fn registry(&self) -> &'a ProtoTypeRegistry {
300        self.registry
301    }
302}
303
304#[derive(Debug, Clone, PartialEq, thiserror::Error)]
305pub(crate) enum CompileError {
306    #[error("not implemented")]
307    NotImplemented,
308    #[error("invalid syntax: {message} - {syntax}")]
309    InvalidSyntax { message: String, syntax: &'static str },
310    #[error("type conversion error on type {ty:?}: {message}")]
311    TypeConversion { ty: Box<CelType>, message: String },
312    #[error("member access error on type {ty:?}: {message}")]
313    MemberAccess { ty: Box<CelType>, message: String },
314    #[error("variable not found: {0}")]
315    VariableNotFound(String),
316    #[error("function not found: {0}")]
317    FunctionNotFound(String),
318    #[error("unsupported function call identifier type: {0:?}")]
319    UnsupportedFunctionCallIdentifierType(cel_parser::Expression),
320    #[error("missing message: {0}")]
321    MissingMessage(ProtoPath),
322}
323
324impl CompileError {
325    pub(crate) fn syntax(message: impl std::fmt::Display, func: &impl Function) -> CompileError {
326        CompileError::InvalidSyntax {
327            message: message.to_string(),
328            syntax: func.syntax(),
329        }
330    }
331}
332
333impl From<CelError<'_>> for CompileError {
334    fn from(value: CelError<'_>) -> Self {
335        Self::TypeConversion {
336            ty: Box::new(CelType::CelValue),
337            message: value.to_string(),
338        }
339    }
340}