Skip to main content

cel_parser/
compile.rs

1//! Compiles retained syntax directly through the parser's runtime emission context.
2//!
3//! [`compile_expr`] borrows compile-time environments and creates a fresh execution owner.
4//! Its external identifier emitter can install full runtime descriptors on the supplied segment.
5
6use cel_runtime::{DynSegment, ValueType};
7
8use crate::{
9    DynSegmentContext, Expr, ExprSpan, Literal, LogicalOp, OpLookup, ParseError, ParserContext,
10    Result, SourceSpan, TypeResolver,
11};
12
13/// Compiles retained syntax into an independently owned executable segment.
14///
15/// Borrows `lookup` and `types` only during compilation. `resolve` emits an external
16/// identifier and returns `true`, or leaves the segment unchanged and returns `false`
17/// to continue normal lookup. An emitted value must carry its complete runtime
18/// descriptor, including tuple, array, record, or callable shape.
19///
20/// `resolve` must provide segment-local mutable execution owners for each compilation.
21/// Intentionally shared host input state and immutable definitions may remain shared.
22/// The compiler does not enforce freshness of captured execution resources.
23///
24/// Noncapturing closure bodies resolve only their own formals and persistent library
25/// names; they cannot consult `resolve` or transient lookup scopes. Each compilation
26/// creates fresh branch and closure execution resources. Neither syntax nor environments
27/// need to outlive the returned segment.
28///
29/// # Errors
30/// Returns a spanned error for unresolved names, duplicate/reserved parameters, incompatible operands, invalid annotations,
31/// or external emitter failures. Partially emitted resources remain safely discardable.
32///
33/// - Complexity: O(n) traversal time and space, plus type resolution, linear formal-name
34///   lookup and operation dispatch costs for the n syntax nodes.
35///
36/// # Examples
37/// ```
38/// use cel_parser::{AstContext, OpLookup, Parser, builtin_type_resolver, compile_expr};
39/// let expr = Parser::<AstContext>::new(OpLookup::new())
40///     .parse_str_ast("false && (1i32 / 0i32 == 0i32)").unwrap();
41/// let types = builtin_type_resolver();
42/// let mut segment = compile_expr(&expr, &OpLookup::new(), types.as_ref(),
43///     &mut |_, _, _| Ok(false)).unwrap();
44/// drop(expr);
45/// assert!(!segment.call0::<bool>().unwrap());
46/// ```
47pub fn compile_expr<F>(
48    expr: &Expr,
49    lookup: &OpLookup,
50    types: &dyn TypeResolver,
51    resolve: &mut F,
52) -> Result<DynSegment>
53where
54    F: FnMut(&str, &mut DynSegment, SourceSpan) -> anyhow::Result<bool>,
55{
56    let mut context = DynSegmentContext::new_context();
57    emit(expr, &mut context, lookup, types, resolve, None)?;
58    Ok(context.into_inner())
59}
60
61/// Emits one syntax tree in source evaluation order, isolating names in closure bodies.
62///
63/// `locals == None` selects the external environment; `Some` selects only the current
64/// closure's formal bindings and persistent lookup scopes.
65///
66/// # Errors
67/// Returns a spanned semantic or external-emission error.
68/// - Complexity: O(n) traversal time and stack space, plus dispatch and annotation resolution.
69fn emit<F>(
70    expr: &Expr,
71    context: &mut DynSegmentContext,
72    lookup: &OpLookup,
73    types: &dyn TypeResolver,
74    resolve: &mut F,
75    locals: Option<&[(&str, ValueType)]>,
76) -> Result<()>
77where
78    F: FnMut(&str, &mut DynSegment, SourceSpan) -> anyhow::Result<bool>,
79{
80    let span = expr.span();
81    let error = |message: String| ParseError::new_range(message, span.start, span.end);
82    match expr {
83        Expr::Literal { value, .. } => push_literal(context, value, span)?,
84        Expr::Ident { name, .. } => {
85            if let Some(locals) = locals {
86                if let Some((index, (_, ty))) = locals
87                    .iter()
88                    .enumerate()
89                    .rev()
90                    .find(|(_, (local, _))| *local == name)
91                {
92                    return ty
93                        .push_argument(context, index)
94                        .map_err(|e| error(e.to_string()));
95                }
96            } else if resolve(
97                name,
98                context,
99                SourceSpan::from_proc_macro2_range(span.start, span.end),
100            )
101            .map_err(|e| error(e.to_string()))?
102            {
103                return Ok(());
104            }
105            context.apply_scoped_op(lookup, name, 0, span, false, locals.is_some())?;
106        }
107        Expr::Op { name, operands, .. } => {
108            for operand in operands {
109                emit(operand, context, lookup, types, resolve, locals)?;
110            }
111            context.apply_scoped_op(lookup, name, operands.len(), span, false, locals.is_some())?;
112        }
113        Expr::Apply { callee, args, .. } => {
114            emit(callee, context, lookup, types, resolve, locals)?;
115            for arg in args {
116                emit(arg, context, lookup, types, resolve, locals)?;
117            }
118            context.apply_scoped_op(lookup, "()", args.len() + 1, span, false, locals.is_some())?;
119        }
120        Expr::Member {
121            receiver,
122            name,
123            name_span,
124            args,
125            ..
126        } => {
127            emit(receiver, context, lookup, types, resolve, locals)?;
128            if let Some(args) = args {
129                for arg in args {
130                    emit(arg, context, lookup, types, resolve, locals)?;
131                }
132            }
133            context.apply_scoped_op(
134                lookup,
135                name,
136                1 + args.as_ref().map_or(0, Vec::len),
137                *name_span,
138                true,
139                locals.is_some(),
140            )?;
141        }
142        Expr::Struct {
143            name,
144            name_span,
145            fields,
146            ..
147        } => {
148            let ambient = context.current_stack_offset();
149            for field in fields {
150                emit(&field.value, context, lookup, types, resolve, locals)?;
151            }
152            context.make_record(
153                lookup,
154                name,
155                *name_span,
156                fields
157                    .iter()
158                    .map(|field| (field.name.clone(), field.name_span))
159                    .collect(),
160                ambient,
161                span.start,
162                span.end,
163            )?;
164        }
165        Expr::Tuple { elements, .. } => {
166            let ambient = context.current_stack_offset();
167            for element in elements {
168                emit(element, context, lookup, types, resolve, locals)?;
169            }
170            context.make_tuple(elements.len(), ambient, span.start, span.end)?;
171        }
172        Expr::Array {
173            elements,
174            type_annotation,
175            ..
176        } => {
177            let ambient = context.current_stack_offset();
178            for element in elements {
179                emit(element, context, lookup, types, resolve, locals)?;
180            }
181            let element_type = type_annotation
182                .as_ref()
183                .map(|ty| {
184                    crate::ResolvedArrayType::from_resolved_type(ty.resolve(types)?, ty.span())
185                        .map(|resolved| resolved.into_element_type())
186                })
187                .transpose()?;
188            context.make_resolved_array(elements.len(), ambient, element_type, span)?;
189        }
190        Expr::TupleIndex { base, index, .. } => {
191            emit(base, context, lookup, types, resolve, locals)?;
192            context.tuple_index(*index, span.start, span.end)?;
193        }
194        Expr::If {
195            cond,
196            then_branch,
197            else_branch,
198            ..
199        } => {
200            emit(cond, context, lookup, types, resolve, locals)?;
201            let mut then_context = context.new_fragment();
202            emit(
203                then_branch,
204                &mut then_context,
205                lookup,
206                types,
207                resolve,
208                locals,
209            )?;
210            let else_context = if let Some(branch) = else_branch {
211                let mut fragment = context.new_fragment();
212                emit(branch, &mut fragment, lookup, types, resolve, locals)?;
213                Some(fragment)
214            } else {
215                None
216            };
217            context
218                .join2(then_context, else_context, span.start, span.end)
219                .map_err(|e| error(e.to_string()))?;
220        }
221        Expr::Logical { op, lhs, rhs, .. } => {
222            emit(lhs, context, lookup, types, resolve, locals)?;
223            let mut fragment = context.new_fragment();
224            emit(rhs, &mut fragment, lookup, types, resolve, locals)?;
225            context.apply_logical(
226                match op {
227                    LogicalOp::And => "&&",
228                    LogicalOp::Or => "||",
229                },
230                fragment,
231                span.start,
232                span.end,
233            )?;
234        }
235        Expr::Cast {
236            expr, type_name, ..
237        } => {
238            emit(expr, context, lookup, types, resolve, locals)?;
239            context.apply_cast(lookup, type_name, span.start, span.end)?;
240        }
241        Expr::Closure { params, body, .. } => {
242            if let Some(error) = crate::ast::closure_parameter_diagnostics(params)
243                .into_iter()
244                .next()
245            {
246                return Err(error);
247            }
248            let bindings = params
249                .iter()
250                .map(|param| {
251                    crate::closure_param_type_from_type_expr(&param.type_expr, types)
252                        .map(|ty| (param.name.as_str(), ty))
253                })
254                .collect::<Result<Vec<_>>>()?;
255            let mut body_context = DynSegmentContext::new_context();
256            emit(
257                body,
258                &mut body_context,
259                lookup,
260                types,
261                resolve,
262                Some(&bindings),
263            )?;
264            context.push_closure(
265                bindings.into_iter().map(|(_, ty)| ty).collect(),
266                Vec::new(),
267                body_context,
268                span.start,
269            )?;
270        }
271    }
272    Ok(())
273}
274
275/// Copies a retained literal into fresh segment-owned storage.
276///
277/// # Errors
278/// Returns an error if the canonical unit layout cannot be represented.
279/// - Complexity: O(n) time and space for an n-byte owned literal; O(1) otherwise.
280fn push_literal(context: &mut DynSegmentContext, value: &Literal, span: ExprSpan) -> Result<()> {
281    macro_rules! push {
282        ($($variant:ident),* $(,)?) => {
283            match value {
284                $(Literal::$variant(value) => context.push_literal(value.clone(), span.start),)*
285                Literal::Unit => context.make_tuple(
286                    0, context.current_stack_offset(), span.start, span.end,
287                )?,
288            }
289        };
290    }
291    push!(
292        I8, I16, I32, I64, I128, Isize, U8, U16, U32, U64, U128, Usize, F32, F64, Bool, Char, Str,
293        ByteStr, CStr
294    );
295    Ok(())
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::{AstContext, Parser, builtin_type_resolver};
302    use std::{cell::Cell, rc::Rc};
303
304    /// Resolves formals before external names and keeps compiled output independent of syntax.
305    #[test]
306    fn formals_shadow_external_names_without_retaining_syntax() {
307        let expr = Parser::<AstContext>::new(OpLookup::new())
308            .parse_str_ast("(|value: i32| value)(7)")
309            .unwrap();
310        let calls = Cell::new(0usize);
311        let mut segment = compile_expr(
312            &expr,
313            &OpLookup::new(),
314            builtin_type_resolver().as_ref(),
315            &mut |_, segment, _| {
316                calls.set(calls.get() + 1);
317                segment.just(99i32);
318                Ok(true)
319            },
320        )
321        .unwrap();
322        drop(expr);
323        assert_eq!(segment.call0::<i32>().unwrap(), 7);
324        assert_eq!(calls.get(), 0);
325    }
326
327    /// Compilation failure releases previously emitted owners without executing them.
328    #[test]
329    fn failed_emission_releases_resources() {
330        let expr = Parser::<AstContext>::new(OpLookup::new())
331            .parse_str_ast("owned + missing")
332            .unwrap();
333        let owner = Rc::new(Cell::new(false));
334        let result = compile_expr(
335            &expr,
336            &OpLookup::new(),
337            builtin_type_resolver().as_ref(),
338            &mut |name, segment, _| {
339                if name == "owned" {
340                    let owner = owner.clone();
341                    segment.op0(move || {
342                        owner.set(true);
343                        1i32
344                    });
345                    Ok(true)
346                } else {
347                    anyhow::bail!("external emission failed")
348                }
349            },
350        );
351        assert!(
352            result
353                .err()
354                .unwrap()
355                .message()
356                .contains("external emission failed")
357        );
358        assert_eq!(Rc::strong_count(&owner), 1);
359        assert!(!owner.get());
360    }
361}