Skip to main content

openjd_expr/
default_library.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5//! Default function library with all built-in signatures.
6//!
7//! Registers signatures for all operators, functions, and properties defined
8//! in the expression language spec. Implementations are placeholders — the
9//! evaluator's hardcoded match arms handle actual evaluation. The library
10//! is used for static type checking via `derive_return_type`.
11
12use crate::function_library::FunctionLibrary;
13use crate::profile::{ExprProfile, ExprRevision, HostContext, HostKind, ProfileKey};
14use std::collections::HashMap;
15use std::sync::{Arc, LazyLock, Mutex};
16
17pub(crate) static DEFAULT_LIBRARY: LazyLock<FunctionLibrary> = LazyLock::new(build_default_library);
18
19/// Cache of per-profile libraries, keyed by the rules-independent portion
20/// of an [`ExprProfile`].
21///
22/// Keeping the cache keyed on [`ProfileKey`] rather than on the full
23/// profile means that callers can construct many libraries sharing the
24/// same (revision, extensions, host kind) but different path-mapping
25/// rules without thrashing the cache — the cached skeleton is cloned
26/// once and the host-context registrations are applied on top.
27static PROFILE_CACHE: LazyLock<Mutex<HashMap<ProfileKey, Arc<FunctionLibrary>>>> =
28    LazyLock::new(|| Mutex::new(HashMap::new()));
29
30/// Build a library skeleton (no host context) for a given profile.
31///
32/// The skeleton is what goes into the profile cache. Host-context
33/// functions are added on top, in `for_profile`, either as stubs
34/// (`HostContext::Unresolved`) or as closures capturing rules
35/// (`HostContext::WithRules`).
36///
37/// Revision selects the base library; the subsequent extension loop
38/// merges in any expression-level extensions enabled on the profile.
39/// Both the revision match and the per-extension match are
40/// intentionally exhaustive-without-wildcard — adding a new
41/// `ExprRevision` variant *or* a new `ExprExtension` variant produces
42/// a compile error here, forcing an explicit decision about how the
43/// new variant affects the library.
44fn build_library_skeleton(profile: &ExprProfile) -> FunctionLibrary {
45    let lib = match profile.revision() {
46        ExprRevision::V2026_02 => {
47            // Base library for 2026-02. Future revisions can diverge
48            // here.
49            build_default_library()
50        } // Intentionally no wildcard: this match lives in the same
51          // crate as `ExprRevision`, so adding a new revision variant
52          // will produce a compile error here.
53    };
54
55    // Merge in expression-level extensions. `ExprExtension` has no
56    // variants today, so this loop body is unreachable — but the
57    // exhaustive match on `*ext` below is the forcing function that
58    // makes adding the first variant produce a compile error at this
59    // site, rather than silently inheriting the base library.
60    //
61    // `#[allow(clippy::never_loop)]` is required because the empty
62    // match on an uninhabited-today enum diverges, so clippy's
63    // `never_loop` lint flags the loop. That's exactly the property
64    // we want preserved: when `ExprExtension` gains its first variant,
65    // the empty match becomes a compile error and the lint stops
66    // firing in one step.
67    #[allow(clippy::never_loop)]
68    for ext in profile.extensions() {
69        match *ext {} // Intentionally empty — every future variant
70                      // must add an arm describing how it modifies
71                      // the library.
72    }
73
74    lib
75}
76
77impl FunctionLibrary {
78    /// Construct or retrieve a cached function library matching the
79    /// given expression profile.
80    ///
81    /// Libraries are cached keyed on the profile's
82    /// (revision, extensions, host-kind) triple. Profiles that differ
83    /// only in the specific `Arc<Vec<PathMappingRule>>` they carry share
84    /// a cached skeleton; the rules are applied as a cheap clone-and-
85    /// register on top for each call.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use openjd_expr::{ExprProfile, FunctionLibrary, HostContext};
91    ///
92    /// // Default library for template validation — host functions available
93    /// // as unresolved type-check stubs.
94    /// let profile = ExprProfile::current().with_host_context(HostContext::Unresolved);
95    /// let lib = FunctionLibrary::for_profile(&profile);
96    /// assert!(lib.host_context_enabled);
97    ///
98    /// // Runtime library with real path mapping rules.
99    /// let profile = ExprProfile::current()
100    ///     .with_host_context(HostContext::with_rules(Vec::new()));
101    /// let lib = FunctionLibrary::for_profile(&profile);
102    /// assert!(lib.host_context_enabled);
103    /// ```
104    pub fn for_profile(profile: &ExprProfile) -> Arc<FunctionLibrary> {
105        // Fast path: look up the skeleton (no-host or unresolved-host)
106        // in the cache. Insert if missing.
107        let key = profile.cache_key();
108        let cached = {
109            let mut cache = PROFILE_CACHE.lock().expect("profile cache mutex poisoned");
110            Arc::clone(cache.entry(key.clone()).or_insert_with(|| {
111                // Build a skeleton *without* any host-context
112                // registrations. For the Unresolved bucket we then
113                // register the stub inline; for WithRules we defer to
114                // the caller below.
115                let mut skeleton = build_library_skeleton(profile);
116                if matches!(key.host_kind, HostKind::Unresolved) {
117                    skeleton.host_context_enabled = true;
118                    register_unresolved_host_context_functions(&mut skeleton);
119                }
120                Arc::new(skeleton)
121            }))
122        };
123
124        // Slow path: WithRules always needs the rules applied on top of
125        // a no-host skeleton. We cache the *no-host* skeleton, not the
126        // WithRules variant, because the rules are per-call.
127        if let HostContext::WithRules(rules) = profile.host_context() {
128            // Pull the no-host skeleton (a separate cache entry, since
129            // the key for this call is `WithRules` but we want the
130            // `None` skeleton underneath). If it's absent, build it.
131            let base_key = ProfileKey {
132                host_kind: HostKind::None,
133                ..key
134            };
135            let base = {
136                let mut cache = PROFILE_CACHE.lock().expect("profile cache mutex poisoned");
137                Arc::clone(
138                    cache
139                        .entry(base_key)
140                        .or_insert_with(|| Arc::new(build_library_skeleton(profile))),
141                )
142            };
143            // Clone the base library and register host context on the clone.
144            // The clone is cheap — `FunctionLibrary` holds `Arc<dyn Fn>`
145            // entries in its `HashMap`.
146            let mut derived = (*base).clone();
147            derived.host_context_enabled = true;
148            register_host_context_functions(&mut derived, Arc::clone(rules));
149            return Arc::new(derived);
150        }
151
152        cached
153    }
154}
155
156/// Build the default library (called once by LazyLock).
157fn build_default_library() -> FunctionLibrary {
158    let lib = FunctionLibrary::new();
159    lib.merge(arithmetic())
160        .merge(string_ops())
161        .merge(list_ops())
162        .merge(comparison())
163        .merge(math_ops())
164        .merge(string_functions())
165        .merge(list_functions())
166        .merge(conversion())
167        .merge(path_ops())
168        .merge(repr_ops())
169        .merge(regex_ops())
170        .merge(misc())
171}
172
173fn arithmetic() -> FunctionLibrary {
174    use crate::functions::arithmetic::*;
175    let mut lib = FunctionLibrary::new();
176    // int arithmetic
177    lib.register_sig("__add__", "(int, int) -> int", add_int)
178        .expect("bad builtin signature");
179    lib.register_sig("__sub__", "(int, int) -> int", sub_int)
180        .expect("bad builtin signature");
181    lib.register_sig("__mul__", "(int, int) -> int", mul_int)
182        .expect("bad builtin signature");
183    lib.register_sig("__truediv__", "(int, int) -> float", truediv_int)
184        .expect("bad builtin signature");
185    lib.register_sig("__floordiv__", "(int, int) -> int", floordiv_int)
186        .expect("bad builtin signature");
187    lib.register_sig("__mod__", "(int, int) -> int", mod_int)
188        .expect("bad builtin signature");
189    lib.register_sig("__pow__", "(int, int) -> float | int", pow_int)
190        .expect("bad builtin signature");
191    lib.register_sig("__neg__", "(int) -> int", neg_int)
192        .expect("bad builtin signature");
193    lib.register_sig("__pos__", "(int) -> int", pos_int)
194        .expect("bad builtin signature");
195    // float arithmetic
196    lib.register_sig("__add__", "(float, float) -> float", add_float)
197        .expect("bad builtin signature");
198    lib.register_sig("__sub__", "(float, float) -> float", sub_float)
199        .expect("bad builtin signature");
200    lib.register_sig("__mul__", "(float, float) -> float", mul_float)
201        .expect("bad builtin signature");
202    lib.register_sig("__truediv__", "(float, float) -> float", truediv_float)
203        .expect("bad builtin signature");
204    lib.register_sig("__floordiv__", "(float, float) -> int", floordiv_float)
205        .expect("bad builtin signature");
206    lib.register_sig("__mod__", "(float, float) -> float", mod_float)
207        .expect("bad builtin signature");
208    lib.register_sig("__pow__", "(float, float) -> float", pow_float)
209        .expect("bad builtin signature");
210    lib.register_sig("__neg__", "(float) -> float", neg_float)
211        .expect("bad builtin signature");
212    lib.register_sig("__pos__", "(float) -> float", pos_float)
213        .expect("bad builtin signature");
214    lib
215}
216
217fn string_ops() -> FunctionLibrary {
218    use crate::functions::arithmetic::*;
219    let mut lib = FunctionLibrary::new();
220    lib.register_sig("__add__", "(string, string) -> string", add_string)
221        .expect("bad builtin signature");
222    lib.register_sig(
223        "__add__",
224        "(string, range_expr) -> string",
225        add_string_range,
226    )
227    .expect("bad builtin signature");
228    lib.register_sig(
229        "__add__",
230        "(range_expr, string) -> string",
231        add_range_string,
232    )
233    .expect("bad builtin signature");
234    lib.register_sig("__mul__", "(string, int) -> string", mul_string)
235        .expect("bad builtin signature");
236    lib
237}
238
239fn list_ops() -> FunctionLibrary {
240    use crate::functions::arithmetic::*;
241    let mut lib = FunctionLibrary::new();
242    lib.register_sig("__add__", "(list[T1], list[T2]) -> list[T3]", add_list_list)
243        .expect("bad builtin signature");
244    lib.register_sig(
245        "__add__",
246        "(range_expr, list[T1]) -> list[T2]",
247        add_range_list,
248    )
249    .expect("bad builtin signature");
250    lib.register_sig(
251        "__add__",
252        "(list[T1], range_expr) -> list[T2]",
253        add_list_range,
254    )
255    .expect("bad builtin signature");
256    lib.register_sig(
257        "__add__",
258        "(range_expr, range_expr) -> list[int]",
259        add_range_range,
260    )
261    .expect("bad builtin signature");
262    lib.register_sig("__mul__", "(list[T1], int) -> list[T1]", mul_list)
263        .expect("bad builtin signature");
264    lib.register_sig(
265        "__getitem__",
266        "(list[T1], int) -> T1",
267        crate::functions::misc::getitem_list,
268    )
269    .expect("bad builtin signature");
270    lib.register_sig(
271        "__getitem__",
272        "(string, int) -> string",
273        crate::functions::misc::getitem_string,
274    )
275    .expect("bad builtin signature");
276    lib.register_sig(
277        "__getitem__",
278        "(range_expr, int) -> int",
279        crate::functions::misc::getitem_range,
280    )
281    .expect("bad builtin signature");
282    lib
283}
284
285fn comparison() -> FunctionLibrary {
286    use crate::functions::arithmetic::not_bool;
287    use crate::functions::comparison::*;
288    let mut lib = FunctionLibrary::new();
289    lib.register_sig("__not__", "(bool) -> bool", not_bool)
290        .expect("bad builtin signature");
291    // Equality / ordering — generic (T1, T2) -> bool
292    lib.register_sig("__eq__", "(T1, T2) -> bool", eq_generic)
293        .expect("bad builtin signature");
294    lib.register_sig("__ne__", "(T1, T2) -> bool", ne_generic)
295        .expect("bad builtin signature");
296    lib.register_sig("__lt__", "(T1, T2) -> bool", lt_generic)
297        .expect("bad builtin signature");
298    lib.register_sig("__le__", "(T1, T2) -> bool", le_generic)
299        .expect("bad builtin signature");
300    lib.register_sig("__gt__", "(T1, T2) -> bool", gt_generic)
301        .expect("bad builtin signature");
302    lib.register_sig("__ge__", "(T1, T2) -> bool", ge_generic)
303        .expect("bad builtin signature");
304    // Containment — container first, item second
305    lib.register_sig("__contains__", "(list[T1], T1) -> bool", contains_list)
306        .expect("bad builtin signature");
307    lib.register_sig("__contains__", "(range_expr, int) -> bool", contains_range)
308        .expect("bad builtin signature");
309    lib.register_sig("__contains__", "(string, string) -> bool", contains_string)
310        .expect("bad builtin signature");
311    lib.register_sig(
312        "__not_contains__",
313        "(list[T1], T1) -> bool",
314        not_contains_list,
315    )
316    .expect("bad builtin signature");
317    lib.register_sig(
318        "__not_contains__",
319        "(range_expr, int) -> bool",
320        not_contains_range,
321    )
322    .expect("bad builtin signature");
323    lib.register_sig(
324        "__not_contains__",
325        "(string, string) -> bool",
326        not_contains_string,
327    )
328    .expect("bad builtin signature");
329    // Slice — 4-arg __getitem__ overloads
330    lib.register_sig(
331        "__getitem__",
332        "(list[T1], int | nulltype, int | nulltype, int | nulltype) -> list[T1]",
333        slice_list,
334    )
335    .expect("bad builtin signature");
336    lib.register_sig(
337        "__getitem__",
338        "(range_expr, int | nulltype, int | nulltype, int | nulltype) -> range_expr | list[int]",
339        slice_range,
340    )
341    .expect("bad builtin signature");
342    lib.register_sig(
343        "__getitem__",
344        "(string, int | nulltype, int | nulltype, int | nulltype) -> string",
345        slice_string,
346    )
347    .expect("bad builtin signature");
348    lib
349}
350
351fn math_ops() -> FunctionLibrary {
352    use crate::functions::math::*;
353    let mut lib = FunctionLibrary::new();
354    lib.register_sig("min", "(int, int) -> int", min_fn)
355        .expect("bad builtin signature");
356    lib.register_sig("min", "(float, float) -> float", min_fn)
357        .expect("bad builtin signature");
358    lib.register_sig("min", "(int, int, int) -> int", min_fn)
359        .expect("bad builtin signature");
360    lib.register_sig("min", "(float, float, float) -> float", min_fn)
361        .expect("bad builtin signature");
362    lib.register_sig("min", "(list[int]) -> int", min_fn)
363        .expect("bad builtin signature");
364    lib.register_sig("min", "(list[float]) -> float", min_fn)
365        .expect("bad builtin signature");
366    lib.register_sig("min", "(range_expr) -> int", min_fn)
367        .expect("bad builtin signature");
368    lib.register_sig("min", "(list[nulltype]) -> noreturn", min_fn)
369        .expect("bad builtin signature");
370    lib.register_sig("max", "(int, int) -> int", max_fn)
371        .expect("bad builtin signature");
372    lib.register_sig("max", "(float, float) -> float", max_fn)
373        .expect("bad builtin signature");
374    lib.register_sig("max", "(int, int, int) -> int", max_fn)
375        .expect("bad builtin signature");
376    lib.register_sig("max", "(float, float, float) -> float", max_fn)
377        .expect("bad builtin signature");
378    lib.register_sig("max", "(list[int]) -> int", max_fn)
379        .expect("bad builtin signature");
380    lib.register_sig("max", "(list[float]) -> float", max_fn)
381        .expect("bad builtin signature");
382    lib.register_sig("max", "(range_expr) -> int", max_fn)
383        .expect("bad builtin signature");
384    lib.register_sig("max", "(list[nulltype]) -> noreturn", max_fn)
385        .expect("bad builtin signature");
386    lib.register_sig("floor", "(int) -> int", floor_int)
387        .expect("bad builtin signature");
388    lib.register_sig("floor", "(float) -> int", floor_float)
389        .expect("bad builtin signature");
390    lib.register_sig("ceil", "(int) -> int", ceil_int)
391        .expect("bad builtin signature");
392    lib.register_sig("ceil", "(float) -> int", ceil_float)
393        .expect("bad builtin signature");
394    lib.register_sig("round", "(float) -> int", round_fn)
395        .expect("bad builtin signature");
396    lib.register_sig("round", "(float, int) -> float | int", round_fn)
397        .expect("bad builtin signature");
398    lib.register_sig("round", "(int, int) -> int", round_fn)
399        .expect("bad builtin signature");
400    lib.register_sig("sum", "(list[int]) -> int", sum_list)
401        .expect("bad builtin signature");
402    lib.register_sig("sum", "(list[float]) -> float", sum_list)
403        .expect("bad builtin signature");
404    lib.register_sig("sum", "(list[nulltype]) -> int", sum_list)
405        .expect("bad builtin signature");
406    lib.register_sig("sum", "(range_expr) -> int", sum_list)
407        .expect("bad builtin signature");
408    lib
409}
410
411fn string_functions() -> FunctionLibrary {
412    use crate::functions::string::*;
413    let mut lib = FunctionLibrary::new();
414    lib.register_sig("upper", "(string) -> string", upper_fn)
415        .expect("bad builtin signature");
416    lib.register_sig("lower", "(string) -> string", lower_fn)
417        .expect("bad builtin signature");
418    lib.register_sig("strip", "(string) -> string", strip_fn)
419        .expect("bad builtin signature");
420    lib.register_sig("strip", "(string, string) -> string", strip_fn)
421        .expect("bad builtin signature");
422    lib.register_sig("lstrip", "(string) -> string", lstrip_fn)
423        .expect("bad builtin signature");
424    lib.register_sig("lstrip", "(string, string) -> string", lstrip_fn)
425        .expect("bad builtin signature");
426    lib.register_sig("rstrip", "(string) -> string", rstrip_fn)
427        .expect("bad builtin signature");
428    lib.register_sig("rstrip", "(string, string) -> string", rstrip_fn)
429        .expect("bad builtin signature");
430    lib.register_sig("startswith", "(string, string) -> bool", startswith_fn)
431        .expect("bad builtin signature");
432    lib.register_sig("endswith", "(string, string) -> bool", endswith_fn)
433        .expect("bad builtin signature");
434    lib.register_sig("replace", "(string, string, string) -> string", replace_fn)
435        .expect("bad builtin signature");
436    lib.register_sig("split", "(string) -> list[string]", split_fn)
437        .expect("bad builtin signature");
438    lib.register_sig("split", "(string, string) -> list[string]", split_fn)
439        .expect("bad builtin signature");
440    lib.register_sig("split", "(string, string, int) -> list[string]", split_fn)
441        .expect("bad builtin signature");
442    lib.register_sig("rsplit", "(string) -> list[string]", rsplit_fn)
443        .expect("bad builtin signature");
444    lib.register_sig("rsplit", "(string, string) -> list[string]", rsplit_fn)
445        .expect("bad builtin signature");
446    lib.register_sig("rsplit", "(string, string, int) -> list[string]", rsplit_fn)
447        .expect("bad builtin signature");
448    lib.register_sig("find", "(string, string) -> int", find_fn)
449        .expect("bad builtin signature");
450    lib.register_sig("rfind", "(string, string) -> int", rfind_fn)
451        .expect("bad builtin signature");
452    lib.register_sig("index", "(string, string) -> int", index_fn)
453        .expect("bad builtin signature");
454    lib.register_sig("rindex", "(string, string) -> int", rindex_fn)
455        .expect("bad builtin signature");
456    lib.register_sig("count", "(string, string) -> int", count_fn)
457        .expect("bad builtin signature");
458    lib.register_sig(
459        "removeprefix",
460        "(string, string) -> string",
461        removeprefix_fn,
462    )
463    .expect("bad builtin signature");
464    lib.register_sig(
465        "removesuffix",
466        "(string, string) -> string",
467        removesuffix_fn,
468    )
469    .expect("bad builtin signature");
470    lib.register_sig("isdigit", "(string) -> bool", isdigit_fn)
471        .expect("bad builtin signature");
472    lib.register_sig("isalpha", "(string) -> bool", isalpha_fn)
473        .expect("bad builtin signature");
474    lib.register_sig("isalnum", "(string) -> bool", isalnum_fn)
475        .expect("bad builtin signature");
476    lib.register_sig("isspace", "(string) -> bool", isspace_fn)
477        .expect("bad builtin signature");
478    lib.register_sig("isupper", "(string) -> bool", isupper_fn)
479        .expect("bad builtin signature");
480    lib.register_sig("islower", "(string) -> bool", islower_fn)
481        .expect("bad builtin signature");
482    lib.register_sig("isascii", "(string) -> bool", isascii_fn)
483        .expect("bad builtin signature");
484    lib.register_sig("title", "(string) -> string", title_fn)
485        .expect("bad builtin signature");
486    lib.register_sig("capitalize", "(string) -> string", capitalize_fn)
487        .expect("bad builtin signature");
488    lib.register_sig("center", "(string, int) -> string", center_fn)
489        .expect("bad builtin signature");
490    lib.register_sig("ljust", "(string, int) -> string", ljust_fn)
491        .expect("bad builtin signature");
492    lib.register_sig("rjust", "(string, int) -> string", rjust_fn)
493        .expect("bad builtin signature");
494    lib
495}
496
497fn list_functions() -> FunctionLibrary {
498    use crate::functions::list::*;
499    let mut lib = FunctionLibrary::new();
500    lib.register_sig("sorted", "(list[T1]) -> list[T1]", sorted_fn)
501        .expect("bad builtin signature");
502    lib.register_sig("reversed", "(list[T1]) -> list[T1]", reversed_fn)
503        .expect("bad builtin signature");
504    lib.register_sig("unique", "(list[T1]) -> list[T1]", unique_fn)
505        .expect("bad builtin signature");
506    lib.register_sig("flatten", "(list[list[T1]]) -> list[T1]", flatten_fn)
507        .expect("bad builtin signature");
508    lib.register_sig("flatten", "(list[T1]) -> list[T1]", flatten_fn)
509        .expect("bad builtin signature");
510    lib.register_sig("flatten", "(list[nulltype]) -> list[nulltype]", flatten_fn)
511        .expect("bad builtin signature");
512    lib.register_sig("join", "(list[string], string) -> string", join_fn)
513        .expect("bad builtin signature");
514    lib.register_sig("join", "(list[path], string) -> string", join_fn)
515        .expect("bad builtin signature");
516    lib.register_sig("join", "(list[nulltype], string) -> string", join_fn)
517        .expect("bad builtin signature");
518    lib.register_sig("range", "(int) -> list[int]", range_fn)
519        .expect("bad builtin signature");
520    lib.register_sig("range", "(int, int) -> list[int]", range_fn)
521        .expect("bad builtin signature");
522    lib.register_sig("range", "(int, int, int) -> list[int]", range_fn)
523        .expect("bad builtin signature");
524    lib
525}
526
527fn conversion() -> FunctionLibrary {
528    use crate::functions::conversion::*;
529    let mut lib = FunctionLibrary::new();
530    lib.register_sig("int", "(int) -> int", int_from_int)
531        .expect("bad builtin signature");
532    lib.register_sig("int", "(float) -> int", int_from_float)
533        .expect("bad builtin signature");
534    lib.register_sig("int", "(string) -> int", int_from_string)
535        .expect("bad builtin signature");
536    lib.register_sig("float", "(float) -> float", float_from_float)
537        .expect("bad builtin signature");
538    lib.register_sig("float", "(int) -> float", float_from_int)
539        .expect("bad builtin signature");
540    lib.register_sig("float", "(string) -> float", float_from_string)
541        .expect("bad builtin signature");
542    lib.register_sig("string", "(int) -> string", string_fn)
543        .expect("bad builtin signature");
544    lib.register_sig("string", "(float) -> string", string_fn)
545        .expect("bad builtin signature");
546    lib.register_sig("string", "(bool) -> string", string_fn)
547        .expect("bad builtin signature");
548    lib.register_sig("string", "(string) -> string", string_fn)
549        .expect("bad builtin signature");
550    lib.register_sig("string", "(path) -> string", string_fn)
551        .expect("bad builtin signature");
552    lib.register_sig("string", "(nulltype) -> string", string_fn)
553        .expect("bad builtin signature");
554    lib.register_sig("string", "(list[T1]) -> string", string_fn)
555        .expect("bad builtin signature");
556    lib.register_sig("string", "(range_expr) -> string", string_fn)
557        .expect("bad builtin signature");
558    lib.register_sig("bool", "(bool) -> bool", bool_from_bool)
559        .expect("bad builtin signature");
560    lib.register_sig("bool", "(int) -> bool", bool_from_int)
561        .expect("bad builtin signature");
562    lib.register_sig("bool", "(float) -> bool", bool_from_float)
563        .expect("bad builtin signature");
564    lib.register_sig("bool", "(string) -> bool", bool_from_string)
565        .expect("bad builtin signature");
566    lib.register_sig("bool", "(nulltype) -> bool", bool_from_null)
567        .expect("bad builtin signature");
568    lib.register_sig("bool", "(path) -> noreturn", bool_from_path)
569        .expect("bad builtin signature");
570    lib.register_sig("bool", "(list[T]) -> noreturn", bool_from_list)
571        .expect("bad builtin signature");
572    lib.register_sig(
573        "list",
574        "(range_expr) -> list[int]",
575        crate::functions::list::list_from_range,
576    )
577    .expect("bad builtin signature");
578    lib.register_sig(
579        "range_expr",
580        "(string) -> range_expr",
581        crate::functions::list::range_expr_from_string,
582    )
583    .expect("bad builtin signature");
584    lib.register_sig(
585        "range_expr",
586        "(list[int]) -> range_expr",
587        crate::functions::list::range_expr_from_list,
588    )
589    .expect("bad builtin signature");
590    lib.register_sig(
591        "range_expr",
592        "(list[nulltype]) -> noreturn",
593        crate::functions::list::range_expr_from_empty_list,
594    )
595    .expect("bad builtin signature");
596    lib
597}
598
599fn path_ops() -> FunctionLibrary {
600    use crate::functions::arithmetic::*;
601    use crate::functions::path::*;
602    let mut lib = FunctionLibrary::new();
603    lib.register_sig("path", "(string) -> path", crate::functions::misc::path_fn)
604        .expect("bad builtin signature");
605    lib.register_sig(
606        "path",
607        "(list[string]) -> path",
608        crate::functions::misc::path_fn,
609    )
610    .expect("bad builtin signature");
611    lib.register_sig("__truediv__", "(path, string) -> path", path_div)
612        .expect("bad builtin signature");
613    lib.register_sig("__truediv__", "(path, path) -> path", path_div)
614        .expect("bad builtin signature");
615    lib.register_sig("__add__", "(path, string) -> path", add_path_string)
616        .expect("bad builtin signature");
617    lib.register_sig("as_posix", "(path) -> string", as_posix_fn)
618        .expect("bad builtin signature");
619    lib.register_sig("with_name", "(path, string) -> path", with_name_fn)
620        .expect("bad builtin signature");
621    lib.register_sig("with_stem", "(path, string) -> path", with_stem_fn)
622        .expect("bad builtin signature");
623    lib.register_sig("with_suffix", "(path, string) -> path", with_suffix_fn)
624        .expect("bad builtin signature");
625    lib.register_sig("with_number", "(path, int) -> path", with_number_fn)
626        .expect("bad builtin signature");
627    lib.register_sig("with_number", "(string, int) -> string", with_number_fn)
628        .expect("bad builtin signature");
629    lib.register_sig("is_absolute", "(path) -> bool", is_absolute_fn)
630        .expect("bad builtin signature");
631    lib.register_sig("is_relative_to", "(path, path) -> bool", is_relative_to_fn)
632        .expect("bad builtin signature");
633    lib.register_sig(
634        "is_relative_to",
635        "(path, string) -> bool",
636        is_relative_to_fn,
637    )
638    .expect("bad builtin signature");
639    lib.register_sig("relative_to", "(path, path) -> path", relative_to_fn)
640        .expect("bad builtin signature");
641    lib.register_sig("relative_to", "(path, string) -> path", relative_to_fn)
642        .expect("bad builtin signature");
643    // apply_path_mapping is host-context only — registered on a library
644    // built via `FunctionLibrary::for_profile` with a host context of
645    // `HostContext::WithRules` or `HostContext::Unresolved`.
646    // Properties (handled by eval_attribute, registered for type checking)
647    lib.register_sig("__property_name__", "(path) -> string", prop_name)
648        .expect("bad builtin signature");
649    lib.register_sig("__property_stem__", "(path) -> string", prop_stem)
650        .expect("bad builtin signature");
651    lib.register_sig("__property_suffix__", "(path) -> string", prop_suffix)
652        .expect("bad builtin signature");
653    lib.register_sig(
654        "__property_suffixes__",
655        "(path) -> list[string]",
656        prop_suffixes,
657    )
658    .expect("bad builtin signature");
659    lib.register_sig("__property_parent__", "(path) -> path", prop_parent)
660        .expect("bad builtin signature");
661    lib.register_sig("__property_parts__", "(path) -> list[string]", prop_parts)
662        .expect("bad builtin signature");
663    lib
664}
665
666fn repr_ops() -> FunctionLibrary {
667    use crate::functions::repr::*;
668    let mut lib = FunctionLibrary::new();
669    for f in [
670        (
671            "repr_py",
672            repr_py_fn
673                as fn(
674                    &mut dyn crate::function_library::EvalContext,
675                    &[crate::value::ExprValue],
676                )
677                    -> Result<crate::value::ExprValue, crate::error::ExpressionError>,
678        ),
679        ("repr_json", repr_json_fn),
680        ("repr_sh", repr_sh_fn),
681        ("repr_cmd", repr_cmd_fn),
682        ("repr_pwsh", repr_pwsh_fn),
683    ] {
684        lib.register_sig(f.0, "(int) -> string", f.1)
685            .expect("bad builtin signature");
686        lib.register_sig(f.0, "(float) -> string", f.1)
687            .expect("bad builtin signature");
688        lib.register_sig(f.0, "(string) -> string", f.1)
689            .expect("bad builtin signature");
690        lib.register_sig(f.0, "(bool) -> string", f.1)
691            .expect("bad builtin signature");
692        lib.register_sig(f.0, "(path) -> string", f.1)
693            .expect("bad builtin signature");
694        lib.register_sig(f.0, "(nulltype) -> string", f.1)
695            .expect("bad builtin signature");
696        lib.register_sig(f.0, "(list[T1]) -> string", f.1)
697            .expect("bad builtin signature");
698        lib.register_sig(f.0, "(range_expr) -> string", f.1)
699            .expect("bad builtin signature");
700    }
701    lib
702}
703
704fn regex_ops() -> FunctionLibrary {
705    use crate::functions::regex::*;
706    let mut lib = FunctionLibrary::new();
707    lib.register_sig("re_match", "(string, string) -> list[string]?", re_match_fn)
708        .expect("bad builtin signature");
709    lib.register_sig(
710        "re_search",
711        "(string, string) -> list[string]?",
712        re_search_fn,
713    )
714    .expect("bad builtin signature");
715    lib.register_sig(
716        "re_findall",
717        "(string, string) -> list[string]",
718        re_findall_fn,
719    )
720    .expect("bad builtin signature");
721    lib.register_sig(
722        "re_findall",
723        "(string, string) -> list[list[string]]",
724        re_findall_fn,
725    )
726    .expect("bad builtin signature");
727    lib.register_sig(
728        "re_sub",
729        "(string, string, string) -> string",
730        re_replace_fn,
731    )
732    .expect("bad builtin signature");
733    lib.register_sig("re_split", "(string, string) -> list[string]", re_split_fn)
734        .expect("bad builtin signature");
735    lib.register_sig(
736        "re_split",
737        "(string, string, int) -> list[string]",
738        re_split_fn,
739    )
740    .expect("bad builtin signature");
741    lib.register_sig("re_escape", "(string) -> string", re_escape_fn)
742        .expect("bad builtin signature");
743    lib
744}
745
746/// Register host-context-only functions (e.g. `apply_path_mapping`).
747///
748/// `rules` are captured by the registered closure and applied on every
749/// `apply_path_mapping` call during evaluation.
750pub fn register_host_context_functions(
751    lib: &mut FunctionLibrary,
752    rules: std::sync::Arc<Vec<crate::path_mapping::PathMappingRule>>,
753) {
754    lib.register_sig(
755        "apply_path_mapping",
756        "(string) -> path",
757        crate::functions::path::make_apply_path_mapping_fn(rules),
758    )
759    .expect("bad builtin signature");
760}
761
762pub fn register_unresolved_host_context_functions(lib: &mut FunctionLibrary) {
763    fn unresolved_apply_path_mapping(
764        _ctx: &mut dyn crate::function_library::EvalContext,
765        _a: &[crate::ExprValue],
766    ) -> Result<crate::ExprValue, crate::ExpressionError> {
767        Ok(crate::ExprValue::Unresolved(crate::ExprType::PATH))
768    }
769    lib.register_sig(
770        "apply_path_mapping",
771        "(string) -> path",
772        unresolved_apply_path_mapping,
773    )
774    .expect("bad builtin signature");
775}
776
777fn misc() -> FunctionLibrary {
778    use crate::functions::misc::*;
779    let mut lib = FunctionLibrary::new();
780    lib.register_sig("fail", "(string) -> noreturn", fail_fn)
781        .expect("bad builtin signature");
782    lib.register_sig("zfill", "(string, int) -> string", zfill_fn)
783        .expect("bad builtin signature");
784    lib.register_sig("zfill", "(int, int) -> string", zfill_fn)
785        .expect("bad builtin signature");
786    lib.register_sig("zfill", "(float, int) -> string", zfill_fn)
787        .expect("bad builtin signature");
788    lib.register_sig("any", "(list[bool]) -> bool", any_fn)
789        .expect("bad builtin signature");
790    lib.register_sig("any", "(list[nulltype]) -> bool", any_fn)
791        .expect("bad builtin signature");
792    lib.register_sig("all", "(list[bool]) -> bool", all_fn)
793        .expect("bad builtin signature");
794    lib.register_sig("all", "(list[nulltype]) -> bool", all_fn)
795        .expect("bad builtin signature");
796    lib.register_sig("abs", "(int) -> int", abs_int)
797        .expect("bad builtin signature");
798    lib.register_sig("abs", "(float) -> float", abs_float)
799        .expect("bad builtin signature");
800    lib.register_sig("len", "(string) -> int", len_string)
801        .expect("bad builtin signature");
802    lib.register_sig("len", "(path) -> int", len_path)
803        .expect("bad builtin signature");
804    lib.register_sig("len", "(list[T1]) -> int", len_list)
805        .expect("bad builtin signature");
806    lib.register_sig("len", "(range_expr) -> int", len_range)
807        .expect("bad builtin signature");
808    lib
809}
810
811#[cfg(test)]
812mod tests {
813    // Tests in this module exercise `DEFAULT_LIBRARY` directly because
814    // they inspect the contents of the default skeleton; external callers
815    // go through `FunctionLibrary::for_profile(&ExprProfile::current())`.
816    use super::*;
817    use crate::types::ExprType;
818
819    #[test]
820    fn default_library_has_all_categories() {
821        let lib = &*DEFAULT_LIBRARY;
822        // Spot check each category
823        assert!(!lib.get_signatures("__add__").is_empty(), "arithmetic");
824        assert!(!lib.get_signatures("upper").is_empty(), "string functions");
825        assert!(!lib.get_signatures("sorted").is_empty(), "list functions");
826        assert!(!lib.get_signatures("__not__").is_empty(), "not operator");
827        assert!(!lib.get_signatures("abs").is_empty(), "math");
828        assert!(!lib.get_signatures("int").is_empty(), "conversion");
829        assert!(!lib.get_signatures("path").is_empty(), "path");
830        assert!(!lib.get_signatures("repr_py").is_empty(), "repr");
831        assert!(!lib.get_signatures("re_match").is_empty(), "regex");
832        assert!(!lib.get_signatures("fail").is_empty(), "misc");
833    }
834
835    #[test]
836    fn derive_return_type_add_int() {
837        let lib = &*DEFAULT_LIBRARY;
838        assert_eq!(
839            lib.derive_return_type("__add__", &[ExprType::INT, ExprType::INT]),
840            Some(ExprType::INT)
841        );
842    }
843
844    #[test]
845    fn derive_return_type_add_float_coercion() {
846        let lib = &*DEFAULT_LIBRARY;
847        assert_eq!(
848            lib.derive_return_type("__add__", &[ExprType::INT, ExprType::FLOAT]),
849            Some(ExprType::FLOAT)
850        );
851    }
852
853    #[test]
854    fn derive_return_type_getitem_generic() {
855        let lib = &*DEFAULT_LIBRARY;
856        assert_eq!(
857            lib.derive_return_type(
858                "__getitem__",
859                &[ExprType::list(ExprType::STRING), ExprType::INT]
860            ),
861            Some(ExprType::STRING)
862        );
863    }
864
865    #[test]
866    fn derive_return_type_sorted_generic() {
867        let lib = &*DEFAULT_LIBRARY;
868        assert_eq!(
869            lib.derive_return_type("sorted", &[ExprType::list(ExprType::INT)]),
870            Some(ExprType::list(ExprType::INT))
871        );
872    }
873
874    #[test]
875    fn derive_return_type_comparison_operators() {
876        let lib = &*DEFAULT_LIBRARY;
877        assert_eq!(
878            lib.derive_return_type("__eq__", &[ExprType::INT, ExprType::INT]),
879            Some(ExprType::BOOL)
880        );
881        assert_eq!(
882            lib.derive_return_type("__ne__", &[ExprType::STRING, ExprType::STRING]),
883            Some(ExprType::BOOL)
884        );
885        assert_eq!(
886            lib.derive_return_type("__lt__", &[ExprType::INT, ExprType::FLOAT]),
887            Some(ExprType::BOOL)
888        );
889        assert_eq!(
890            lib.derive_return_type("__ge__", &[ExprType::FLOAT, ExprType::INT]),
891            Some(ExprType::BOOL)
892        );
893    }
894
895    #[test]
896    fn derive_return_type_contains_operators() {
897        let lib = &*DEFAULT_LIBRARY;
898        assert_eq!(
899            lib.derive_return_type(
900                "__contains__",
901                &[ExprType::list(ExprType::INT), ExprType::INT]
902            ),
903            Some(ExprType::BOOL)
904        );
905        assert_eq!(
906            lib.derive_return_type("__contains__", &[ExprType::STRING, ExprType::STRING]),
907            Some(ExprType::BOOL)
908        );
909        assert_eq!(
910            lib.derive_return_type(
911                "__not_contains__",
912                &[ExprType::list(ExprType::STRING), ExprType::STRING]
913            ),
914            Some(ExprType::BOOL)
915        );
916    }
917
918    #[test]
919    fn derive_return_type_slice_operators() {
920        let lib = &*DEFAULT_LIBRARY;
921        assert_eq!(
922            lib.derive_return_type(
923                "__getitem__",
924                &[
925                    ExprType::list(ExprType::INT),
926                    ExprType::NULLTYPE,
927                    ExprType::INT,
928                    ExprType::NULLTYPE
929                ]
930            ),
931            Some(ExprType::list(ExprType::INT))
932        );
933        assert_eq!(
934            lib.derive_return_type(
935                "__getitem__",
936                &[
937                    ExprType::STRING,
938                    ExprType::INT,
939                    ExprType::NULLTYPE,
940                    ExprType::NULLTYPE
941                ]
942            ),
943            Some(ExprType::STRING)
944        );
945    }
946
947    #[test]
948    fn get_property_type_path() {
949        let lib = &*DEFAULT_LIBRARY;
950        assert_eq!(
951            lib.get_property_type(&ExprType::PATH, "name"),
952            Some(ExprType::STRING)
953        );
954        assert_eq!(
955            lib.get_property_type(&ExprType::PATH, "parent"),
956            Some(ExprType::PATH)
957        );
958        assert_eq!(
959            lib.get_property_type(&ExprType::PATH, "suffixes"),
960            Some(ExprType::list(ExprType::STRING))
961        );
962        assert_eq!(lib.get_property_type(&ExprType::INT, "name"), None);
963    }
964
965    #[test]
966    fn signature_count() {
967        let lib = &*DEFAULT_LIBRARY;
968        let total: usize = lib
969            .function_names()
970            .map(|n| lib.get_signatures(n).len())
971            .sum();
972        assert!(total >= 190, "total signatures: {total}");
973    }
974
975    #[test]
976    fn all_signatures_have_real_implementations() {
977        // Verify zero nyi — all signatures have real implementations
978        let st = crate::SymbolTable::new();
979        // Test a representative function from each category
980        assert!(crate::ParsedExpression::new("1 + 2")
981            .and_then(|p| p.evaluate(&st))
982            .is_ok()); // arithmetic
983        assert!(crate::ParsedExpression::new("'hello'.upper()")
984            .and_then(|p| p.evaluate(&st))
985            .is_ok()); // string
986        assert!(crate::ParsedExpression::new("len([1,2,3])")
987            .and_then(|p| p.evaluate(&st))
988            .is_ok()); // list
989        assert!(crate::ParsedExpression::new("abs(-5)")
990            .and_then(|p| p.evaluate(&st))
991            .is_ok()); // math
992        assert!(crate::ParsedExpression::new("int('42')")
993            .and_then(|p| p.evaluate(&st))
994            .is_ok()); // conversion
995        assert!(crate::ParsedExpression::new("repr_py(42)")
996            .and_then(|p| p.evaluate(&st))
997            .is_ok()); // repr
998        assert!(crate::ParsedExpression::new("1 == 1")
999            .and_then(|p| p.evaluate(&st))
1000            .is_ok()); // comparison
1001        assert!(crate::ParsedExpression::new("2 in [1,2,3]")
1002            .and_then(|p| p.evaluate(&st))
1003            .is_ok()); // contains
1004        assert!(crate::ParsedExpression::new("[1,2,3][0]")
1005            .and_then(|p| p.evaluate(&st))
1006            .is_ok()); // getitem
1007        assert!(crate::ParsedExpression::new("sorted([3,1,2])")
1008            .and_then(|p| p.evaluate(&st))
1009            .is_ok()); // list functions
1010        assert!(crate::ParsedExpression::new("range(5)")
1011            .and_then(|p| p.evaluate(&st))
1012            .is_ok()); // range
1013    }
1014
1015    #[test]
1016    fn python_function_names_present() {
1017        let lib = &*DEFAULT_LIBRARY;
1018        // All function names from the Python implementation
1019        let expected = vec![
1020            "__add__",
1021            "__sub__",
1022            "__mul__",
1023            "__truediv__",
1024            "__floordiv__",
1025            "__mod__",
1026            "__pow__",
1027            "__neg__",
1028            "__pos__",
1029            "__not__",
1030            "__eq__",
1031            "__ne__",
1032            "__lt__",
1033            "__le__",
1034            "__gt__",
1035            "__ge__",
1036            "__contains__",
1037            "__not_contains__",
1038            "__getitem__",
1039            "__property_name__",
1040            "__property_stem__",
1041            "__property_suffix__",
1042            "__property_suffixes__",
1043            "__property_parent__",
1044            "__property_parts__",
1045            "abs",
1046            "all",
1047            "any",
1048            "as_posix",
1049            "bool",
1050            "capitalize",
1051            "ceil",
1052            "center",
1053            "count",
1054            "endswith",
1055            "fail",
1056            "find",
1057            "flatten",
1058            "float",
1059            "floor",
1060            "index",
1061            "int",
1062            "is_absolute",
1063            "is_relative_to",
1064            "isalnum",
1065            "isalpha",
1066            "isascii",
1067            "isdigit",
1068            "islower",
1069            "isspace",
1070            "isupper",
1071            "join",
1072            "len",
1073            "list",
1074            "ljust",
1075            "lower",
1076            "lstrip",
1077            "max",
1078            "min",
1079            "path",
1080            "range",
1081            "range_expr",
1082            "re_escape",
1083            "re_findall",
1084            "re_match",
1085            "re_search",
1086            "re_split",
1087            "re_sub",
1088            "relative_to",
1089            "removeprefix",
1090            "removesuffix",
1091            "replace",
1092            "repr_cmd",
1093            "repr_json",
1094            "repr_py",
1095            "repr_pwsh",
1096            "repr_sh",
1097            "reversed",
1098            "rfind",
1099            "rindex",
1100            "rjust",
1101            "round",
1102            "rsplit",
1103            "rstrip",
1104            "sorted",
1105            "split",
1106            "startswith",
1107            "string",
1108            "strip",
1109            "sum",
1110            "title",
1111            "unique",
1112            "upper",
1113            "with_name",
1114            "with_number",
1115            "with_stem",
1116            "with_suffix",
1117            "zfill",
1118        ];
1119        for name in &expected {
1120            assert!(
1121                !lib.get_signatures(name).is_empty(),
1122                "Missing function: {name}"
1123            );
1124        }
1125        // apply_path_mapping should NOT be in default library
1126        assert!(
1127            lib.get_signatures("apply_path_mapping").is_empty(),
1128            "apply_path_mapping should only be available with host context"
1129        );
1130    }
1131
1132    #[test]
1133    fn host_context_has_apply_path_mapping() {
1134        let lib = FunctionLibrary::for_profile(&ExprProfile::current().with_host_context(
1135            HostContext::with_rules(Vec::<crate::path_mapping::PathMappingRule>::new()),
1136        ));
1137        assert!(!lib.get_signatures("apply_path_mapping").is_empty());
1138        assert!(lib.host_context_enabled);
1139    }
1140}
1141
1142#[cfg(test)]
1143mod for_profile_tests {
1144    //! Tests that use only the non-deprecated `FunctionLibrary::for_profile`
1145    //! API. Kept out of the main `tests` module so they prove the new API
1146    //! works without relying on the deprecated surface.
1147
1148    use crate::path_mapping::{PathFormat, PathMappingRule};
1149    use crate::profile::{ExprProfile, HostContext};
1150    use crate::FunctionLibrary;
1151
1152    #[test]
1153    fn default_profile_has_builtins() {
1154        let lib = FunctionLibrary::for_profile(&ExprProfile::current());
1155        assert!(!lib.get_signatures("__add__").is_empty());
1156        assert!(!lib.get_signatures("upper").is_empty());
1157        // No host context → no apply_path_mapping.
1158        assert!(lib.get_signatures("apply_path_mapping").is_empty());
1159        assert!(!lib.host_context_enabled);
1160    }
1161
1162    #[test]
1163    fn unresolved_host_context_registers_stub() {
1164        let profile = ExprProfile::current().with_host_context(HostContext::Unresolved);
1165        let lib = FunctionLibrary::for_profile(&profile);
1166        assert!(!lib.get_signatures("apply_path_mapping").is_empty());
1167        assert!(lib.host_context_enabled);
1168    }
1169
1170    #[test]
1171    fn with_rules_host_context_registers_real_impl() {
1172        let rule = PathMappingRule {
1173            source_path_format: PathFormat::Posix,
1174            source_path: "/src".into(),
1175            destination_path: "/dst".into(),
1176        };
1177        let profile = ExprProfile::current().with_host_context(HostContext::with_rules(vec![rule]));
1178        let lib = FunctionLibrary::for_profile(&profile);
1179        assert!(!lib.get_signatures("apply_path_mapping").is_empty());
1180        assert!(lib.host_context_enabled);
1181    }
1182
1183    #[test]
1184    fn cache_returns_same_arc_for_none_profile() {
1185        // Two requests with the same profile shape should share a cached
1186        // skeleton Arc.
1187        let profile = ExprProfile::current();
1188        let a = FunctionLibrary::for_profile(&profile);
1189        let b = FunctionLibrary::for_profile(&profile);
1190        assert!(std::sync::Arc::ptr_eq(&a, &b));
1191    }
1192
1193    #[test]
1194    fn cache_returns_same_arc_for_unresolved_profile() {
1195        let profile = ExprProfile::current().with_host_context(HostContext::Unresolved);
1196        let a = FunctionLibrary::for_profile(&profile);
1197        let b = FunctionLibrary::for_profile(&profile);
1198        assert!(std::sync::Arc::ptr_eq(&a, &b));
1199    }
1200
1201    #[test]
1202    fn with_rules_does_not_cache_rules_variant() {
1203        // Rules are per-call; the returned Arc must be fresh each time
1204        // so that different rule sets don't alias.
1205        let r1 = vec![PathMappingRule {
1206            source_path_format: PathFormat::Posix,
1207            source_path: "/a".into(),
1208            destination_path: "/b".into(),
1209        }];
1210        let r2 = vec![PathMappingRule {
1211            source_path_format: PathFormat::Posix,
1212            source_path: "/c".into(),
1213            destination_path: "/d".into(),
1214        }];
1215        let p1 = ExprProfile::current().with_host_context(HostContext::with_rules(r1));
1216        let p2 = ExprProfile::current().with_host_context(HostContext::with_rules(r2));
1217        let a = FunctionLibrary::for_profile(&p1);
1218        let b = FunctionLibrary::for_profile(&p2);
1219        assert!(!std::sync::Arc::ptr_eq(&a, &b));
1220    }
1221
1222    #[test]
1223    fn evaluator_works_with_for_profile_library() {
1224        // Smoke test: evaluate a simple expression using a library from
1225        // for_profile, end-to-end.
1226        use crate::{ExprValue, ParsedExpression, SymbolTable};
1227        let lib = FunctionLibrary::for_profile(&ExprProfile::current());
1228        let parsed = ParsedExpression::new("1 + 2 * 3").unwrap();
1229        let st = SymbolTable::new();
1230        let v = parsed.with_library(&lib).evaluate(&[&st]).unwrap();
1231        assert_eq!(v, ExprValue::Int(7));
1232    }
1233}