Skip to main content

sim_lib_music_core/
arranger_expr.rs

1use sim_kernel::{Error, Expr, Result, Symbol};
2
3use crate::arranger::{
4    Arranger, ArrangerPlacement, FilterRef, PitchRemap, PlacementTransform, PlayableRef,
5    StretchPolicy, TracePolicy,
6};
7use crate::arranger_music_expr::{music_from_expr, music_to_expr};
8use crate::{LaneId, LaneTarget, Pitch, PitchClass, Time};
9
10const NS: &str = "music/arranger";
11
12impl Arranger {
13    /// Encodes the arrangement as a tagged `music/arranger` expression map.
14    pub fn to_expr(&self) -> Expr {
15        map(vec![
16            ("tag", tag_expr("arranger")),
17            (
18                "lanes",
19                Expr::Vector(
20                    self.lanes
21                        .iter()
22                        .map(|lane| Expr::String(lane.0.clone()))
23                        .collect(),
24                ),
25            ),
26            (
27                "placements",
28                Expr::Vector(
29                    self.placements
30                        .iter()
31                        .map(ArrangerPlacement::to_expr)
32                        .collect(),
33                ),
34            ),
35        ])
36    }
37
38    /// Decodes an arrangement from a tagged `music/arranger` expression map.
39    pub fn from_expr(expr: &Expr) -> Result<Self> {
40        let entries = expr_map(expr, "arranger")?;
41        expect_tag(entries, "arranger", "arranger")?;
42        let lanes = optional_vector(entries, "lanes")?
43            .iter()
44            .map(|expr| Ok(LaneId::new(expr_string(expr, "lane")?.to_owned())))
45            .collect::<Result<Vec<_>>>()?;
46        let placements = expr_vector(lookup_required(entries, "placements")?, "placements")?
47            .iter()
48            .map(ArrangerPlacement::from_expr)
49            .collect::<Result<Vec<_>>>()?;
50        Self::new(placements, lanes)
51    }
52}
53
54impl ArrangerPlacement {
55    /// Encodes the placement as a tagged `placement` expression map.
56    pub fn to_expr(&self) -> Expr {
57        map(vec![
58            ("tag", tag_expr("placement")),
59            ("id", Expr::Symbol(self.id.clone())),
60            ("playable", self.playable.to_expr()),
61            ("at", time_expr(self.at)),
62            (
63                "duration",
64                self.duration.map(time_expr).unwrap_or(Expr::Nil),
65            ),
66            ("lane", Expr::String(self.lane.0.clone())),
67            (
68                "targets",
69                Expr::Vector(self.targets.iter().map(lane_target_expr).collect()),
70            ),
71            ("stretch", self.stretch.to_expr()),
72            (
73                "transform",
74                Expr::Vector(
75                    self.transform
76                        .iter()
77                        .map(PlacementTransform::to_expr)
78                        .collect(),
79                ),
80            ),
81            ("remap-pitch", self.remap_pitch.to_expr()),
82            (
83                "filter",
84                self.filter
85                    .as_ref()
86                    .map(FilterRef::to_expr)
87                    .unwrap_or(Expr::Nil),
88            ),
89            (
90                "seed",
91                self.seed
92                    .map(|seed| Expr::String(seed.to_string()))
93                    .unwrap_or(Expr::Nil),
94            ),
95            ("trace", Expr::Symbol(self.trace.symbol())),
96        ])
97    }
98
99    /// Decodes a placement from a tagged `placement` expression map.
100    pub fn from_expr(expr: &Expr) -> Result<Self> {
101        let entries = expr_map(expr, "arranger placement")?;
102        expect_tag(entries, "placement", "arranger placement")?;
103        let duration = match lookup_required(entries, "duration")? {
104            Expr::Nil => None,
105            expr => Some(time_from_expr(expr)?),
106        };
107        let placement = Self {
108            id: expr_symbol(lookup_required(entries, "id")?, "placement id")?,
109            playable: PlayableRef::from_expr(lookup_required(entries, "playable")?)?,
110            at: time_from_expr(lookup_required(entries, "at")?)?,
111            duration,
112            lane: LaneId::new(expr_string(lookup_required(entries, "lane")?, "lane")?.to_owned()),
113            targets: expr_vector(lookup_required(entries, "targets")?, "placement targets")?
114                .iter()
115                .map(lane_target_from_expr)
116                .collect::<Result<Vec<_>>>()?,
117            stretch: StretchPolicy::from_expr(lookup_required(entries, "stretch")?)?,
118            transform: expr_vector(lookup_required(entries, "transform")?, "transform")?
119                .iter()
120                .map(PlacementTransform::from_expr)
121                .collect::<Result<Vec<_>>>()?,
122            remap_pitch: PitchRemap::from_expr(lookup_required(entries, "remap-pitch")?)?,
123            filter: match lookup_required(entries, "filter")? {
124                Expr::Nil => None,
125                expr => Some(FilterRef::from_expr(expr)?),
126            },
127            seed: match lookup_required(entries, "seed")? {
128                Expr::Nil => None,
129                expr => Some(expr_u64(expr, "placement seed")?),
130            },
131            trace: TracePolicy::from_expr(lookup_required(entries, "trace")?)?,
132        };
133        placement.validate()?;
134        Ok(placement)
135    }
136}
137
138impl PlayableRef {
139    /// Encodes the reference as a tagged `playable-ref` expression map.
140    pub fn to_expr(&self) -> Expr {
141        match self {
142            Self::Inline(music) => map(vec![
143                ("tag", tag_expr("playable-ref")),
144                ("kind", tag_expr("inline")),
145                ("value", music_to_expr(music)),
146            ]),
147            Self::Symbol(symbol) => map(vec![
148                ("tag", tag_expr("playable-ref")),
149                ("kind", tag_expr("symbol")),
150                ("value", Expr::Symbol(symbol.clone())),
151            ]),
152        }
153    }
154
155    /// Decodes the reference from a tagged `playable-ref` expression map.
156    pub fn from_expr(expr: &Expr) -> Result<Self> {
157        let entries = expr_map(expr, "playable ref")?;
158        expect_tag(entries, "playable-ref", "playable ref")?;
159        match symbol_name(lookup_required(entries, "kind")?, "playable ref kind")? {
160            "inline" => Ok(Self::inline(music_from_expr(lookup_required(
161                entries, "value",
162            )?)?)),
163            "symbol" => Ok(Self::symbol(expr_symbol(
164                lookup_required(entries, "value")?,
165                "playable symbol",
166            )?)),
167            _ => Err(Error::Eval("playable ref kind is invalid".to_owned())),
168        }
169    }
170}
171
172impl FilterRef {
173    /// Encodes the filter as a tagged `filter` expression map.
174    pub fn to_expr(&self) -> Expr {
175        map(vec![
176            ("tag", tag_expr("filter")),
177            ("id", Expr::Symbol(self.id.clone())),
178            (
179                "keep-lanes",
180                Expr::Vector(
181                    self.keep_lanes
182                        .iter()
183                        .map(|lane| Expr::String(lane.0.clone()))
184                        .collect(),
185                ),
186            ),
187        ])
188    }
189
190    /// Decodes the filter from a tagged `filter` expression map.
191    pub fn from_expr(expr: &Expr) -> Result<Self> {
192        let entries = expr_map(expr, "filter")?;
193        expect_tag(entries, "filter", "filter")?;
194        Ok(Self {
195            id: expr_symbol(lookup_required(entries, "id")?, "filter id")?,
196            keep_lanes: expr_vector(lookup_required(entries, "keep-lanes")?, "keep lanes")?
197                .iter()
198                .map(|expr| Ok(LaneId::new(expr_string(expr, "keep lane")?.to_owned())))
199                .collect::<Result<Vec<_>>>()?,
200        })
201    }
202}
203
204impl StretchPolicy {
205    fn to_expr(&self) -> Expr {
206        match self {
207            Self::None => map(vec![
208                ("tag", tag_expr("stretch")),
209                ("kind", tag_expr("none")),
210            ]),
211            Self::TempoRatio(ratio) => map(vec![
212                ("tag", tag_expr("stretch")),
213                ("kind", tag_expr("tempo-ratio")),
214                ("value", time_expr(*ratio)),
215            ]),
216            Self::TimeRatio(ratio) => map(vec![
217                ("tag", tag_expr("stretch")),
218                ("kind", tag_expr("time-ratio")),
219                ("value", time_expr(*ratio)),
220            ]),
221            Self::FitToDuration => map(vec![
222                ("tag", tag_expr("stretch")),
223                ("kind", tag_expr("fit")),
224            ]),
225        }
226    }
227
228    fn from_expr(expr: &Expr) -> Result<Self> {
229        let entries = expr_map(expr, "stretch policy")?;
230        expect_tag(entries, "stretch", "stretch policy")?;
231        match symbol_name(lookup_required(entries, "kind")?, "stretch kind")? {
232            "none" => Ok(Self::None),
233            "tempo-ratio" => Ok(Self::TempoRatio(time_from_expr(lookup_required(
234                entries, "value",
235            )?)?)),
236            "time-ratio" => Ok(Self::TimeRatio(time_from_expr(lookup_required(
237                entries, "value",
238            )?)?)),
239            "fit" => Ok(Self::FitToDuration),
240            _ => Err(Error::Eval("stretch policy kind is invalid".to_owned())),
241        }
242    }
243}
244
245impl PlacementTransform {
246    fn to_expr(&self) -> Expr {
247        match self {
248            Self::TransposeSemitones(semitones) => {
249                value_transform("transpose-semitones", *semitones)
250            }
251            Self::TransposeOctaves(octaves) => value_transform("transpose-octaves", *octaves),
252            Self::InvertAroundPitch(axis) => map(vec![
253                ("tag", tag_expr("transform")),
254                ("kind", tag_expr("invert-pitch")),
255                ("value", pitch_expr(*axis)),
256            ]),
257            Self::InvertAroundPitchClass(axis) => value_transform("invert-pitch-class", axis.0),
258            Self::Retrograde => map(vec![
259                ("tag", tag_expr("transform")),
260                ("kind", tag_expr("retrograde")),
261            ]),
262        }
263    }
264
265    fn from_expr(expr: &Expr) -> Result<Self> {
266        let entries = expr_map(expr, "placement transform")?;
267        expect_tag(entries, "transform", "placement transform")?;
268        match symbol_name(lookup_required(entries, "kind")?, "transform kind")? {
269            "transpose-semitones" => Ok(Self::TransposeSemitones(expr_i32(
270                lookup_required(entries, "value")?,
271                "transpose semitones",
272            )?)),
273            "transpose-octaves" => Ok(Self::TransposeOctaves(expr_i16(
274                lookup_required(entries, "value")?,
275                "transpose octaves",
276            )?)),
277            "invert-pitch" => Ok(Self::InvertAroundPitch(pitch_from_expr(lookup_required(
278                entries, "value",
279            )?)?)),
280            "invert-pitch-class" => Ok(Self::InvertAroundPitchClass(
281                PitchClass::new(expr_u8(lookup_required(entries, "value")?, "pitch class")?)
282                    .map_err(|_| Error::Eval("pitch class is invalid".to_owned()))?,
283            )),
284            "retrograde" => Ok(Self::Retrograde),
285            _ => Err(Error::Eval(
286                "placement transform kind is invalid".to_owned(),
287            )),
288        }
289    }
290}
291
292impl PitchRemap {
293    fn to_expr(&self) -> Expr {
294        match self {
295            Self::None => map(vec![
296                ("tag", tag_expr("pitch-remap")),
297                ("kind", tag_expr("none")),
298            ]),
299            Self::Chromatic(semitones) => value_remap("chromatic", *semitones),
300            Self::PitchClass { from, to } => map(vec![
301                ("tag", tag_expr("pitch-remap")),
302                ("kind", tag_expr("pitch-class")),
303                ("from", Expr::String(from.0.to_string())),
304                ("to", Expr::String(to.0.to_string())),
305            ]),
306            Self::DrumKey(items) => map(vec![
307                ("tag", tag_expr("pitch-remap")),
308                ("kind", tag_expr("drum-key")),
309                (
310                    "items",
311                    Expr::Vector(items.iter().map(drum_key_expr).collect()),
312                ),
313            ]),
314            Self::ScaleDegree(symbol) => symbolic_remap_expr("scale-degree", symbol),
315            Self::ChordTone(symbol) => symbolic_remap_expr("chord-tone", symbol),
316            Self::Tuning(symbol) => symbolic_remap_expr("tuning", symbol),
317            Self::Vector(symbol) => symbolic_remap_expr("vector", symbol),
318            Self::Matrix(symbol) => symbolic_remap_expr("matrix", symbol),
319            Self::Callable(symbol) => symbolic_remap_expr("callable", symbol),
320        }
321    }
322
323    fn from_expr(expr: &Expr) -> Result<Self> {
324        let entries = expr_map(expr, "pitch remap")?;
325        expect_tag(entries, "pitch-remap", "pitch remap")?;
326        match symbol_name(lookup_required(entries, "kind")?, "pitch remap kind")? {
327            "none" => Ok(Self::None),
328            "chromatic" => Ok(Self::Chromatic(expr_i32(
329                lookup_required(entries, "value")?,
330                "chromatic remap",
331            )?)),
332            "pitch-class" => Ok(Self::PitchClass {
333                from: PitchClass::new(expr_u8(lookup_required(entries, "from")?, "from")?)
334                    .map_err(|_| Error::Eval("source pitch class is invalid".to_owned()))?,
335                to: PitchClass::new(expr_u8(lookup_required(entries, "to")?, "to")?)
336                    .map_err(|_| Error::Eval("target pitch class is invalid".to_owned()))?,
337            }),
338            "drum-key" => expr_vector(lookup_required(entries, "items")?, "drum key items")?
339                .iter()
340                .map(drum_key_from_expr)
341                .collect::<Result<Vec<_>>>()
342                .map(Self::DrumKey),
343            "scale-degree" => symbolic_remap(entries, Self::ScaleDegree),
344            "chord-tone" => symbolic_remap(entries, Self::ChordTone),
345            "tuning" => symbolic_remap(entries, Self::Tuning),
346            "vector" => symbolic_remap(entries, Self::Vector),
347            "matrix" => symbolic_remap(entries, Self::Matrix),
348            "callable" => symbolic_remap(entries, Self::Callable),
349            _ => Err(Error::Eval("pitch remap kind is invalid".to_owned())),
350        }
351    }
352}
353
354impl TracePolicy {
355    /// Returns the `music/arranger` symbol that names this trace policy.
356    pub fn symbol(self) -> Symbol {
357        match self {
358            Self::Off => tag("trace-off"),
359            Self::Diagnostics => tag("trace-diagnostics"),
360            Self::Full => tag("trace-full"),
361        }
362    }
363
364    fn from_expr(expr: &Expr) -> Result<Self> {
365        match symbol_name(expr, "trace policy")? {
366            "trace-off" => Ok(Self::Off),
367            "trace-diagnostics" => Ok(Self::Diagnostics),
368            "trace-full" => Ok(Self::Full),
369            _ => Err(Error::Eval("trace policy is invalid".to_owned())),
370        }
371    }
372}
373
374impl PartialEq for Arranger {
375    fn eq(&self, other: &Self) -> bool {
376        self.to_expr().canonical_eq(&other.to_expr())
377    }
378}
379
380impl Eq for Arranger {}
381
382impl PartialEq for ArrangerPlacement {
383    fn eq(&self, other: &Self) -> bool {
384        self.to_expr().canonical_eq(&other.to_expr())
385    }
386}
387
388impl Eq for ArrangerPlacement {}
389
390impl PartialEq for PlayableRef {
391    fn eq(&self, other: &Self) -> bool {
392        self.to_expr().canonical_eq(&other.to_expr())
393    }
394}
395
396impl Eq for PlayableRef {}
397
398fn time_expr(time: Time) -> Expr {
399    map(vec![
400        ("numer", Expr::String(time.numer().to_string())),
401        ("denom", Expr::String(time.denom().to_string())),
402    ])
403}
404
405fn time_from_expr(expr: &Expr) -> Result<Time> {
406    let entries = expr_map(expr, "time")?;
407    let denominator = expr_i64(lookup_required(entries, "denom")?, "time denominator")?;
408    if denominator == 0 {
409        return Err(Error::Eval("time denominator cannot be zero".to_owned()));
410    }
411    Ok(Time::new(
412        expr_i64(lookup_required(entries, "numer")?, "time numerator")?,
413        denominator,
414    ))
415}
416
417fn pitch_expr(pitch: Pitch) -> Expr {
418    Expr::String(
419        pitch
420            .to_midi()
421            .map(|midi| format!("midi:{midi}"))
422            .unwrap_or_else(|| format!("semitone:{}", pitch.semitone())),
423    )
424}
425
426fn pitch_from_expr(expr: &Expr) -> Result<Pitch> {
427    let value = expr_string(expr, "pitch")?;
428    if let Some(midi) = value.strip_prefix("midi:") {
429        return Ok(Pitch::from_midi(
430            midi.parse::<u8>()
431                .map_err(|_| Error::Eval("MIDI pitch is invalid".to_owned()))?,
432        ));
433    }
434    if let Some(semitone) = value.strip_prefix("semitone:") {
435        return Ok(Pitch::from_semitone(semitone.parse::<i32>().map_err(
436            |_| Error::Eval("semitone pitch is invalid".to_owned()),
437        )?));
438    }
439    crate::parse_pitch(value).map_err(|_| Error::Eval("pitch is invalid".to_owned()))
440}
441
442fn lane_target_expr(target: &LaneTarget) -> Expr {
443    match target {
444        LaneTarget::Instrument(symbol) => target_expr("instrument", symbol),
445        LaneTarget::Stream(symbol) => target_expr("stream", symbol),
446        LaneTarget::Control(symbol) => target_expr("control", symbol),
447        LaneTarget::None => map(vec![("kind", tag_expr("none"))]),
448    }
449}
450
451fn lane_target_from_expr(expr: &Expr) -> Result<LaneTarget> {
452    let entries = expr_map(expr, "lane target")?;
453    match symbol_name(lookup_required(entries, "kind")?, "lane target kind")? {
454        "instrument" => Ok(LaneTarget::Instrument(expr_symbol(
455            lookup_required(entries, "symbol")?,
456            "target symbol",
457        )?)),
458        "stream" => Ok(LaneTarget::Stream(expr_symbol(
459            lookup_required(entries, "symbol")?,
460            "target symbol",
461        )?)),
462        "control" => Ok(LaneTarget::Control(expr_symbol(
463            lookup_required(entries, "symbol")?,
464            "target symbol",
465        )?)),
466        "none" => Ok(LaneTarget::None),
467        _ => Err(Error::Eval("lane target kind is invalid".to_owned())),
468    }
469}
470
471fn value_transform<T: ToString>(kind: &'static str, value: T) -> Expr {
472    map(vec![
473        ("tag", tag_expr("transform")),
474        ("kind", tag_expr(kind)),
475        ("value", Expr::String(value.to_string())),
476    ])
477}
478
479fn value_remap<T: ToString>(kind: &'static str, value: T) -> Expr {
480    map(vec![
481        ("tag", tag_expr("pitch-remap")),
482        ("kind", tag_expr(kind)),
483        ("value", Expr::String(value.to_string())),
484    ])
485}
486
487fn drum_key_expr((from, to): &(u8, u8)) -> Expr {
488    map(vec![
489        ("from", Expr::String(from.to_string())),
490        ("to", Expr::String(to.to_string())),
491    ])
492}
493
494fn drum_key_from_expr(expr: &Expr) -> Result<(u8, u8)> {
495    let item = expr_map(expr, "drum key item")?;
496    Ok((
497        expr_u8(lookup_required(item, "from")?, "drum key from")?,
498        expr_u8(lookup_required(item, "to")?, "drum key to")?,
499    ))
500}
501
502fn symbolic_remap_expr(kind: &'static str, symbol: &Symbol) -> Expr {
503    map(vec![
504        ("tag", tag_expr("pitch-remap")),
505        ("kind", tag_expr(kind)),
506        ("symbol", Expr::Symbol(symbol.clone())),
507    ])
508}
509
510fn symbolic_remap(
511    entries: &[(Expr, Expr)],
512    build: impl FnOnce(Symbol) -> PitchRemap,
513) -> Result<PitchRemap> {
514    Ok(build(expr_symbol(
515        lookup_required(entries, "symbol")?,
516        "pitch remap symbol",
517    )?))
518}
519
520fn target_expr(kind: &'static str, symbol: &Symbol) -> Expr {
521    map(vec![
522        ("kind", tag_expr(kind)),
523        ("symbol", Expr::Symbol(symbol.clone())),
524    ])
525}
526
527fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
528    Expr::Map(
529        entries
530            .into_iter()
531            .map(|(key, value)| (field(key), value))
532            .collect(),
533    )
534}
535
536fn field(name: &'static str) -> Expr {
537    sim_value::build::qsym(NS, name)
538}
539
540fn tag(name: &'static str) -> Symbol {
541    Symbol::qualified(NS, name)
542}
543
544fn tag_expr(name: &'static str) -> Expr {
545    Expr::Symbol(tag(name))
546}
547
548fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
549    match expr {
550        Expr::Map(entries) => Ok(entries),
551        _ => Err(Error::Eval(format!("{context} must be a map"))),
552    }
553}
554
555fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
556    match expr {
557        Expr::Vector(items) => Ok(items),
558        _ => Err(Error::Eval(format!("{context} must be a vector"))),
559    }
560}
561
562fn optional_vector<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
563    match lookup(entries, name) {
564        Some(expr) => expr_vector(expr, name),
565        None => Ok(&[]),
566    }
567}
568
569fn lookup_required<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
570    lookup(entries, name).ok_or_else(|| Error::Eval(format!("arranger field is missing: {name}")))
571}
572
573fn lookup<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
574    entries.iter().find_map(|(key, value)| match key {
575        Expr::Symbol(symbol)
576            if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
577        {
578            Some(value)
579        }
580        _ => None,
581    })
582}
583
584fn expect_tag(entries: &[(Expr, Expr)], name: &'static str, context: &str) -> Result<()> {
585    match lookup(entries, "tag") {
586        Some(Expr::Symbol(symbol))
587            if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
588        {
589            Ok(())
590        }
591        Some(_) => Err(Error::Eval(format!("{context} tag is invalid"))),
592        None => Err(Error::Eval(format!("{context} tag is missing"))),
593    }
594}
595
596fn symbol_name<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
597    match expr {
598        Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(NS) => Ok(symbol.name.as_ref()),
599        _ => Err(Error::Eval(format!("{context} must be an arranger symbol"))),
600    }
601}
602
603fn expr_symbol(expr: &Expr, context: &str) -> Result<Symbol> {
604    match expr {
605        Expr::Symbol(symbol) => Ok(symbol.clone()),
606        _ => Err(Error::Eval(format!("{context} must be a symbol"))),
607    }
608}
609
610fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
611    match expr {
612        Expr::String(value) => Ok(value),
613        _ => Err(Error::Eval(format!("{context} must be a string"))),
614    }
615}
616
617fn expr_i16(expr: &Expr, context: &str) -> Result<i16> {
618    parse_number(expr, context)
619}
620
621fn expr_i32(expr: &Expr, context: &str) -> Result<i32> {
622    parse_number(expr, context)
623}
624
625fn expr_i64(expr: &Expr, context: &str) -> Result<i64> {
626    parse_number(expr, context)
627}
628
629fn expr_u8(expr: &Expr, context: &str) -> Result<u8> {
630    parse_number(expr, context)
631}
632
633fn expr_u64(expr: &Expr, context: &str) -> Result<u64> {
634    parse_number(expr, context)
635}
636
637fn parse_number<T>(expr: &Expr, context: &str) -> Result<T>
638where
639    T: std::str::FromStr,
640{
641    let text = match expr {
642        Expr::String(value) => value.as_str(),
643        Expr::Number(value) => value.canonical.as_str(),
644        _ => return Err(Error::Eval(format!("{context} must be a number"))),
645    };
646    text.parse()
647        .map_err(|_| Error::Eval(format!("{context} is invalid")))
648}