Skip to main content

toasty_core/stmt/
input.rs

1use crate::{
2    Schema,
3    schema::app::{Model, ModelId},
4    stmt::{Expr, ExprArg, ExprContext, ExprReference, Project, Projection, Resolve, Type, Value},
5};
6
7/// Provides runtime argument and reference resolution for expression
8/// evaluation and substitution.
9///
10/// During expression evaluation, `Arg` and `Reference` nodes are resolved
11/// by calling methods on an `Input` implementation. The default methods
12/// return `None` (unresolved).
13///
14/// # Examples
15///
16/// ```
17/// use toasty_core::stmt::{ConstInput, Input};
18///
19/// // ConstInput resolves nothing -- suitable for expressions with no
20/// // external arguments.
21/// let mut input = ConstInput::new();
22/// ```
23pub trait Input {
24    /// Resolves an argument expression at the given projection.
25    ///
26    /// Returns `Some(expr)` if the argument can be resolved, or `None`
27    /// if it cannot.
28    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
29        let _ = (expr_arg, projection);
30        None
31    }
32
33    /// Resolves a reference expression at the given projection.
34    ///
35    /// Returns `Some(expr)` if the reference can be resolved, or `None`
36    /// if it cannot.
37    fn resolve_ref(
38        &mut self,
39        expr_reference: &ExprReference,
40        projection: &Projection,
41    ) -> Option<Expr> {
42        let _ = (expr_reference, projection);
43        None
44    }
45
46    /// Resolves the application model with the given ID, for casts whose
47    /// conversion is schema-directed (a `#[document]` embed's record ↔
48    /// object conversions).
49    ///
50    /// Defaults to `None`: inputs without schema access evaluate only
51    /// schema-free casts, and a schema-directed cast reaching one fails
52    /// loudly at evaluation.
53    fn resolve_model(&self, id: ModelId) -> Option<&Model> {
54        let _ = id;
55        None
56    }
57}
58
59/// Adapts an [`Input`]'s model resolution to the [`Resolve`] trait, so
60/// expression evaluation can hand it to schema-directed casts
61/// ([`Type::cast`](crate::stmt::Type::cast)).
62pub(crate) struct InputResolve<'a, I: ?Sized>(pub(crate) &'a I);
63
64impl<I: Input + ?Sized> Resolve for InputResolve<'_, I> {
65    fn model(&self, id: ModelId) -> Option<&Model> {
66        self.0.resolve_model(id)
67    }
68}
69
70/// An [`Input`] implementation that resolves nothing.
71///
72/// Use `ConstInput` when evaluating expressions that contain no external
73/// arguments or references (i.e., constant expressions).
74///
75/// # Examples
76///
77/// ```
78/// use toasty_core::stmt::{ConstInput, Value, Expr};
79///
80/// let expr = Expr::from(Value::from(42_i64));
81/// let result = expr.eval(ConstInput::new()).unwrap();
82/// assert_eq!(result, Value::from(42_i64));
83/// ```
84#[derive(Debug, Default)]
85pub struct ConstInput {}
86
87/// An [`Input`] wrapper that validates resolved argument types against
88/// expected types at resolution time.
89///
90/// `TypedInput` delegates resolution to an inner `Input` and then checks
91/// that the resolved expression can evaluate to a value of the expected
92/// argument type from `tys` (via [`Expr::is_a`]).
93pub struct TypedInput<'a, I, T = Schema> {
94    cx: ExprContext<'a, T>,
95    tys: &'a [Type],
96    input: I,
97}
98
99impl ConstInput {
100    /// Creates a new `ConstInput`.
101    pub fn new() -> ConstInput {
102        ConstInput {}
103    }
104}
105
106impl Input for ConstInput {}
107
108impl<'a, I, T> TypedInput<'a, I, T> {
109    /// Creates a new `TypedInput` with the given expression context,
110    /// expected argument types, and inner input.
111    pub fn new(cx: ExprContext<'a, T>, tys: &'a [Type], input: I) -> Self {
112        TypedInput { cx, tys, input }
113    }
114}
115
116impl<I: Input, T: Resolve> Input for TypedInput<'_, I, T> {
117    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
118        let expr = self.input.resolve_arg(expr_arg, projection)?;
119
120        let mut ty = &self.tys[expr_arg.position];
121
122        for step in projection {
123            ty = match ty {
124                Type::Record(tys) => &tys[step],
125                Type::List(item) => item,
126                _ => todo!("ty={ty:#?}"),
127            };
128        }
129
130        assert!(
131            expr.is_a(self.cx.schema(), ty),
132            "resolved input cannot evaluate to the requested argument type; expected={ty:#?}; actual={expr:#?}"
133        );
134
135        Some(expr)
136    }
137
138    fn resolve_model(&self, id: ModelId) -> Option<&Model> {
139        self.cx.schema().model(id)
140    }
141}
142
143impl Input for &Vec<Value> {
144    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
145        Some(self[expr_arg.position].entry(projection).to_expr())
146    }
147}
148
149impl<T, const N: usize> Input for [T; N]
150where
151    for<'a> &'a T: Project,
152{
153    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
154        (&self[expr_arg.position]).project(projection)
155    }
156}
157
158impl<T, const N: usize> Input for &[T; N]
159where
160    for<'a> &'a T: Project,
161{
162    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
163        (&self[expr_arg.position]).project(projection)
164    }
165}
166
167impl<T> Input for &[T]
168where
169    for<'a> &'a T: Project,
170{
171    fn resolve_arg(&mut self, expr_arg: &ExprArg, projection: &Projection) -> Option<Expr> {
172        (&self[expr_arg.position]).project(projection)
173    }
174}