Skip to main content

yulang_native/
eval.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use yulang_runtime as runtime;
5use yulang_typed_ir as typed_ir;
6
7use crate::control_ir::{
8    BlockId, NativeBlock, NativeFunction, NativeLiteral, NativeModule, NativeStmt,
9    NativeTerminator, ValueId,
10};
11
12pub type NativeEvalResult<T> = Result<T, NativeEvalError>;
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum NativeEvalError {
16    EmptyFunction {
17        name: String,
18    },
19    MissingFunction {
20        name: String,
21    },
22    MissingBlock {
23        id: BlockId,
24    },
25    BlockArgumentMismatch {
26        id: BlockId,
27        expected: usize,
28        actual: usize,
29    },
30    MissingValue {
31        id: ValueId,
32    },
33    ExpectedPlainValue {
34        id: ValueId,
35    },
36    ExpectedClosure {
37        id: ValueId,
38    },
39    ExpectedRecord {
40        value: runtime::VmValue,
41    },
42    ExpectedTuple {
43        value: runtime::VmValue,
44    },
45    ExpectedVariant {
46        value: runtime::VmValue,
47    },
48    UnsupportedPrimitive {
49        op: typed_ir::PrimitiveOp,
50    },
51    PrimitiveTypeMismatch {
52        op: typed_ir::PrimitiveOp,
53        value: runtime::VmValue,
54    },
55    InvalidPrimitiveArity {
56        op: typed_ir::PrimitiveOp,
57        expected: usize,
58        actual: usize,
59    },
60}
61
62impl fmt::Display for NativeEvalError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            NativeEvalError::EmptyFunction { name } => {
66                write!(f, "native control function {name} has no entry block")
67            }
68            NativeEvalError::MissingFunction { name } => {
69                write!(f, "native control function {name} is missing")
70            }
71            NativeEvalError::MissingBlock { id } => {
72                write!(f, "native control block {id:?} is missing")
73            }
74            NativeEvalError::BlockArgumentMismatch {
75                id,
76                expected,
77                actual,
78            } => {
79                write!(
80                    f,
81                    "native control block {id:?} expected {expected} arguments, got {actual}"
82                )
83            }
84            NativeEvalError::MissingValue { id } => {
85                write!(f, "native control value {id:?} is missing")
86            }
87            NativeEvalError::ExpectedPlainValue { id } => {
88                write!(f, "native control expected plain value {id:?}")
89            }
90            NativeEvalError::ExpectedClosure { id } => {
91                write!(f, "native control expected closure value {id:?}")
92            }
93            NativeEvalError::ExpectedRecord { value } => {
94                write!(f, "native control expected record, got {value:?}")
95            }
96            NativeEvalError::ExpectedTuple { value } => {
97                write!(f, "native control expected tuple, got {value:?}")
98            }
99            NativeEvalError::ExpectedVariant { value } => {
100                write!(f, "native control expected variant, got {value:?}")
101            }
102            NativeEvalError::UnsupportedPrimitive { op } => {
103                write!(
104                    f,
105                    "native control evaluator does not support primitive {op:?} yet"
106                )
107            }
108            NativeEvalError::PrimitiveTypeMismatch { op, value } => {
109                write!(f, "native primitive {op:?} cannot accept value {value:?}")
110            }
111            NativeEvalError::InvalidPrimitiveArity {
112                op,
113                expected,
114                actual,
115            } => write!(
116                f,
117                "native primitive {op:?} expected {expected} arguments, got {actual}"
118            ),
119        }
120    }
121}
122
123impl std::error::Error for NativeEvalError {}
124
125pub fn eval_module(module: &NativeModule) -> NativeEvalResult<Vec<runtime::VmValue>> {
126    module
127        .roots
128        .iter()
129        .map(|root| eval_function(module, root, Vec::new()))
130        .collect()
131}
132
133fn eval_function(
134    module: &NativeModule,
135    function: &NativeFunction,
136    args: Vec<runtime::VmValue>,
137) -> NativeEvalResult<runtime::VmValue> {
138    into_plain_value(
139        ValueId(usize::MAX),
140        eval_function_value(
141            module,
142            function,
143            args.into_iter().map(NativeRuntimeValue::Plain).collect(),
144        )?,
145    )
146}
147
148fn eval_function_value(
149    module: &NativeModule,
150    function: &NativeFunction,
151    args: Vec<NativeRuntimeValue>,
152) -> NativeEvalResult<NativeRuntimeValue> {
153    if function.params.len() != args.len() {
154        return Err(NativeEvalError::BlockArgumentMismatch {
155            id: BlockId(0),
156            expected: function.params.len(),
157            actual: args.len(),
158        });
159    }
160    let entry = function
161        .blocks
162        .first()
163        .ok_or_else(|| NativeEvalError::EmptyFunction {
164            name: function.name.clone(),
165        })?;
166    eval_blocks(module, function, entry.id, args)
167}
168
169fn eval_blocks(
170    module: &NativeModule,
171    function: &NativeFunction,
172    entry: BlockId,
173    initial_args: Vec<NativeRuntimeValue>,
174) -> NativeEvalResult<NativeRuntimeValue> {
175    let mut values = Vec::<Option<NativeRuntimeValue>>::new();
176    let mut current = entry;
177    let mut args = initial_args;
178    loop {
179        let block = block_by_id(function, current)?;
180        assign_block_args(&mut values, block, args)?;
181        args = Vec::new();
182
183        for stmt in &block.stmts {
184            match stmt {
185                NativeStmt::Literal { dest, literal } => {
186                    write_value(
187                        &mut values,
188                        *dest,
189                        NativeRuntimeValue::Plain(eval_literal(literal)),
190                    );
191                }
192                NativeStmt::Primitive { dest, op, args } => {
193                    let args = args
194                        .iter()
195                        .map(|id| read_plain_value(&values, *id))
196                        .collect::<NativeEvalResult<Vec<_>>>()?;
197                    write_value(
198                        &mut values,
199                        *dest,
200                        NativeRuntimeValue::Plain(eval_primitive(*op, &args)?),
201                    );
202                }
203                NativeStmt::DirectCall { dest, target, args } => {
204                    let function = module
205                        .functions
206                        .iter()
207                        .find(|function| &function.name == target)
208                        .ok_or_else(|| NativeEvalError::MissingFunction {
209                            name: target.clone(),
210                        })?;
211                    let args = args
212                        .iter()
213                        .map(|id| read_value(&values, *id))
214                        .collect::<NativeEvalResult<Vec<_>>>()?;
215                    write_value(
216                        &mut values,
217                        *dest,
218                        eval_function_value(module, function, args)?,
219                    );
220                }
221                NativeStmt::Tuple { dest, items } => {
222                    let items = items
223                        .iter()
224                        .map(|id| read_plain_value(&values, *id))
225                        .collect::<NativeEvalResult<Vec<_>>>()?;
226                    write_value(
227                        &mut values,
228                        *dest,
229                        NativeRuntimeValue::Plain(runtime::VmValue::Tuple(items)),
230                    );
231                }
232                NativeStmt::Record { dest, base, fields } => {
233                    let mut record = match base {
234                        Some(base) => match read_plain_value(&values, *base)? {
235                            runtime::VmValue::Record(fields) => fields,
236                            value => return Err(NativeEvalError::ExpectedRecord { value }),
237                        },
238                        None => BTreeMap::new(),
239                    };
240                    for field in fields {
241                        record.insert(field.name.clone(), read_plain_value(&values, field.value)?);
242                    }
243                    write_value(
244                        &mut values,
245                        *dest,
246                        NativeRuntimeValue::Plain(runtime::VmValue::Record(record)),
247                    );
248                }
249                NativeStmt::RecordWithoutFields { dest, base, fields } => {
250                    let mut record = match read_plain_value(&values, *base)? {
251                        runtime::VmValue::Record(fields) => fields,
252                        value => return Err(NativeEvalError::ExpectedRecord { value }),
253                    };
254                    for field in fields {
255                        record.remove(field);
256                    }
257                    write_value(
258                        &mut values,
259                        *dest,
260                        NativeRuntimeValue::Plain(runtime::VmValue::Record(record)),
261                    );
262                }
263                NativeStmt::Variant { dest, tag, value } => {
264                    let value = value
265                        .map(|value| read_plain_value(&values, value).map(Box::new))
266                        .transpose()?;
267                    write_value(
268                        &mut values,
269                        *dest,
270                        NativeRuntimeValue::Plain(runtime::VmValue::Variant {
271                            tag: tag.clone(),
272                            value,
273                        }),
274                    );
275                }
276                NativeStmt::Select { dest, base, field } => {
277                    let value = match read_plain_value(&values, *base)? {
278                        runtime::VmValue::Record(fields) => fields.get(field).cloned(),
279                        _ => None,
280                    }
281                    .ok_or(NativeEvalError::ExpectedPlainValue { id: *base })?;
282                    write_value(&mut values, *dest, NativeRuntimeValue::Plain(value));
283                }
284                NativeStmt::TupleGet { dest, tuple, index } => {
285                    let value = read_plain_value(&values, *tuple)?;
286                    let runtime::VmValue::Tuple(items) = value else {
287                        return Err(NativeEvalError::ExpectedTuple { value });
288                    };
289                    let value = items.get(*index).cloned().ok_or_else(|| {
290                        NativeEvalError::ExpectedTuple {
291                            value: runtime::VmValue::Tuple(items.clone()),
292                        }
293                    })?;
294                    write_value(&mut values, *dest, NativeRuntimeValue::Plain(value));
295                }
296                NativeStmt::VariantTagEq { dest, variant, tag } => {
297                    let value = read_plain_value(&values, *variant)?;
298                    let runtime::VmValue::Variant {
299                        tag: actual_tag, ..
300                    } = value
301                    else {
302                        return Err(NativeEvalError::ExpectedVariant { value });
303                    };
304                    write_value(
305                        &mut values,
306                        *dest,
307                        NativeRuntimeValue::Plain(runtime::VmValue::Bool(actual_tag == *tag)),
308                    );
309                }
310                NativeStmt::VariantPayload { dest, variant } => {
311                    let value = read_plain_value(&values, *variant)?;
312                    let runtime::VmValue::Variant {
313                        value: Some(payload),
314                        ..
315                    } = value
316                    else {
317                        return Err(NativeEvalError::ExpectedVariant { value });
318                    };
319                    write_value(&mut values, *dest, NativeRuntimeValue::Plain(*payload));
320                }
321                NativeStmt::ValueEq { dest, left, right } => {
322                    let left = read_plain_value(&values, *left)?;
323                    let right = read_plain_value(&values, *right)?;
324                    write_value(
325                        &mut values,
326                        *dest,
327                        NativeRuntimeValue::Plain(runtime::VmValue::Bool(left == right)),
328                    );
329                }
330                NativeStmt::BoolAnd { dest, left, right } => {
331                    let left = read_plain_value(&values, *left)?;
332                    let right = read_plain_value(&values, *right)?;
333                    write_value(
334                        &mut values,
335                        *dest,
336                        NativeRuntimeValue::Plain(runtime::VmValue::Bool(
337                            matches!(left, runtime::VmValue::Bool(true))
338                                && matches!(right, runtime::VmValue::Bool(true)),
339                        )),
340                    );
341                }
342                NativeStmt::MakeClosure {
343                    dest,
344                    target,
345                    captures,
346                } => {
347                    let captures = captures
348                        .iter()
349                        .map(|id| read_value(&values, *id))
350                        .collect::<NativeEvalResult<Vec<_>>>()?;
351                    write_value(
352                        &mut values,
353                        *dest,
354                        NativeRuntimeValue::Closure(NativeClosureValue {
355                            target: target.clone(),
356                            captures,
357                        }),
358                    );
359                }
360                NativeStmt::ClosureCall { dest, callee, args } => {
361                    let closure = read_closure(&values, *callee)?;
362                    let mut call_args = closure.captures;
363                    call_args.extend(
364                        args.iter()
365                            .map(|id| read_value(&values, *id))
366                            .collect::<NativeEvalResult<Vec<_>>>()?,
367                    );
368                    let function = module
369                        .functions
370                        .iter()
371                        .find(|function| function.name == closure.target)
372                        .ok_or_else(|| NativeEvalError::MissingFunction {
373                            name: closure.target,
374                        })?;
375                    write_value(
376                        &mut values,
377                        *dest,
378                        eval_function_value(module, function, call_args)?,
379                    );
380                }
381            }
382        }
383        match &block.terminator {
384            NativeTerminator::Return(id) => return read_value(&values, *id),
385            NativeTerminator::Jump {
386                target,
387                args: jump_args,
388            } => {
389                args = jump_args
390                    .iter()
391                    .map(|id| read_value(&values, *id))
392                    .collect::<NativeEvalResult<Vec<_>>>()?;
393                current = *target;
394            }
395            NativeTerminator::Branch {
396                cond,
397                then_block,
398                else_block,
399            } => {
400                let cond = read_plain_value(&values, *cond)?;
401                current = if bool_value(typed_ir::PrimitiveOp::BoolNot, &cond)? {
402                    *then_block
403                } else {
404                    *else_block
405                };
406            }
407        }
408    }
409}
410
411fn block_by_id(function: &NativeFunction, id: BlockId) -> NativeEvalResult<&NativeBlock> {
412    function
413        .blocks
414        .iter()
415        .find(|block| block.id == id)
416        .ok_or(NativeEvalError::MissingBlock { id })
417}
418
419fn assign_block_args(
420    values: &mut Vec<Option<NativeRuntimeValue>>,
421    block: &NativeBlock,
422    args: Vec<NativeRuntimeValue>,
423) -> NativeEvalResult<()> {
424    if block.params.len() != args.len() {
425        return Err(NativeEvalError::BlockArgumentMismatch {
426            id: block.id,
427            expected: block.params.len(),
428            actual: args.len(),
429        });
430    }
431    for (param, value) in block.params.iter().copied().zip(args) {
432        write_value(values, param, value);
433    }
434    Ok(())
435}
436
437fn write_value(
438    values: &mut Vec<Option<NativeRuntimeValue>>,
439    id: ValueId,
440    value: NativeRuntimeValue,
441) {
442    if values.len() <= id.0 {
443        values.resize_with(id.0 + 1, || None);
444    }
445    values[id.0] = Some(value);
446}
447
448fn read_value(
449    values: &[Option<NativeRuntimeValue>],
450    id: ValueId,
451) -> NativeEvalResult<NativeRuntimeValue> {
452    values
453        .get(id.0)
454        .and_then(Clone::clone)
455        .ok_or(NativeEvalError::MissingValue { id })
456}
457
458fn read_plain_value(
459    values: &[Option<NativeRuntimeValue>],
460    id: ValueId,
461) -> NativeEvalResult<runtime::VmValue> {
462    into_plain_value(id, read_value(values, id)?)
463}
464
465fn read_closure(
466    values: &[Option<NativeRuntimeValue>],
467    id: ValueId,
468) -> NativeEvalResult<NativeClosureValue> {
469    match read_value(values, id)? {
470        NativeRuntimeValue::Closure(value) => Ok(value),
471        NativeRuntimeValue::Plain(_) => Err(NativeEvalError::ExpectedClosure { id }),
472    }
473}
474
475fn into_plain_value(id: ValueId, value: NativeRuntimeValue) -> NativeEvalResult<runtime::VmValue> {
476    match value {
477        NativeRuntimeValue::Plain(value) => Ok(value),
478        NativeRuntimeValue::Closure(_) => Err(NativeEvalError::ExpectedPlainValue { id }),
479    }
480}
481
482#[derive(Debug, Clone, PartialEq)]
483enum NativeRuntimeValue {
484    Plain(runtime::VmValue),
485    Closure(NativeClosureValue),
486}
487
488#[derive(Debug, Clone, PartialEq)]
489struct NativeClosureValue {
490    target: String,
491    captures: Vec<NativeRuntimeValue>,
492}
493
494fn eval_literal(lit: &NativeLiteral) -> runtime::VmValue {
495    match lit {
496        NativeLiteral::Int(value) => runtime::VmValue::Int(value.clone()),
497        NativeLiteral::Float(value) => runtime::VmValue::Float(value.clone()),
498        NativeLiteral::String(value) => {
499            runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(value))
500        }
501        NativeLiteral::Bool(value) => runtime::VmValue::Bool(*value),
502        NativeLiteral::Unit => runtime::VmValue::Unit,
503    }
504}
505
506pub(crate) fn eval_primitive_for_abi(
507    op: typed_ir::PrimitiveOp,
508    args: &[runtime::VmValue],
509) -> NativeEvalResult<runtime::VmValue> {
510    eval_primitive(op, args)
511}
512
513fn eval_primitive(
514    op: typed_ir::PrimitiveOp,
515    args: &[runtime::VmValue],
516) -> NativeEvalResult<runtime::VmValue> {
517    use typed_ir::PrimitiveOp;
518    match op {
519        PrimitiveOp::BoolNot => {
520            expect_arity(op, args, 1)?;
521            Ok(runtime::VmValue::Bool(!bool_value(op, &args[0])?))
522        }
523        PrimitiveOp::BoolEq => {
524            expect_arity(op, args, 2)?;
525            Ok(runtime::VmValue::Bool(
526                bool_value(op, &args[0])? == bool_value(op, &args[1])?,
527            ))
528        }
529        PrimitiveOp::ListEmpty => {
530            expect_arity(op, args, 1)?;
531            Ok(runtime::VmValue::List(
532                runtime::runtime::list_tree::ListTree::empty(),
533            ))
534        }
535        PrimitiveOp::ListSingleton => {
536            expect_arity(op, args, 1)?;
537            Ok(runtime::VmValue::List(
538                runtime::runtime::list_tree::ListTree::singleton(std::rc::Rc::new(args[0].clone())),
539            ))
540        }
541        PrimitiveOp::ListMerge => {
542            expect_arity(op, args, 2)?;
543            Ok(runtime::VmValue::List(
544                runtime::runtime::list_tree::ListTree::concat(
545                    list_value(op, &args[0])?.clone(),
546                    list_value(op, &args[1])?.clone(),
547                ),
548            ))
549        }
550        PrimitiveOp::ListLen => {
551            expect_arity(op, args, 1)?;
552            Ok(runtime::VmValue::Int(
553                list_value(op, &args[0])?.len().to_string(),
554            ))
555        }
556        PrimitiveOp::ListIndex => {
557            expect_arity(op, args, 2)?;
558            let index = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
559                NativeEvalError::PrimitiveTypeMismatch {
560                    op,
561                    value: args[1].clone(),
562                }
563            })?;
564            let value = list_value(op, &args[0])?.index(index).ok_or_else(|| {
565                NativeEvalError::PrimitiveTypeMismatch {
566                    op,
567                    value: args[0].clone(),
568                }
569            })?;
570            Ok(value.as_ref().clone())
571        }
572        PrimitiveOp::ListIndexRangeRaw => {
573            expect_arity(op, args, 3)?;
574            let start = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
575                NativeEvalError::PrimitiveTypeMismatch {
576                    op,
577                    value: args[1].clone(),
578                }
579            })?;
580            let end = usize::try_from(int_value(op, &args[2])?).map_err(|_| {
581                NativeEvalError::PrimitiveTypeMismatch {
582                    op,
583                    value: args[2].clone(),
584                }
585            })?;
586            let value = list_value(op, &args[0])?
587                .index_range(start, end)
588                .ok_or_else(|| NativeEvalError::PrimitiveTypeMismatch {
589                    op,
590                    value: args[0].clone(),
591                })?;
592            Ok(runtime::VmValue::List(value))
593        }
594        PrimitiveOp::ListIndexRange => {
595            expect_arity(op, args, 2)?;
596            let list = list_value(op, &args[0])?;
597            let (start, end) = normalized_int_range_value(op, &args[1], list.len())?;
598            let value = list.index_range(start, end).ok_or_else(|| {
599                NativeEvalError::PrimitiveTypeMismatch {
600                    op,
601                    value: args[0].clone(),
602                }
603            })?;
604            Ok(runtime::VmValue::List(value))
605        }
606        PrimitiveOp::ListSplice => {
607            expect_arity(op, args, 3)?;
608            let list = list_value(op, &args[0])?;
609            let (start, end) = normalized_int_range_value(op, &args[1], list.len())?;
610            let insert = list_value(op, &args[2])?;
611            let value = list.splice(start, end, insert.clone()).ok_or_else(|| {
612                NativeEvalError::PrimitiveTypeMismatch {
613                    op,
614                    value: args[0].clone(),
615                }
616            })?;
617            Ok(runtime::VmValue::List(value))
618        }
619        PrimitiveOp::ListSpliceRaw => {
620            expect_arity(op, args, 4)?;
621            let start = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
622                NativeEvalError::PrimitiveTypeMismatch {
623                    op,
624                    value: args[1].clone(),
625                }
626            })?;
627            let end = usize::try_from(int_value(op, &args[2])?).map_err(|_| {
628                NativeEvalError::PrimitiveTypeMismatch {
629                    op,
630                    value: args[2].clone(),
631                }
632            })?;
633            let list = list_value(op, &args[0])?;
634            let insert = list_value(op, &args[3])?;
635            let value = list.splice(start, end, insert.clone()).ok_or_else(|| {
636                NativeEvalError::PrimitiveTypeMismatch {
637                    op,
638                    value: args[0].clone(),
639                }
640            })?;
641            Ok(runtime::VmValue::List(value))
642        }
643        PrimitiveOp::ListViewRaw => {
644            expect_arity(op, args, 1)?;
645            let value = match list_value(op, &args[0])?.view() {
646                runtime::runtime::list_tree::ListView::Empty => runtime::VmValue::Variant {
647                    tag: typed_ir::Name("empty".to_string()),
648                    value: None,
649                },
650                runtime::runtime::list_tree::ListView::Leaf(single) => runtime::VmValue::Variant {
651                    tag: typed_ir::Name("leaf".to_string()),
652                    value: Some(Box::new((*single).clone())),
653                },
654                runtime::runtime::list_tree::ListView::Node { left, right, .. } => {
655                    runtime::VmValue::Variant {
656                        tag: typed_ir::Name("node".to_string()),
657                        value: Some(Box::new(runtime::VmValue::Tuple(vec![
658                            runtime::VmValue::List(left),
659                            runtime::VmValue::List(right),
660                        ]))),
661                    }
662                }
663            };
664            Ok(value)
665        }
666        PrimitiveOp::IntAdd => int_bin_op(op, args, |left, right| left + right),
667        PrimitiveOp::IntSub => int_bin_op(op, args, |left, right| left - right),
668        PrimitiveOp::IntMul => int_bin_op(op, args, |left, right| left * right),
669        PrimitiveOp::IntDiv => int_bin_op(op, args, |left, right| left / right),
670        PrimitiveOp::IntEq => int_cmp_op(op, args, |left, right| left == right),
671        PrimitiveOp::IntLt => int_cmp_op(op, args, |left, right| left < right),
672        PrimitiveOp::IntLe => int_cmp_op(op, args, |left, right| left <= right),
673        PrimitiveOp::IntGt => int_cmp_op(op, args, |left, right| left > right),
674        PrimitiveOp::IntGe => int_cmp_op(op, args, |left, right| left >= right),
675        PrimitiveOp::FloatAdd => float_bin_op(op, args, |left, right| left + right),
676        PrimitiveOp::FloatSub => float_bin_op(op, args, |left, right| left - right),
677        PrimitiveOp::FloatMul => float_bin_op(op, args, |left, right| left * right),
678        PrimitiveOp::FloatDiv => float_bin_op(op, args, |left, right| left / right),
679        PrimitiveOp::FloatEq => float_cmp_op(op, args, |left, right| left == right),
680        PrimitiveOp::FloatLt => float_cmp_op(op, args, |left, right| left < right),
681        PrimitiveOp::FloatLe => float_cmp_op(op, args, |left, right| left <= right),
682        PrimitiveOp::FloatGt => float_cmp_op(op, args, |left, right| left > right),
683        PrimitiveOp::FloatGe => float_cmp_op(op, args, |left, right| left >= right),
684        PrimitiveOp::StringConcat => {
685            expect_arity(op, args, 2)?;
686            let left = string_value(op, &args[0])?;
687            let right = string_value(op, &args[1])?;
688            Ok(runtime::VmValue::String(
689                runtime::runtime::string_tree::StringTree::concat(left.clone(), right.clone()),
690            ))
691        }
692        PrimitiveOp::StringEq => {
693            expect_arity(op, args, 2)?;
694            Ok(runtime::VmValue::Bool(
695                string_value(op, &args[0])?.to_flat_string()
696                    == string_value(op, &args[1])?.to_flat_string(),
697            ))
698        }
699        PrimitiveOp::StringLen => {
700            expect_arity(op, args, 1)?;
701            Ok(runtime::VmValue::Int(
702                string_value(op, &args[0])?.len().to_string(),
703            ))
704        }
705        PrimitiveOp::StringIndex => {
706            expect_arity(op, args, 2)?;
707            let index = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
708                NativeEvalError::PrimitiveTypeMismatch {
709                    op,
710                    value: args[1].clone(),
711                }
712            })?;
713            let value = string_value(op, &args[0])?.index(index).ok_or_else(|| {
714                NativeEvalError::PrimitiveTypeMismatch {
715                    op,
716                    value: args[0].clone(),
717                }
718            })?;
719            Ok(value_from_string(&value.to_string()))
720        }
721        PrimitiveOp::StringIndexRange => {
722            expect_arity(op, args, 2)?;
723            let text = string_value(op, &args[0])?;
724            let (start, end) = normalized_int_range_value(op, &args[1], text.len())?;
725            let value = text.index_range(start, end).ok_or_else(|| {
726                NativeEvalError::PrimitiveTypeMismatch {
727                    op,
728                    value: args[0].clone(),
729                }
730            })?;
731            Ok(runtime::VmValue::String(value))
732        }
733        PrimitiveOp::StringSplice => {
734            expect_arity(op, args, 3)?;
735            let text = string_value(op, &args[0])?;
736            let (start, end) = normalized_int_range_value(op, &args[1], text.len())?;
737            let insert = string_value(op, &args[2])?;
738            let value = text.splice(start, end, insert.clone()).ok_or_else(|| {
739                NativeEvalError::PrimitiveTypeMismatch {
740                    op,
741                    value: args[0].clone(),
742                }
743            })?;
744            Ok(runtime::VmValue::String(value))
745        }
746        PrimitiveOp::StringIndexRangeRaw => {
747            expect_arity(op, args, 3)?;
748            let start = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
749                NativeEvalError::PrimitiveTypeMismatch {
750                    op,
751                    value: args[1].clone(),
752                }
753            })?;
754            let end = usize::try_from(int_value(op, &args[2])?).map_err(|_| {
755                NativeEvalError::PrimitiveTypeMismatch {
756                    op,
757                    value: args[2].clone(),
758                }
759            })?;
760            let value = string_value(op, &args[0])?
761                .index_range(start, end)
762                .ok_or_else(|| NativeEvalError::PrimitiveTypeMismatch {
763                    op,
764                    value: args[0].clone(),
765                })?;
766            Ok(runtime::VmValue::String(value))
767        }
768        PrimitiveOp::StringSpliceRaw => {
769            expect_arity(op, args, 4)?;
770            let start = usize::try_from(int_value(op, &args[1])?).map_err(|_| {
771                NativeEvalError::PrimitiveTypeMismatch {
772                    op,
773                    value: args[1].clone(),
774                }
775            })?;
776            let end = usize::try_from(int_value(op, &args[2])?).map_err(|_| {
777                NativeEvalError::PrimitiveTypeMismatch {
778                    op,
779                    value: args[2].clone(),
780                }
781            })?;
782            let insert = string_value(op, &args[3])?;
783            let value = string_value(op, &args[0])?
784                .splice(start, end, insert.clone())
785                .ok_or_else(|| NativeEvalError::PrimitiveTypeMismatch {
786                    op,
787                    value: args[0].clone(),
788                })?;
789            Ok(runtime::VmValue::String(value))
790        }
791        PrimitiveOp::IntToString => {
792            expect_arity(op, args, 1)?;
793            Ok(value_from_string(&int_value(op, &args[0])?.to_string()))
794        }
795        PrimitiveOp::IntToHex => {
796            expect_arity(op, args, 1)?;
797            Ok(value_from_string(&format!(
798                "{:x}",
799                int_value(op, &args[0])?
800            )))
801        }
802        PrimitiveOp::IntToUpperHex => {
803            expect_arity(op, args, 1)?;
804            Ok(value_from_string(&format!(
805                "{:X}",
806                int_value(op, &args[0])?
807            )))
808        }
809        PrimitiveOp::FloatToString => {
810            expect_arity(op, args, 1)?;
811            Ok(value_from_string(&format_float_value(float_value(
812                op, &args[0],
813            )?)))
814        }
815        PrimitiveOp::BoolToString => {
816            expect_arity(op, args, 1)?;
817            Ok(value_from_string(if bool_value(op, &args[0])? {
818                "true"
819            } else {
820                "false"
821            }))
822        }
823        PrimitiveOp::StringToBytes
824        | PrimitiveOp::BytesLen
825        | PrimitiveOp::BytesEq
826        | PrimitiveOp::BytesConcat
827        | PrimitiveOp::BytesIndex
828        | PrimitiveOp::BytesIndexRange
829        | PrimitiveOp::BytesToUtf8Raw
830        | PrimitiveOp::BytesToPath
831        | PrimitiveOp::PathToBytes => Err(NativeEvalError::UnsupportedPrimitive { op }),
832    }
833}
834
835fn int_bin_op(
836    op: typed_ir::PrimitiveOp,
837    args: &[runtime::VmValue],
838    f: impl FnOnce(i64, i64) -> i64,
839) -> NativeEvalResult<runtime::VmValue> {
840    expect_arity(op, args, 2)?;
841    Ok(runtime::VmValue::Int(
842        f(int_value(op, &args[0])?, int_value(op, &args[1])?).to_string(),
843    ))
844}
845
846fn int_cmp_op(
847    op: typed_ir::PrimitiveOp,
848    args: &[runtime::VmValue],
849    f: impl FnOnce(i64, i64) -> bool,
850) -> NativeEvalResult<runtime::VmValue> {
851    expect_arity(op, args, 2)?;
852    Ok(runtime::VmValue::Bool(f(
853        int_value(op, &args[0])?,
854        int_value(op, &args[1])?,
855    )))
856}
857
858fn float_bin_op(
859    op: typed_ir::PrimitiveOp,
860    args: &[runtime::VmValue],
861    f: impl FnOnce(f64, f64) -> f64,
862) -> NativeEvalResult<runtime::VmValue> {
863    expect_arity(op, args, 2)?;
864    Ok(runtime::VmValue::Float(format_float_value(f(
865        float_value(op, &args[0])?,
866        float_value(op, &args[1])?,
867    ))))
868}
869
870fn float_cmp_op(
871    op: typed_ir::PrimitiveOp,
872    args: &[runtime::VmValue],
873    f: impl FnOnce(f64, f64) -> bool,
874) -> NativeEvalResult<runtime::VmValue> {
875    expect_arity(op, args, 2)?;
876    Ok(runtime::VmValue::Bool(f(
877        float_value(op, &args[0])?,
878        float_value(op, &args[1])?,
879    )))
880}
881
882fn expect_arity(
883    op: typed_ir::PrimitiveOp,
884    args: &[runtime::VmValue],
885    expected: usize,
886) -> NativeEvalResult<()> {
887    if args.len() == expected {
888        Ok(())
889    } else {
890        Err(NativeEvalError::InvalidPrimitiveArity {
891            op,
892            expected,
893            actual: args.len(),
894        })
895    }
896}
897
898fn int_value(op: typed_ir::PrimitiveOp, value: &runtime::VmValue) -> NativeEvalResult<i64> {
899    match value {
900        runtime::VmValue::Int(value) => {
901            value
902                .parse()
903                .map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
904                    op,
905                    value: value_from_string(value),
906                })
907        }
908        value => Err(NativeEvalError::PrimitiveTypeMismatch {
909            op,
910            value: value.clone(),
911        }),
912    }
913}
914
915fn float_value(op: typed_ir::PrimitiveOp, value: &runtime::VmValue) -> NativeEvalResult<f64> {
916    match value {
917        runtime::VmValue::Float(value) => {
918            value
919                .parse()
920                .map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
921                    op,
922                    value: runtime::VmValue::Float(value.clone()),
923                })
924        }
925        value => Err(NativeEvalError::PrimitiveTypeMismatch {
926            op,
927            value: value.clone(),
928        }),
929    }
930}
931
932fn bool_value(op: typed_ir::PrimitiveOp, value: &runtime::VmValue) -> NativeEvalResult<bool> {
933    match value {
934        runtime::VmValue::Bool(value) => Ok(*value),
935        value => Err(NativeEvalError::PrimitiveTypeMismatch {
936            op,
937            value: value.clone(),
938        }),
939    }
940}
941
942fn string_value(
943    op: typed_ir::PrimitiveOp,
944    value: &runtime::VmValue,
945) -> NativeEvalResult<&runtime::runtime::string_tree::StringTree> {
946    match value {
947        runtime::VmValue::String(value) => Ok(value),
948        value => Err(NativeEvalError::PrimitiveTypeMismatch {
949            op,
950            value: value.clone(),
951        }),
952    }
953}
954
955fn list_value(
956    op: typed_ir::PrimitiveOp,
957    value: &runtime::VmValue,
958) -> NativeEvalResult<&runtime::runtime::list_tree::ListTree<std::rc::Rc<runtime::VmValue>>> {
959    match value {
960        runtime::VmValue::List(value) => Ok(value),
961        value => Err(NativeEvalError::PrimitiveTypeMismatch {
962            op,
963            value: value.clone(),
964        }),
965    }
966}
967
968fn normalized_int_range_value(
969    op: typed_ir::PrimitiveOp,
970    value: &runtime::VmValue,
971    len: usize,
972) -> NativeEvalResult<(usize, usize)> {
973    let original = value.clone();
974    let runtime::VmValue::Variant { tag, value } = value else {
975        return Err(NativeEvalError::PrimitiveTypeMismatch {
976            op,
977            value: original,
978        });
979    };
980    if tag.0 != "within" {
981        return Err(NativeEvalError::PrimitiveTypeMismatch {
982            op,
983            value: original,
984        });
985    }
986    let Some(payload) = value.as_ref() else {
987        return Err(NativeEvalError::PrimitiveTypeMismatch {
988            op,
989            value: runtime::VmValue::Variant {
990                tag: tag.clone(),
991                value: value.clone(),
992            },
993        });
994    };
995    let runtime::VmValue::Tuple(items) = payload.as_ref() else {
996        return Err(NativeEvalError::PrimitiveTypeMismatch {
997            op,
998            value: payload.as_ref().clone(),
999        });
1000    };
1001    let [start, end] = items.as_slice() else {
1002        return Err(NativeEvalError::PrimitiveTypeMismatch {
1003            op,
1004            value: runtime::VmValue::Tuple(items.clone()),
1005        });
1006    };
1007    let start = normalized_start_bound_value(op, start)?;
1008    let end = normalized_end_bound_value(op, end, len)?;
1009    if start <= end && end <= len {
1010        Ok((start, end))
1011    } else {
1012        Err(NativeEvalError::PrimitiveTypeMismatch {
1013            op,
1014            value: payload.as_ref().clone(),
1015        })
1016    }
1017}
1018
1019fn normalized_start_bound_value(
1020    op: typed_ir::PrimitiveOp,
1021    value: &runtime::VmValue,
1022) -> NativeEvalResult<usize> {
1023    let original = value.clone();
1024    let runtime::VmValue::Variant { tag, value } = value else {
1025        return Err(NativeEvalError::PrimitiveTypeMismatch {
1026            op,
1027            value: original,
1028        });
1029    };
1030    match tag.0.as_str() {
1031        "unbounded" => Ok(0),
1032        "included" => {
1033            let value = int_variant_payload(op, value)?;
1034            usize::try_from(value).map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
1035                op,
1036                value: value_from_string(&value.to_string()),
1037            })
1038        }
1039        "excluded" => {
1040            let value = int_variant_payload(op, value)?;
1041            usize::try_from(value + 1).map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
1042                op,
1043                value: value_from_string(&value.to_string()),
1044            })
1045        }
1046        _ => Err(NativeEvalError::PrimitiveTypeMismatch {
1047            op,
1048            value: original,
1049        }),
1050    }
1051}
1052
1053fn normalized_end_bound_value(
1054    op: typed_ir::PrimitiveOp,
1055    value: &runtime::VmValue,
1056    len: usize,
1057) -> NativeEvalResult<usize> {
1058    let original = value.clone();
1059    let runtime::VmValue::Variant { tag, value } = value else {
1060        return Err(NativeEvalError::PrimitiveTypeMismatch {
1061            op,
1062            value: original,
1063        });
1064    };
1065    match tag.0.as_str() {
1066        "unbounded" => Ok(len),
1067        "included" => {
1068            let value = int_variant_payload(op, value)?;
1069            usize::try_from(value + 1).map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
1070                op,
1071                value: value_from_string(&value.to_string()),
1072            })
1073        }
1074        "excluded" => {
1075            let value = int_variant_payload(op, value)?;
1076            usize::try_from(value).map_err(|_| NativeEvalError::PrimitiveTypeMismatch {
1077                op,
1078                value: value_from_string(&value.to_string()),
1079            })
1080        }
1081        _ => Err(NativeEvalError::PrimitiveTypeMismatch {
1082            op,
1083            value: original,
1084        }),
1085    }
1086}
1087
1088fn int_variant_payload(
1089    op: typed_ir::PrimitiveOp,
1090    value: &Option<Box<runtime::VmValue>>,
1091) -> NativeEvalResult<i64> {
1092    let Some(value) = value.as_ref() else {
1093        return Err(NativeEvalError::PrimitiveTypeMismatch {
1094            op,
1095            value: runtime::VmValue::Unit,
1096        });
1097    };
1098    int_value(op, value)
1099}
1100
1101fn format_float_value(value: f64) -> String {
1102    let mut rendered = value.to_string();
1103    if !rendered.contains('.') && !rendered.contains('e') && !rendered.contains('E') {
1104        rendered.push_str(".0");
1105    }
1106    rendered
1107}
1108
1109fn value_from_string(value: &str) -> runtime::VmValue {
1110    runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(value))
1111}