tinc_build/codegen/cel/compiler/
mod.rs1use 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}