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}