1use super::{
10 Cache, Closure, Environment, ErrorKind, ImportResolver, VirtualMachine,
11 cache::lazy::Thunk,
12 contract_eq::contract_eq,
13 merge::{self, MergeMode, split},
14 stack::{EqItem, Op1ContItem, Op2FirstContItem, Op2SecondContItem, PrimopAppInfo, StrAccItem},
15 subst,
16 value::{
17 Array, ArrayData, Container, EnumVariantData, NickelValue, TypeData, ValueContentRef,
18 ValueContentRefMut,
19 },
20};
21
22#[cfg(feature = "nix-experimental")]
23use crate::nix_ffi;
24
25use crate::{
26 cache::InputFormat,
27 closurize::Closurize,
28 combine::Combine,
29 error::{EvalErrorKind, IllegalPolymorphicTailAction, Warning},
30 identifier::LocIdent,
31 label::{Polarity, TypeVarData, ty_path},
32 metrics::increment,
33 mk_app, mk_fun, mk_record,
34 position::PosIdx,
35 serialize::{self, ExportFormat, yaml::Listify},
36 stdlib::internals,
37 term::{make as mk_term, record::*, string::NickelString, *},
38};
39
40use base64::Engine;
41use nickel_lang_parser::utils::parse_number_sci;
42
43#[cfg(feature = "metrics")]
44use crate::pretty::PrettyPrintCap;
45
46use malachite::{
47 Integer,
48 base::{
49 num::{arithmetic::traits::Pow, basic::traits::Zero, conversion::traits::RoundingFrom},
50 rounding_modes::RoundingMode,
51 },
52};
53
54use md5::digest::Digest;
55use simple_counter::*;
56use unicode_segmentation::UnicodeSegmentation;
57
58use std::{convert::TryFrom, iter::Extend, rc::Rc};
59
60generate_counter!(FreshVariableCounter, usize);
61
62enum EqResult {
73 Bool(bool),
74 Eqs(NickelValue, NickelValue, Vec<(Closure, Closure)>),
75}
76
77struct Op1EvalData {
79 op: UnaryOp,
81 arg: Closure,
83 orig_pos_arg: PosIdx,
86 pos_op: PosIdx,
88}
89
90struct Op2EvalData {
92 op: BinaryOp,
94 arg1: Closure,
96 arg2: Closure,
98 orig_pos_arg1: PosIdx,
101 orig_pos_arg2: PosIdx,
104 pos_op: PosIdx,
106}
107
108struct OpNEvalData {
109 op: NAryOp,
111 args: Vec<(Closure, PosIdx)>,
114 pos_op: PosIdx,
116}
117
118static ENUM_FORMAT: &str = "[| 'Json, 'Toml, 'Yaml, 'YamlDocuments |]";
121
122impl<'ctxt, R: ImportResolver, C: Cache> VirtualMachine<'ctxt, R, C> {
123 pub fn continue_op(&mut self, closure: Closure) -> Result<Closure, ErrorKind> {
128 if let Some(op1_cont) = self.stack.pop_op1_cont() {
129 self.call_stack
130 .truncate(op1_cont.app_info.call_stack_size as usize);
131
132 self.eval_op1(Op1EvalData {
133 op: op1_cont.op,
134 arg: closure,
135 orig_pos_arg: op1_cont.orig_pos_arg,
136 pos_op: op1_cont.app_info.pos_idx,
137 })
138 } else if let Some(op2_fst_cont) = self.stack.pop_op2_first_cont() {
139 self.call_stack
140 .truncate(op2_fst_cont.app_info.call_stack_size as usize);
141
142 self.stack.push_op2_second_cont(Op2SecondContItem {
143 op: op2_fst_cont.op,
144 app_info: op2_fst_cont.app_info,
145 arg1_evaled: closure,
146 orig_pos_arg1: op2_fst_cont.orig_pos_arg1,
147 orig_pos_arg2: op2_fst_cont.arg2.value.pos_idx(),
148 });
149
150 Ok(op2_fst_cont.arg2)
151 } else if let Some(op2_snd_cont) = self.stack.pop_op2_second_cont() {
152 self.call_stack
153 .truncate(op2_snd_cont.app_info.call_stack_size as usize);
154
155 self.eval_op2(Op2EvalData {
156 op: op2_snd_cont.op,
157 arg1: op2_snd_cont.arg1_evaled,
158 arg2: closure,
159 orig_pos_arg1: op2_snd_cont.orig_pos_arg1,
160 orig_pos_arg2: op2_snd_cont.orig_pos_arg2,
161 pos_op: op2_snd_cont.app_info.pos_idx,
162 })
163 } else if let Some(mut opn_cont) = self.stack.pop_opn_cont() {
164 self.call_stack
165 .truncate(opn_cont.app_info.call_stack_size as usize);
166
167 opn_cont.evaluated.push((closure, opn_cont.current_pos_idx));
168
169 if let Some(next) = opn_cont.pending.pop() {
170 opn_cont.current_pos_idx = next.value.pos_idx();
171 self.stack.push_opn_cont(opn_cont);
172
173 Ok(next)
174 } else {
175 self.eval_opn(OpNEvalData {
176 op: opn_cont.op,
177 args: opn_cont.evaluated,
178 pos_op: opn_cont.app_info.pos_idx,
179 })
180 }
181 } else {
182 panic!("unexpected state of eval stack in continue_op()");
183 }
184 }
185
186 fn eval_op1(&mut self, eval_data: Op1EvalData) -> Result<Closure, ErrorKind> {
191 let Op1EvalData {
192 orig_pos_arg,
193 arg: Closure { value, env },
194 pos_op,
195 op,
196 } = eval_data;
197
198 increment!(format!("primop:{op}"));
199
200 let pos = value.pos_idx();
201 let pos_op_inh = eval_data.pos_op.to_inherited();
202
203 macro_rules! mk_type_error {
204 (op_name=$op_name:expr, $expected:expr) => {
205 mk_type_error!(op_name = $op_name, $expected, value = value)
206 };
207 (op_name=$op_name:expr, $expected:expr, value=$value:expr) => {
208 Err(Box::new(EvalErrorKind::UnaryPrimopTypeError {
209 primop: String::from($op_name),
210 expected: String::from($expected),
211 pos_arg: orig_pos_arg,
212 arg_evaluated: $value,
213 }))
214 };
215 ($expected:expr) => {
216 mk_type_error!(op_name = op.to_string(), $expected)
217 };
218 ($expected:expr, value=$value:expr) => {
219 mk_type_error!(op_name = op.to_string(), $expected, value = $value)
220 };
221 ($expected:expr, value=$value:expr) => {};
222 ($expected:expr, $arg_number:expr) => {
223 mk_type_error!($expected, $arg_number, value = value)
224 };
225 ($expected:expr, $arg_number:expr, value=$value:expr) => {
226 Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
227 primop: op.to_string(),
228 expected: String::from($expected),
229 arg_number: $arg_number,
230 pos_arg: orig_pos_arg,
231 arg_evaluated: $value,
232 pos_op,
233 }))
234 };
235 }
236
237 match op {
238 UnaryOp::IfThenElse => {
239 if let Some(b) = value.as_bool() {
240 let (fst, ..) = self
241 .stack
242 .pop_arg(&self.context.cache)
243 .expect("if-then-else primop isn't saturated");
244 let (snd, ..) = self
245 .stack
246 .pop_arg(&self.context.cache)
247 .expect("if-then-else primop isn't saturated");
248
249 Ok(if b { fst } else { snd })
250 } else {
251 Err(Box::new(EvalErrorKind::TypeError {
253 expected: String::from("Bool"),
254 message: String::from(
255 "the condition in an if expression must have type Bool",
256 ),
257 orig_pos: orig_pos_arg,
258 term: value,
259 }))
260 }
261 }
262 UnaryOp::Typeof => Ok(NickelValue::enum_tag(type_tag(&value), pos_op_inh).into()),
263 UnaryOp::Cast => {
264 Ok(NickelValue::enum_variant(type_tag(&value), Some(value), pos_op_inh).into())
265 }
266 UnaryOp::BoolAnd =>
267 {
269 if let Some((next, ..)) = self.stack.pop_arg(&self.context.cache) {
270 match value.as_bool() {
271 Some(true) => Ok(next),
272 Some(false) => Ok(value.with_pos_idx(pos_op_inh).into()),
278 _ => mk_type_error!("Bool", 1),
279 }
280 } else {
281 Err(Box::new(EvalErrorKind::NotEnoughArgs(
282 2,
283 String::from("&&"),
284 pos_op,
285 )))
286 }
287 }
288 UnaryOp::BoolOr => {
289 if let Some((next, ..)) = self.stack.pop_arg(&self.context.cache) {
290 match value.as_bool() {
291 Some(true) => Ok(value.with_pos_idx(pos_op_inh).into()),
292 Some(false) => Ok(next),
298 _ => mk_type_error!("Bool", 1),
299 }
300 } else {
301 Err(Box::new(EvalErrorKind::NotEnoughArgs(
302 2,
303 String::from("||"),
304 pos_op,
305 )))
306 }
307 }
308 UnaryOp::BoolNot => {
309 if let Some(b) = value.as_bool() {
310 Ok(NickelValue::bool_value_posless(!b)
311 .with_pos_idx(pos_op_inh)
312 .into())
313 } else {
314 mk_type_error!("Bool")
315 }
316 }
317 UnaryOp::Blame => {
318 let Some(label) = value.as_label() else {
319 return mk_type_error!("Label");
320 };
321
322 Err(Box::new(EvalErrorKind::BlameError {
323 evaluated_arg: label.get_evaluated_arg(&self.context.cache),
324 label: label.clone(),
325 }))
326 }
327 UnaryOp::EnumEmbed(_id) => {
328 if value.as_enum_variant().is_some() {
329 Ok(value.with_pos_idx(pos_op_inh).into())
330 } else {
331 mk_type_error!("Enum")
332 }
333 }
334 UnaryOp::TagsOnlyMatch { has_default } => {
335 let (cases_closure, ..) = self
336 .stack
337 .pop_arg(&self.context.cache)
338 .expect("missing arg for match");
339
340 let default = if has_default {
341 Some(
342 self.stack
343 .pop_arg(&self.context.cache)
344 .map(|(clos, ..)| clos)
345 .expect("missing default case for match"),
346 )
347 } else {
348 None
349 };
350
351 if let Some(enum_variant) = value.as_enum_variant()
352 && enum_variant.arg.is_none()
353 {
354 let Closure {
355 value: cases_val,
356 env: cases_env,
357 } = cases_closure;
358
359 let ValueContentRef::Record(container) = cases_val.content_ref() else {
360 panic!("invalid argument for %match%")
361 };
362
363 container
364 .get(enum_variant.tag)
365 .map(|field| Closure {
366 value: field
371 .value
372 .as_ref()
373 .cloned()
374 .expect("%match% cases must have a definition"),
375 env: cases_env,
376 })
377 .or(default)
378 .ok_or_else(|| {
379 Box::new(EvalErrorKind::NonExhaustiveEnumMatch {
380 expected: container.field_names(RecordOpKind::IgnoreEmptyOpt),
381 found: NickelValue::enum_variant_posless(enum_variant.tag, None)
382 .with_pos_idx(pos),
383 pos: pos_op_inh,
384 })
385 })
386 } else if let Some(clos) = default {
387 Ok(clos)
388 } else {
389 mk_type_error!("Enum", 2)
390 }
391 }
392 UnaryOp::LabelFlipPol => {
393 let Some(label) = value.as_label() else {
394 return mk_type_error!("Label");
395 };
396
397 let mut label = label.clone();
398 label.polarity = label.polarity.flip();
399 Ok(NickelValue::label(label, pos_op_inh).into())
400 }
401 UnaryOp::LabelPol => {
402 if let Some(label) = value.as_label() {
403 Ok(NickelValue::from(label.polarity)
404 .with_pos_idx(pos_op_inh)
405 .into())
406 } else {
407 mk_type_error!("Label")
408 }
409 }
410 UnaryOp::LabelGoDom => {
411 let Some(label) = value.as_label() else {
412 return mk_type_error!("Label");
413 };
414
415 let mut label = label.clone();
416 label.path.push(ty_path::Elem::Domain);
417 Ok(NickelValue::label(label, pos_op_inh).into())
418 }
419 UnaryOp::LabelGoCodom => {
420 let Some(label) = value.as_label() else {
421 return mk_type_error!("Label");
422 };
423
424 let mut label = label.clone();
425 label.path.push(ty_path::Elem::Codomain);
426 Ok(NickelValue::label(label, pos_op_inh).into())
427 }
428 UnaryOp::LabelGoArray => {
429 let Some(label) = value.as_label() else {
430 return mk_type_error!("Label");
431 };
432
433 let mut label = label.clone();
434 label.path.push(ty_path::Elem::Array);
435 Ok(NickelValue::label(label, pos_op_inh).into())
436 }
437 UnaryOp::LabelGoDict => {
438 let Some(label) = value.as_label() else {
439 return mk_type_error!("Label");
440 };
441
442 let mut label = label.clone();
443 label.path.push(ty_path::Elem::Dict);
444 Ok(NickelValue::label(label, pos_op_inh).into())
445 }
446 UnaryOp::RecordAccess(id) => {
447 match value.as_record() {
448 Some(Container::Alloc(record)) => {
449 match record
455 .get_value_with_ctrs(&id)
456 .map_err(|err| err.into_eval_err(pos, pos_op))?
457 {
458 Some(value) => {
459 self.call_stack
460 .enter_field(id, pos, value.pos_idx(), pos_op);
461 Ok(Closure { value, env })
462 }
463 None => match record.sealed_tail.as_ref() {
464 Some(t) if t.has_field(&id.ident()) => {
465 Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
466 action: IllegalPolymorphicTailAction::FieldAccess {
467 field: id.to_string(),
468 },
469 evaluated_arg: t
470 .label
471 .get_evaluated_arg(&self.context.cache),
472 label: t.label.clone(),
473 }))
474 }
475 _ => Err(Box::new(EvalErrorKind::FieldMissing {
476 id,
477 field_names: record.field_names(RecordOpKind::IgnoreEmptyOpt),
478 operator: String::from("(.)"),
479 pos_record: pos,
480 pos_op,
481 })),
482 }, }
484 }
485 Some(Container::Empty) => Err(Box::new(EvalErrorKind::FieldMissing {
486 id,
487 field_names: Vec::new(),
488 operator: String::from("(.)"),
489 pos_record: pos,
490 pos_op,
491 })),
492 None =>
493 {
495 Err(Box::new(EvalErrorKind::TypeError {
496 expected: String::from("Record"),
497 message: String::from("field access only makes sense for records"),
498 orig_pos: orig_pos_arg,
499 term: value,
500 }))
501 }
502 }
503 }
504 UnaryOp::RecordFields(op_kind) => {
505 if let Some(container) = value.as_record() {
506 let fields_as_terms: Array = container
507 .field_names(op_kind)
508 .into_iter()
509 .map(|id| {
510 NickelValue::string(id.label(), self.context.pos_table.push(id.pos))
511 })
512 .collect();
513
514 Ok(Closure {
515 value: NickelValue::array(fields_as_terms, Vec::new(), pos_op_inh),
516 env,
517 })
518 } else {
519 mk_type_error!("Record")
520 }
521 }
522 UnaryOp::RecordValues => {
523 if let Some(container) = value.as_record() {
524 let mut values = container
525 .into_opt()
526 .map(|r| r.iter_without_opts())
527 .into_iter()
528 .flatten()
529 .collect::<Result<Vec<_>, _>>()
530 .map_err(|miss_def_err| miss_def_err.into_eval_err(pos, pos_op))?;
531
532 values.sort_by_key(|(id, _)| *id);
533 let terms = values.into_iter().map(|(_, value)| value).collect();
534
535 Ok(Closure {
536 value: NickelValue::array(terms, Vec::new(), pos_op_inh),
539 env,
540 })
541 } else {
542 mk_type_error!("Record")
543 }
544 }
545 UnaryOp::ArrayMap => {
546 let (f, _) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
547 Box::new(EvalErrorKind::NotEnoughArgs(
548 2,
549 String::from("array/map"),
550 pos_op,
551 ))
552 })?;
553
554 let Some(cont) = value.as_array() else {
555 return mk_type_error!("Array");
556 };
557
558 let Container::Alloc(array_data) = cont else {
559 return Ok(value.into());
560 };
561
562 let f_as_var = f.value.closurize(&mut self.context.cache, f.env);
563
564 let ts = array_data
568 .array
569 .iter()
570 .cloned()
571 .map(|t| {
572 let t_with_ctrs = RuntimeContract::apply_all(
573 t,
574 array_data.pending_contracts.iter().cloned(),
575 pos,
576 );
577
578 NickelValue::term(Term::app(f_as_var.clone(), t_with_ctrs), pos_op_inh)
579 .closurize(&mut self.context.cache, env.clone())
580 })
581 .collect();
582
583 Ok(NickelValue::array(ts, Vec::new(), pos_op_inh).into())
584 }
585 UnaryOp::ArrayGen => {
586 let (f, _) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
587 Box::new(EvalErrorKind::NotEnoughArgs(
588 2,
589 String::from("array/generate"),
590 pos_op,
591 ))
592 })?;
593
594 let Some(n) = value.as_number() else {
595 return mk_type_error!("Number");
596 };
597
598 if n < &Number::ZERO {
599 return Err(Box::new(EvalErrorKind::Other(
600 format!(
601 "array/generate expects its first argument to be a positive number, got {n}"
602 ),
603 pos_op,
604 )));
605 }
606
607 let Ok(n_int) = u32::try_from(n) else {
608 return Err(Box::new(EvalErrorKind::Other(
609 format!(
610 "array/generate expects its first argument to be an integer \
611 smaller than {}, got {n}",
612 u32::MAX,
613 ),
614 pos_op,
615 )));
616 };
617
618 let f_closure = f.value.closurize(&mut self.context.cache, f.env);
619
620 let ts = (0..n_int)
624 .map(|n| {
625 mk_app!(f_closure.clone(), NickelValue::number_posless(n))
626 .closurize(&mut self.context.cache, env.clone())
627 })
628 .collect();
629
630 Ok(NickelValue::array(ts, Vec::new(), pos_op_inh).into())
631 }
632 UnaryOp::RecordMap => {
633 let (f, ..) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
634 EvalErrorKind::NotEnoughArgs(2, String::from("record/map"), pos_op)
635 })?;
636
637 let Some(container) = value.as_record() else {
638 return mk_type_error!("Record");
639 };
640
641 if let Container::Alloc(record) = container {
642 let record = record.clone();
643 if let Some(tail) = record.sealed_tail {
650 let label = Rc::unwrap_or_clone(tail).label;
651
652 return Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
653 action: IllegalPolymorphicTailAction::Map,
654 evaluated_arg: label.get_evaluated_arg(&self.context.cache),
655 label,
656 }));
657 }
658
659 let f_closure = f.value.closurize(&mut self.context.cache, f.env);
660
661 let fields = record
664 .fields
665 .into_iter()
666 .filter(|(_, field)| !field.is_empty_optional())
667 .map_values_closurize(&mut self.context.cache, &env, |id, t| {
668 let pos_idx = t.pos_idx().to_inherited();
669
670 mk_app!(
671 f_closure.clone(),
672 NickelValue::string_posless(id.label()),
673 t
674 )
675 .with_pos_idx(pos_idx)
676 })
677 .map_err(|miss_field_err| miss_field_err.into_eval_err(pos, pos_op))?;
678
679 let attrs = record.attrs.frozen();
683
684 Ok(NickelValue::record(
685 RecordData {
686 fields,
687 attrs,
688 ..record
689 },
690 pos_op_inh,
691 )
692 .into())
693 } else {
694 Ok(value.with_pos_idx(pos_op).into())
695 }
696 }
697 UnaryOp::Seq => self
698 .stack
699 .pop_arg(&self.context.cache)
700 .map(|(next, ..)| next)
701 .ok_or_else(|| {
702 Box::new(EvalErrorKind::NotEnoughArgs(2, String::from("seq"), pos_op))
703 }),
704 UnaryOp::DeepSeq => {
705 fn seq_terms<I>(mut it: I, pos_op_inh: PosIdx) -> NickelValue
710 where
711 I: Iterator<Item = NickelValue>,
712 {
713 let first = it
714 .next()
715 .expect("expected the argument to be a non-empty iterator");
716
717 it.fold(
718 mk_term::op1(UnaryOp::DeepSeq, first).with_pos_idx(pos_op_inh),
719 |acc, t| {
720 mk_app!(mk_term::op1(UnaryOp::DeepSeq, t), acc).with_pos_idx(pos_op_inh)
721 },
722 )
723 }
724
725 match value.content_ref() {
726 ValueContentRef::Record(Container::Alloc(record))
727 if !record.fields.is_empty() =>
728 {
729 let defined = record
730 .iter_without_opts()
732 .collect::<Result<Vec<_>, _>>()
733 .map_err(|missing_def_err| {
734 missing_def_err.into_eval_err(pos, pos_op)
735 })?;
736
737 let terms = defined.into_iter().map(|(_, field)| field);
738
739 Ok(Closure {
740 value: seq_terms(terms, pos_op),
741 env,
742 })
743 }
744 ValueContentRef::Array(Container::Alloc(array_data))
745 if !array_data.array.is_empty() =>
746 {
747 let terms = seq_terms(
748 array_data.array.iter().map(|t| {
749 RuntimeContract::apply_all(
750 t.clone(),
751 array_data.pending_contracts.iter().cloned(),
752 pos.to_inherited(),
753 )
754 .closurize(&mut self.context.cache, env.clone())
755 }),
756 pos_op,
757 );
758
759 Ok(terms.into())
760 }
761 ValueContentRef::EnumVariant(EnumVariantData {
762 tag: _,
763 arg: Some(arg),
764 }) => Ok(Closure {
765 value: seq_terms(std::iter::once(arg.clone()), pos_op),
766 env,
767 }),
768 _ => {
769 if let Some((next, ..)) = self.stack.pop_arg(&self.context.cache) {
770 Ok(next)
771 } else {
772 Err(Box::new(EvalErrorKind::NotEnoughArgs(
773 2,
774 String::from("deep_seq"),
775 pos_op,
776 )))
777 }
778 }
779 }
780 }
781 UnaryOp::ArrayLength => {
782 if let Some(container) = value.as_array() {
784 Ok(NickelValue::number(container.len(), pos_op_inh).into())
786 } else {
787 mk_type_error!("Array")
788 }
789 }
790 UnaryOp::ChunksConcat => {
791 let mut str_acc = self.stack.pop_str_acc().expect("invalid stack state: missing string accumulator on the top while evaluating string chunks");
792
793 if let Some(s) = value.to_nickel_string() {
794 if str_acc.curr_indent != 0 {
795 let indent_str: String = std::iter::once('\n')
796 .chain((0..str_acc.curr_indent).map(|_| ' '))
797 .collect();
798 str_acc.acc.push_str(&s.as_str().replace('\n', &indent_str))
799 } else {
800 str_acc.acc.push_str(s.as_str())
801 }
802
803 let mut next_chunk = self.stack.pop_str_chunk();
804 while let Some(StrChunk::Literal(s)) = next_chunk {
806 str_acc.acc.push_str(&s);
807 next_chunk = self.stack.pop_str_chunk();
808 }
809
810 if let Some(StrChunk::Expr(e, indent)) = next_chunk {
811 self.stack.push_str_acc(StrAccItem {
812 acc: str_acc.acc,
813 curr_indent: indent.try_into().unwrap(),
815 env: str_acc.env.clone(),
816 curr_pos: e.pos_idx(),
817 });
818
819 str_acc.curr_indent = indent.try_into().unwrap();
820 str_acc.curr_pos = e.pos_idx();
821
822 Ok(Closure {
825 value: NickelValue::term(
826 Term::op1(UnaryOp::ChunksConcat, e),
827 pos_op_inh,
828 ),
829 env: str_acc.env.clone(),
830 })
831 } else {
832 Ok(
833 NickelValue::string(std::mem::take(&mut str_acc.acc), pos_op_inh)
834 .into(),
835 )
836 }
837 } else {
838 Err(Box::new(EvalErrorKind::TypeError {
843 expected: String::from("Stringable"),
844 message: String::from(
845 "interpolated values must be Stringable (string, number, boolean, enum tag or null)",
846 ),
847 orig_pos: str_acc.curr_pos,
848 term: value,
849 }))
850 }
851 }
852 UnaryOp::StringTrim => {
853 if let Some(s) = value.as_string() {
854 Ok(NickelValue::string(s.trim(), pos_op_inh).into())
855 } else {
856 mk_type_error!("String")
857 }
858 }
859 UnaryOp::StringChars => {
860 if let Some(s) = value.as_string() {
861 let ts = s.characters();
862 Ok(NickelValue::array(ts, Vec::new(), pos_op_inh).into())
863 } else {
864 mk_type_error!("String")
865 }
866 }
867 UnaryOp::StringUppercase => {
868 if let Some(s) = value.as_string() {
869 Ok(NickelValue::string(s.to_uppercase(), pos_op_inh).into())
870 } else {
871 mk_type_error!("String")
872 }
873 }
874 UnaryOp::StringLowercase => {
875 if let Some(s) = value.as_string() {
876 Ok(NickelValue::string(s.to_lowercase(), pos_op_inh).into())
877 } else {
878 mk_type_error!("String")
879 }
880 }
881 UnaryOp::StringLength => {
882 if let Some(s) = value.as_string() {
883 let length = s.graphemes(true).count();
884 Ok(NickelValue::number(length, pos_op_inh).into())
885 } else {
886 mk_type_error!("String")
887 }
888 }
889 UnaryOp::ToString => value
890 .to_nickel_string()
891 .map(|s| NickelValue::string(s, pos_op_inh).into())
892 .ok_or_else(|| {
893 Box::new(EvalErrorKind::Other(
894 format!(
895 "to_string: can't convert an argument of type {} to string",
896 value.type_of().unwrap()
897 ),
898 pos,
899 ))
900 }),
901 UnaryOp::NumberFromString => {
902 if let Some(s) = value.as_string() {
903 let n = parse_number_sci(s).map_err(|_| {
904 Box::new(EvalErrorKind::Other(
905 format!(
906 "number/from_string: invalid number literal `{}`",
907 s.as_str()
908 ),
909 pos,
910 ))
911 })?;
912
913 Ok(NickelValue::number(n, pos_op_inh).into())
914 } else {
915 mk_type_error!("String")
916 }
917 }
918 UnaryOp::EnumFromString => {
919 if let Some(s) = value.as_string() {
920 Ok(NickelValue::enum_tag(LocIdent::from(s), pos_op_inh).into())
921 } else {
922 mk_type_error!("String")
923 }
924 }
925 UnaryOp::StringIsMatch => {
926 if let Some(s) = value.as_string() {
927 let re = regex::Regex::new(s)
928 .map_err(|err| Box::new(EvalErrorKind::Other(err.to_string(), pos_op)))?;
929
930 let matcher = eta_expand(UnaryOp::StringIsMatchCompiled(re.into()), pos_op_inh);
931 Ok(NickelValue::term(matcher, pos_op_inh).into())
932 } else {
933 mk_type_error!("String", 1)
934 }
935 }
936 UnaryOp::StringFind => {
937 if let Some(s) = value.as_string() {
938 let re = regex::Regex::new(s)
939 .map_err(|err| Box::new(EvalErrorKind::Other(err.to_string(), pos_op)))?;
940
941 let matcher = eta_expand(UnaryOp::StringFindCompiled(re.into()), pos_op_inh);
942 Ok(NickelValue::term(matcher, pos_op_inh).into())
943 } else {
944 mk_type_error!("String", 1)
945 }
946 }
947 UnaryOp::StringFindAll => {
948 if let Some(s) = value.as_string() {
949 let re = regex::Regex::new(s)
950 .map_err(|err| Box::new(EvalErrorKind::Other(err.to_string(), pos_op)))?;
951
952 let matcher = eta_expand(UnaryOp::StringFindAllCompiled(re.into()), pos_op_inh);
953 Ok(NickelValue::term(matcher, pos_op_inh).into())
954 } else {
955 mk_type_error!("String", 1)
956 }
957 }
958 UnaryOp::StringIsMatchCompiled(regex) => {
959 if let Some(s) = value.as_string() {
960 Ok(s.matches_regex(®ex).with_pos_idx(pos_op_inh).into())
961 } else {
962 mk_type_error!(op_name = "a compiled regular expression match", "String")
963 }
964 }
965 UnaryOp::StringFindCompiled(regex) => {
966 if let Some(s) = value.as_string() {
967 use crate::term::string::RegexFindResult;
968
969 let result = match s.find_regex(®ex) {
970 None => mk_record!(
973 ("matched", NickelValue::string_posless("")),
974 ("index", NickelValue::number_posless(-1)),
975 ("groups", NickelValue::empty_array())
976 ),
977 Some(RegexFindResult {
978 matched: mtch,
979 index,
980 groups,
981 }) => closurize_container(mk_record!(
982 ("matched", NickelValue::string_posless(mtch)),
983 ("index", NickelValue::number_posless(index)),
984 (
985 "groups",
986 NickelValue::array_posless(
987 Array::from_iter(
988 groups
989 .into_iter()
990 .map(|s| NickelValue::string_posless(
994 s.unwrap_or_default()
995 ))
996 ),
997 Vec::new()
998 )
999 )
1000 )),
1001 };
1002
1003 Ok(result.with_pos_idx(pos_op_inh).into())
1004 } else {
1005 mk_type_error!(op_name = "a compiled regular expression match", "String")
1006 }
1007 }
1008 UnaryOp::StringFindAllCompiled(regex) => {
1009 if let Some(s) = value.as_string() {
1010 let result = NickelValue::array(
1011 Array::from_iter(s.find_all_regex(®ex).map(|found| {
1012 closurize_container(mk_record!(
1013 ("matched", NickelValue::string_posless(found.matched)),
1014 ("index", NickelValue::number_posless(found.index)),
1015 (
1016 "groups",
1017 NickelValue::array_posless(
1018 Array::from_iter(
1019 found
1020 .groups
1021 .into_iter()
1022 .map(|s| NickelValue::string_posless(
1026 s.unwrap_or_default()
1027 ))
1028 ),
1029 Vec::new(),
1030 )
1031 )
1032 ))
1033 })),
1034 Vec::new(),
1035 pos_op_inh,
1036 );
1037
1038 Ok(result.into())
1039 } else {
1040 mk_type_error!(op_name = "a compiled regular expression match", "String")
1041 }
1042 }
1043 UnaryOp::Force {
1044 ignore_not_exported,
1045 } => {
1046 fn seq_terms<I>(terms: I, pos: PosIdx, cont: NickelValue) -> NickelValue
1048 where
1049 I: Iterator<Item = NickelValue>,
1050 {
1051 terms
1052 .fold(cont, |acc, t| mk_app!(mk_term::op1(UnaryOp::Seq, t), acc))
1053 .with_pos_idx(pos)
1054 }
1055
1056 match value.content_ref() {
1057 ValueContentRef::Record(Container::Alloc(record)) => {
1058 let fields = record
1059 .fields
1060 .iter()
1061 .filter(|(_, field)| {
1062 !(field.is_empty_optional()
1063 || (ignore_not_exported && field.metadata.not_exported()))
1064 })
1065 .map(|(id, field)| (*id, field.clone()))
1066 .map_values_closurize(&mut self.context.cache, &env, |_, value| {
1067 mk_term::op1(
1068 UnaryOp::Force {
1069 ignore_not_exported,
1070 },
1071 value,
1072 )
1073 })
1074 .map_err(|e| e.into_eval_err(pos, pos_op))?;
1075
1076 let terms: Vec<NickelValue> = fields
1077 .values()
1078 .map(|field| {
1079 field.value.as_ref().cloned().expect(
1080 "map_values_closurize ensures that values without a \
1081 definition throw a MissingFieldDefError",
1082 )
1083 })
1084 .collect();
1085
1086 let pos_inh = pos.to_inherited();
1087 let cont = NickelValue::record(
1088 RecordData {
1089 fields,
1090 attrs: record.attrs,
1091 sealed_tail: record.sealed_tail.clone(),
1092 },
1093 pos_inh,
1094 );
1095
1096 Ok(seq_terms(terms.into_iter(), pos_op, cont).into())
1097 }
1098 ValueContentRef::Array(Container::Alloc(array_data)) => {
1099 let ArrayData {
1101 array,
1102 pending_contracts,
1103 } = array_data.clone();
1104 let pos_inh = pos.to_inherited();
1105
1106 let ts = array
1107 .into_iter()
1108 .map(|t| {
1109 mk_term::op1(
1110 UnaryOp::Force {
1111 ignore_not_exported,
1112 },
1113 RuntimeContract::apply_all(
1114 t,
1115 pending_contracts.iter().cloned(),
1116 pos_inh,
1117 ),
1118 )
1119 .closurize(&mut self.context.cache, env.clone())
1120 })
1121 .collect::<Array>();
1126
1127 let terms = ts.clone().into_iter();
1128 let cont = NickelValue::array(ts, Vec::new(), pos_inh);
1129
1130 Ok(seq_terms(terms, pos_op, cont).into())
1131 }
1132 ValueContentRef::EnumVariant(data) => {
1133 let EnumVariantData { tag, arg } = data.clone();
1134
1135 if let Some(arg) = arg {
1136 let arg = mk_term::op1(
1137 UnaryOp::Force {
1138 ignore_not_exported,
1139 },
1140 arg,
1141 )
1142 .closurize(&mut self.context.cache, env.clone());
1143
1144 let cont = NickelValue::enum_variant(
1145 tag,
1146 Some(arg.clone()),
1147 pos.to_inherited(),
1148 );
1149
1150 Ok(Closure {
1151 value: seq_terms(std::iter::once(arg), pos_op, cont),
1152 env,
1153 })
1154 } else {
1155 Ok(Closure { value, env })
1156 }
1157 }
1158 _ => Ok(Closure { value, env }),
1159 }
1160 }
1161 UnaryOp::RecordEmptyWithTail => {
1162 let Some(container) = value.as_record() else {
1163 return mk_type_error!("Record");
1164 };
1165
1166 let mut result = RecordData::empty();
1167 result.sealed_tail = container
1168 .into_opt()
1169 .and_then(|record| record.sealed_tail.clone());
1170
1171 Ok(Closure {
1172 value: NickelValue::record(result, pos_op_inh),
1173 env,
1174 })
1175 }
1176 UnaryOp::RecordFreeze => {
1177 if matches!(value.as_record(), Some(Container::Alloc(record)) if record.attrs.frozen)
1179 {
1180 debug_assert!(
1182 value
1185 .as_record()
1186 .unwrap()
1187 .unwrap_alloc()
1188 .sealed_tail
1189 .is_none()
1190 );
1191
1192 return Ok(Closure { value, env });
1193 }
1194
1195 let Some(container) = value.as_record() else {
1196 return mk_type_error!("Record");
1197 };
1198
1199 if let Container::Alloc(record) = container {
1200 if let Some(tail) = &record.sealed_tail {
1205 return Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
1206 action: IllegalPolymorphicTailAction::Freeze,
1207 evaluated_arg: tail.label.get_evaluated_arg(&self.context.cache),
1208 label: tail.label.clone(),
1209 }));
1210 }
1211
1212 let fields = record
1213 .fields
1214 .iter()
1215 .map(|(id, field)| {
1216 let field = field.clone();
1217
1218 let value = field.value.map(|value| {
1219 let pos = value.pos_idx();
1220 RuntimeContract::apply_all(value, field.pending_contracts, pos)
1221 });
1222
1223 let field = Field {
1224 value,
1225 pending_contracts: Vec::new(),
1226 ..field
1227 }
1228 .closurize(&mut self.context.cache, env.clone());
1229
1230 (*id, field)
1231 })
1232 .collect();
1233
1234 let attrs = record.attrs.frozen();
1235
1236 Ok(Closure {
1237 value: NickelValue::record(
1238 RecordData {
1239 fields,
1240 attrs,
1241 sealed_tail: None,
1242 },
1243 pos_op_inh,
1244 ),
1245 env,
1246 })
1247 } else {
1248 Ok(value.into())
1250 }
1251 }
1252 UnaryOp::Trace => {
1253 if let Some(s) = value.as_string() {
1254 let _ = writeln!(self.context.trace, "std.trace: {s}");
1255 Ok(())
1256 } else {
1257 mk_type_error!("String")
1258 }?;
1259
1260 self.stack
1261 .pop_arg(&self.context.cache)
1262 .map(|(next, ..)| next)
1263 .ok_or_else(|| {
1264 Box::new(EvalErrorKind::NotEnoughArgs(
1265 2,
1266 String::from("trace"),
1267 pos_op,
1268 ))
1269 })
1270 }
1271 UnaryOp::LabelPushDiag => {
1272 let Some(label) = value.as_label() else {
1273 return mk_type_error!("Label");
1274 };
1275
1276 let mut label = label.clone();
1277 label.push_diagnostic();
1278 Ok(Closure {
1279 value: NickelValue::label(label, pos),
1280 env,
1281 })
1282 }
1283 #[cfg(feature = "nix-experimental")]
1284 UnaryOp::EvalNix => {
1285 if let Some(s) = value.as_string() {
1286 let base_dir = self
1287 .context
1288 .pos_table
1289 .get(pos_op)
1290 .into_opt()
1291 .map(|span| self.import_resolver().get_base_dir_for_nix(span.src_id))
1292 .unwrap_or_default();
1293
1294 let json = nix_ffi::eval_to_json(&String::from(s), &base_dir).map_err(|e| {
1295 Box::new(EvalErrorKind::Other(
1296 format!("nix code failed to evaluate:\n {}", e.what()),
1297 pos,
1298 ))
1299 })?;
1300
1301 let result: NickelValue = serde_json::from_str(&json).map_err(|e| {
1302 Box::new(EvalErrorKind::Other(
1303 format!("nix produced invalid json: {e}"),
1304 pos,
1305 ))
1306 })?;
1307
1308 Ok(result.into())
1309 } else {
1310 Err(Box::new(EvalErrorKind::TypeError {
1312 expected: String::from("String"),
1313 message: String::from("eval_nix takes a string of nix code as an argument"),
1314 orig_pos: orig_pos_arg,
1315 term: value,
1316 }))
1317 }
1318 }
1319 UnaryOp::EnumGetArg => {
1320 if let Some(EnumVariantData { arg: Some(arg), .. }) = value.as_enum_variant() {
1321 Ok(Closure {
1322 value: arg.clone(),
1323 env,
1324 })
1325 } else {
1326 mk_type_error!("Enum variant")
1327 }
1328 }
1329 UnaryOp::EnumMakeVariant => {
1330 let Some(tag) = value.as_string() else {
1331 return mk_type_error!("String");
1332 };
1333
1334 let (arg_clos, _) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
1335 Box::new(EvalErrorKind::NotEnoughArgs(
1336 2,
1337 String::from("enum/make_variant"),
1338 pos,
1339 ))
1340 })?;
1341 let arg_pos = arg_clos.value.pos_idx();
1342
1343 Ok(NickelValue::enum_variant(
1344 LocIdent::new(tag).with_pos(self.context.pos_table.get(pos)),
1345 Some(Thunk::new(arg_clos, arg_pos).into()),
1346 pos_op_inh,
1347 )
1348 .into())
1349 }
1350 UnaryOp::EnumGetTag => match value.as_enum_variant() {
1351 Some(EnumVariantData { tag, .. }) => {
1352 Ok(NickelValue::enum_tag(*tag, pos_op_inh).into())
1353 }
1354 _ => mk_type_error!("Enum"),
1355 },
1356 UnaryOp::EnumIsVariant => Ok(NickelValue::bool_value(
1357 value
1358 .as_enum_variant()
1359 .is_some_and(|enum_variant| enum_variant.arg.is_some()),
1360 pos_op_inh,
1361 )
1362 .into()),
1363 UnaryOp::ContractCustom => {
1364 let contract = if matches!(value.as_term(), Some(Term::Fun(..))) {
1365 value.closurize(&mut self.context.cache, env)
1366 } else {
1367 return mk_type_error!("Function or MatchExpression");
1368 };
1369
1370 Ok(NickelValue::custom_contract(contract, pos_op_inh).into())
1371 }
1372 UnaryOp::ContractPostprocessResult => {
1373 let Some(EnumVariantData {
1374 tag,
1375 arg: Some(arg),
1376 }) = value.as_enum_variant()
1377 else {
1378 return mk_type_error!("[| 'Ok, 'Error _ |]");
1379 };
1380
1381 let (label_closure, pos_label) = self.stack.pop_arg(&self.context.cache).unwrap();
1384
1385 match (tag.label(), arg) {
1386 ("Ok", value) => Ok(Closure {
1387 value: value.clone(),
1388 env,
1389 }),
1390 ("Error", err_data) => {
1391 let app_info = PrimopAppInfo {
1392 call_stack_size: self.call_stack.len(),
1393 pos_idx: pos_op_inh,
1394 };
1395 self.stack.push_op1_cont(Op1ContItem {
1403 op: UnaryOp::Blame,
1404 app_info,
1405 orig_pos_arg,
1406 });
1407 self.stack.push_op2_first_cont(Op2FirstContItem {
1408 op: BinaryOp::LabelWithErrorData,
1409 app_info,
1410 arg2: label_closure,
1411 orig_pos_arg1: pos_label,
1412 });
1413 self.stack.push_op1_cont(Op1ContItem {
1414 op: UnaryOp::Force {
1415 ignore_not_exported: false,
1416 },
1417 app_info,
1418 orig_pos_arg,
1419 });
1420
1421 Ok(Closure {
1422 value: err_data.clone(),
1423 env,
1424 })
1425 }
1426 _ => mk_type_error!("[| 'Ok, 'Error {..} |]'"),
1427 }
1428 }
1429 UnaryOp::ContractAttachDefaultLabel => {
1430 if !matches!(
1431 value.as_enum_variant(),
1432 Some(EnumVariantData { arg: Some(_), .. })
1433 ) {
1434 return mk_type_error!("[| 'Ok, 'Error _ |]");
1435 }
1436 self.stack.push_arg(Closure { value, env }, orig_pos_arg);
1439
1440 Ok(Closure {
1441 value: internals::add_default_check_label(),
1442 env: Environment::new(),
1443 })
1444 }
1445 UnaryOp::NumberArcCos => self.number_op1(
1446 f64::acos,
1447 Op1EvalData {
1448 op,
1449 arg: Closure { value, env },
1450 orig_pos_arg,
1451 pos_op,
1452 },
1453 ),
1454 UnaryOp::NumberArcSin => self.number_op1(
1455 f64::asin,
1456 Op1EvalData {
1457 op,
1458 arg: Closure { value, env },
1459 orig_pos_arg,
1460 pos_op,
1461 },
1462 ),
1463 UnaryOp::NumberArcTan => self.number_op1(
1464 f64::atan,
1465 Op1EvalData {
1466 op,
1467 arg: Closure { value, env },
1468 orig_pos_arg,
1469 pos_op,
1470 },
1471 ),
1472 UnaryOp::NumberCos => self.number_op1(
1473 f64::cos,
1474 Op1EvalData {
1475 op,
1476 arg: Closure { value, env },
1477 orig_pos_arg,
1478 pos_op,
1479 },
1480 ),
1481 UnaryOp::NumberSin => self.number_op1(
1482 f64::sin,
1483 Op1EvalData {
1484 op,
1485 arg: Closure { value, env },
1486 orig_pos_arg,
1487 pos_op,
1488 },
1489 ),
1490 UnaryOp::NumberTan => self.number_op1(
1491 f64::tan,
1492 Op1EvalData {
1493 op,
1494 arg: Closure { value, env },
1495 orig_pos_arg,
1496 pos_op,
1497 },
1498 ),
1499 UnaryOp::RecDefault => unimplemented!(),
1500 UnaryOp::RecForce => unimplemented!(),
1501 }
1502 }
1503
1504 fn number_op1<F>(&mut self, f: F, eval_data: Op1EvalData) -> Result<Closure, ErrorKind>
1505 where
1506 F: Fn(f64) -> f64,
1507 {
1508 let Op1EvalData {
1509 op,
1510 arg: Closure { value, env: _ },
1511 orig_pos_arg,
1512 pos_op,
1513 } = eval_data;
1514
1515 if let Some(n) = value.as_number() {
1516 let result_as_f64 = f(f64::rounding_from(n, RoundingMode::Nearest).0);
1517 let result = Number::try_from_float_simplest(result_as_f64).map_err(|_| {
1518 Box::new(EvalErrorKind::Other(
1519 format!(
1520 "invalid arithmetic operation: \
1521 {op}({n}) returned {result_as_f64}, \
1522 but {result_as_f64} isn't representable in Nickel",
1523 ),
1524 pos_op,
1525 ))
1526 })?;
1527
1528 Ok(NickelValue::number(result, pos_op.to_inherited()).into())
1529 } else {
1530 Err(Box::new(EvalErrorKind::UnaryPrimopTypeError {
1531 primop: op.to_string(),
1532 expected: String::from("Number"),
1533 pos_arg: orig_pos_arg,
1534 arg_evaluated: value,
1535 }))
1536 }
1537 }
1538
1539 fn eval_op2(&mut self, eval_data: Op2EvalData) -> Result<Closure, ErrorKind> {
1543 let Op2EvalData {
1544 op,
1545 arg1: Closure {
1546 value: value1,
1547 env: env1,
1548 },
1549 arg2:
1550 Closure {
1551 value: mut value2,
1552 env: env2,
1553 },
1554 orig_pos_arg1,
1555 orig_pos_arg2,
1556 pos_op,
1557 } = eval_data;
1558
1559 increment!(format!("primop:{op}"));
1560
1561 let pos1 = value1.pos_idx();
1562 let pos2 = value2.pos_idx();
1563 let pos_op_inh = pos_op.to_inherited();
1564
1565 macro_rules! mk_type_error {
1566 (op_name=$op_name:expr, $expected:expr, $arg_number:expr, $arg_evaled:expr) => {
1567 Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
1568 primop: String::from($op_name),
1569 expected: String::from($expected),
1570 arg_number: $arg_number,
1571 pos_arg: {
1572 match $arg_number {
1573 1 => orig_pos_arg1,
1574 2 => orig_pos_arg2,
1575 _ => unimplemented!(),
1576 }
1577 },
1578 arg_evaluated: $arg_evaled,
1579 pos_op,
1580 }))
1581 };
1582 ($expected:expr, $arg_number:expr, $arg_evaled:expr) => {
1583 mk_type_error!(
1584 op_name = op.to_string(),
1585 $expected,
1586 $arg_number,
1587 $arg_evaled
1588 )
1589 };
1590 }
1591
1592 match op {
1593 BinaryOp::Seal => {
1594 let Some(key) = value1.as_sealing_key() else {
1595 return mk_type_error!("SealingKey", 1, value1);
1596 };
1597
1598 let Some(label) = value2.as_label() else {
1599 return mk_type_error!("Label", 2, value2);
1600 };
1601
1602 Ok(mk_fun!(
1603 "x",
1604 NickelValue::term(
1605 Term::sealed(*key, mk_term::var("x"), label.clone()),
1606 pos_op_inh
1607 )
1608 )
1609 .into())
1610 }
1611 BinaryOp::Plus => self.number_op2(
1612 |n1, n2| n1 + n2,
1613 Op2EvalData {
1614 op,
1615 arg1: Closure {
1616 value: value1,
1617 env: env1,
1618 },
1619 arg2: Closure {
1620 value: value2,
1621 env: env2,
1622 },
1623 orig_pos_arg1,
1624 orig_pos_arg2,
1625 pos_op,
1626 },
1627 ),
1628 BinaryOp::Sub => self.number_op2(
1629 |n1, n2| n1 - n2,
1630 Op2EvalData {
1631 op,
1632 arg1: Closure {
1633 value: value1,
1634 env: env1,
1635 },
1636 arg2: Closure {
1637 value: value2,
1638 env: env2,
1639 },
1640 orig_pos_arg1,
1641 orig_pos_arg2,
1642 pos_op,
1643 },
1644 ),
1645 BinaryOp::Mult => self.number_op2(
1646 |n1, n2| n1 * n2,
1647 Op2EvalData {
1648 op,
1649 arg1: Closure {
1650 value: value1,
1651 env: env1,
1652 },
1653 arg2: Closure {
1654 value: value2,
1655 env: env2,
1656 },
1657 orig_pos_arg1,
1658 orig_pos_arg2,
1659 pos_op,
1660 },
1661 ),
1662 BinaryOp::Div => {
1663 let Some(n1) = value1.as_number() else {
1664 return mk_type_error!("Number", 1, value1);
1665 };
1666
1667 let Some(n2) = value2.as_number() else {
1668 return mk_type_error!("Number", 2, value2);
1669 };
1670
1671 if n2 == &Number::ZERO {
1672 Err(Box::new(EvalErrorKind::Other(
1673 String::from("division by zero"),
1674 pos_op,
1675 )))
1676 } else {
1677 Ok(NickelValue::number(n1 / n2, pos_op_inh).into())
1678 }
1679 }
1680 BinaryOp::Modulo => {
1681 let Some(n1) = value1.as_number() else {
1682 return mk_type_error!("Number", 1, value1);
1683 };
1684
1685 let Some(n2) = value2.as_number() else {
1686 return mk_type_error!("Number", 2, value2);
1687 };
1688
1689 if n2 == &Number::ZERO {
1690 return Err(Box::new(EvalErrorKind::Other(
1691 String::from("division by zero (%)"),
1692 pos2,
1693 )));
1694 }
1695
1696 let quotient = Number::from(Integer::rounding_from(n1 / n2, RoundingMode::Down).0);
1698
1699 Ok(NickelValue::number(n1 - quotient * n2, pos_op_inh).into())
1700 }
1701 BinaryOp::NumberArcTan2 => {
1702 let Some(n1) = value1.as_number() else {
1703 return mk_type_error!("Number", 1, value1);
1704 };
1705
1706 let Some(n2) = value2.as_number() else {
1707 return mk_type_error!("Number", 2, value2);
1708 };
1709
1710 let y = f64::rounding_from(n1, RoundingMode::Nearest).0;
1711 let x = f64::rounding_from(n2, RoundingMode::Nearest).0;
1712
1713 let result_as_f64 = y.atan2(x);
1714
1715 let result = Number::try_from_float_simplest(result_as_f64).map_err(|_| {
1716 Box::new(EvalErrorKind::Other(
1717 format!(
1718 "invalid arithmetic operation: \
1719 number/arctan2({n1}, {n2}) returned {result_as_f64}, \
1720 but {result_as_f64} isn't representable in Nickel"
1721 ),
1722 pos_op,
1723 ))
1724 })?;
1725
1726 Ok(NickelValue::number(result, pos_op_inh).into())
1727 }
1728 BinaryOp::NumberLog => {
1729 let Some(n1) = value1.as_number() else {
1730 return mk_type_error!("Number", 1, value1);
1731 };
1732
1733 let Some(n2) = value2.as_number() else {
1734 return mk_type_error!("Number", 2, value2);
1735 };
1736
1737 let n = f64::rounding_from(n1, RoundingMode::Nearest).0;
1738
1739 let result_as_f64 = if n2 == &2 {
1740 n.log2()
1741 } else if n2 == &Number::from(10) {
1742 n.log10()
1743 } else {
1744 let base = f64::rounding_from(n2, RoundingMode::Nearest).0;
1745 n.log(base)
1746 };
1747
1748 let result = Number::try_from_float_simplest(result_as_f64).map_err(|_| {
1749 Box::new(EvalErrorKind::Other(
1750 format!(
1751 "invalid arithmetic operation: \
1752 number/log({n1}, {n2}) returned {result_as_f64}, \
1753 but {result_as_f64} isn't representable in Nickel"
1754 ),
1755 pos_op,
1756 ))
1757 })?;
1758
1759 Ok(NickelValue::number(result, pos_op_inh).into())
1760 }
1761 BinaryOp::Pow => {
1762 let Some(n1) = value1.as_number() else {
1763 return mk_type_error!("Number", 1, value1);
1764 };
1765
1766 let Some(n2) = value2.as_number() else {
1767 return mk_type_error!("Number", 2, value2);
1768 };
1769
1770 let result = if let Ok(n2_as_i64) = i64::try_from(n2) {
1781 n1.pow(n2_as_i64)
1782 } else {
1783 let result_as_f64 = f64::rounding_from(n1, RoundingMode::Nearest)
1784 .0
1785 .powf(f64::rounding_from(n2, RoundingMode::Nearest).0);
1786 Number::try_from_float_simplest(result_as_f64).map_err(|_| {
1788 Box::new(EvalErrorKind::Other(
1789 format!(
1790 "invalid arithmetic operation: \
1791 {n1}^{n2} returned {result_as_f64}, \
1792 but {result_as_f64} isn't representable in Nickel"
1793 ),
1794 pos_op,
1795 ))
1796 })?
1797 };
1798
1799 Ok(NickelValue::number(result, pos_op_inh).into())
1800 }
1801 BinaryOp::StringConcat => {
1802 let Some(s1) = value1.as_string() else {
1803 return mk_type_error!("String", 1, value1);
1804 };
1805
1806 let Some(s2) = value2.as_string() else {
1807 return mk_type_error!("String", 2, value2);
1808 };
1809
1810 Ok(NickelValue::string(format!("{s1}{s2}"), pos_op_inh).into())
1811 }
1812 BinaryOp::ContractApply | BinaryOp::ContractCheck => {
1813 let app_info = PrimopAppInfo {
1814 call_stack_size: self.call_stack.len(),
1815 pos_idx: pos_op_inh,
1816 };
1817
1818 #[cfg(feature = "metrics")]
1824 if let Some(TypeData { typ, .. }) = value1.as_type() {
1825 increment!(format!(
1826 "primop:contract/apply:{}",
1827 typ.pretty_print_cap(40)
1828 ));
1829 }
1830
1831 if let Some(TypeData { typ: _, contract }) = value1.as_type() {
1832 self.stack.push_op2_first_cont(Op2FirstContItem {
1841 op,
1842 app_info,
1843 arg2: Closure {
1844 value: value2,
1845 env: env2,
1846 },
1847 orig_pos_arg1,
1848 });
1849
1850 return Ok(Closure {
1851 value: contract.clone(),
1852 env: env1,
1853 });
1854 }
1855
1856 let Some(label) = value2.as_label() else {
1857 return mk_type_error!("Label", 2, value2);
1858 };
1859
1860 let mut label = label.clone();
1861
1862 increment!(format!(
1863 "contract:originates_from_type {}",
1864 label.typ.pretty_print_cap(40)
1865 ));
1866
1867 #[cfg(feature = "metrics")]
1868 if let Some(field) = label.field_name {
1869 increment!(format!("contract:originates_from_field {field}"));
1870 }
1871
1872 let (idx, stack_value_pos) = self
1878 .stack
1879 .pop_arg_as_idx(&mut self.context.cache)
1880 .ok_or_else(|| {
1881 Box::new(EvalErrorKind::NotEnoughArgs(
1882 3,
1883 String::from("contract/apply"),
1884 pos_op,
1885 ))
1886 })?;
1887
1888 label.arg_pos = self
1890 .context
1891 .cache
1892 .get_then(idx.clone(), |c| c.value.pos_idx());
1893 label.arg_idx = Some(idx.clone());
1894 let new_label = NickelValue::label(label, pos2);
1895
1896 if let (
1908 ValueContentRef::CustomContract(_) | ValueContentRef::Record(_),
1909 BinaryOp::ContractApply,
1910 ) = (value1.content_ref(), &op)
1911 {
1912 self.stack.push_arg(new_label.clone().into(), pos_op_inh);
1913
1914 self.stack.push_op1_cont(Op1ContItem {
1915 op: UnaryOp::ContractPostprocessResult,
1916 app_info,
1917 orig_pos_arg: pos1.to_inherited(),
1918 });
1919 }
1920
1921 if let BinaryOp::ContractCheck = &op {
1928 self.stack.push_arg(new_label.clone().into(), pos_op_inh);
1929
1930 self.stack.push_op1_cont(Op1ContItem {
1931 op: UnaryOp::ContractAttachDefaultLabel,
1932 app_info,
1933 orig_pos_arg: pos1.to_inherited(),
1934 });
1935 }
1936
1937 self.stack.push_tracked_arg(idx, stack_value_pos);
1943 self.stack.push_arg(new_label.into(), pos2.to_inherited());
1944
1945 let functoid = match value1.content_ref() {
1948 ValueContentRef::Term(Term::Fun(..)) => {
1949 if let Some(pos) = self.context.pos_table.get(pos1).as_opt_ref()
1952 && !self.context.import_resolver.files().is_stdlib(pos.src_id)
1953 {
1954 self.warn(Warning::NakedFunctionContract {
1955 func_pos: self.context.pos_table.get(pos1),
1956 app_pos: self.context.pos_table.get(pos_op),
1957 });
1958 }
1959
1960 if let BinaryOp::ContractApply = op {
1961 Closure {
1962 value: value1,
1963 env: env1,
1964 }
1965 } else {
1966 self.stack.push_arg(
1969 Closure {
1970 value: value1,
1971 env: env1,
1972 },
1973 orig_pos_arg1,
1974 );
1975
1976 internals::naked_to_custom().into()
1977 }
1978 }
1979 ValueContentRef::CustomContract(ctr) => Closure {
1980 value: ctr.clone(),
1981 env: env1,
1982 },
1983 ValueContentRef::Record(..) => {
1984 self.stack.push_arg(
1987 Closure {
1988 value: value1,
1989 env: env1,
1990 },
1991 orig_pos_arg1,
1992 );
1993
1994 internals::record_contract().into()
1995 }
1996 _ => return mk_type_error!("Contract", 1, value1),
1997 };
1998
1999 Ok(functoid)
2000 }
2001 BinaryOp::LabelWithErrorData => {
2002 let value1 = subst(
2006 &self.context.pos_table,
2007 &self.context.cache,
2008 value1,
2009 &self.initial_env,
2010 &env1,
2011 );
2012
2013 let Some(label) = value2.as_label() else {
2014 return mk_type_error!("Label", 2, value2);
2015 };
2016
2017 let mut label = label.clone();
2018
2019 match value1.as_record() {
2020 Some(Container::Empty) => Ok(NickelValue::label(label, pos2).into()),
2021 Some(Container::Alloc(record_data)) => {
2022 if let Some(user_label) = record_data
2025 .fields
2026 .get(&LocIdent::from("blame_location"))
2027 .and_then(|field| field.value.as_ref())
2028 .and_then(NickelValue::as_label)
2029 {
2030 label = user_label.clone();
2031 }
2032
2033 if let Some(msg) = record_data
2034 .fields
2035 .get(&LocIdent::from("message"))
2036 .and_then(|field| field.value.as_ref())
2037 .and_then(NickelValue::as_string)
2038 {
2039 label = label.with_diagnostic_message(msg.clone().into_inner());
2040 }
2041
2042 if let Some(notes) = record_data
2043 .fields
2044 .get(&LocIdent::from("notes"))
2045 .and_then(|field| field.value.as_ref())
2046 .and_then(NickelValue::as_array)
2047 .and_then(Container::into_opt)
2048 {
2049 let notes = notes
2050 .array
2051 .iter()
2052 .map(|element| {
2053 if let Some(s) = element.as_string() {
2054 Ok(s.clone().into_inner())
2055 } else {
2056 mk_type_error!("String (notes)", 1, element.clone())
2057 }
2058 })
2059 .collect::<Result<Vec<_>, _>>()?;
2060
2061 label = label.with_diagnostic_notes(notes);
2062 }
2063
2064 Ok(NickelValue::label(label, pos2).into())
2065 }
2066 _ => {
2067 mk_type_error!("Record", 1, value1)
2068 }
2069 }
2070 }
2071 BinaryOp::Unseal => {
2072 if let Some(key) = value1.as_sealing_key() {
2073 Ok(if let Some(Term::Sealed(data)) = value2.as_term() {
2081 if key == &data.key {
2082 Closure {
2083 value: mk_fun!(LocIdent::fresh(), data.inner.clone()),
2084 env: env2,
2085 }
2086 } else {
2087 mk_term::id().into()
2088 }
2089 } else {
2090 mk_term::id().into()
2091 })
2092 } else {
2093 mk_type_error!("SealingKey", 1, value1)
2094 }
2095 }
2096 BinaryOp::Eq => {
2097 let c1 = Closure {
2098 value: value1,
2099 env: env1,
2100 };
2101 let c2 = Closure {
2102 value: value2,
2103 env: env2,
2104 };
2105
2106 match eq(&mut self.context.cache, c1, c2, pos_op_inh)? {
2107 EqResult::Bool(b) => match (b, self.stack.pop_eq()) {
2108 (false, _) => {
2109 self.stack.clear_eqs();
2110 Ok(NickelValue::bool_value(false, pos_op_inh).into())
2111 }
2112 (true, None) => Ok(NickelValue::bool_value(true, pos_op_inh).into()),
2113 (true, Some(EqItem { arg1, arg2 })) => {
2114 let v1 = arg1.value.closurize(&mut self.context.cache, arg1.env);
2115 let v2 = arg2.value.closurize(&mut self.context.cache, arg2.env);
2116
2117 Ok(NickelValue::term(Term::op2(BinaryOp::Eq, v1, v2), pos_op).into())
2119 }
2120 },
2121 EqResult::Eqs(v1, v2, subeqs) => {
2122 self.stack.push_eqs(subeqs.into_iter());
2123
2124 Ok(NickelValue::term(Term::op2(BinaryOp::Eq, v1, v2), pos_op).into())
2126 }
2127 }
2128 }
2129 BinaryOp::LessThan => self.number_cmp2(
2130 |n1, n2| n1 < n2,
2131 Op2EvalData {
2132 op,
2133 arg1: Closure {
2134 value: value1,
2135 env: env1,
2136 },
2137 arg2: Closure {
2138 value: value2,
2139 env: env2,
2140 },
2141 orig_pos_arg1,
2142 orig_pos_arg2,
2143 pos_op,
2144 },
2145 ),
2146 BinaryOp::LessOrEq => self.number_cmp2(
2147 |n1, n2| n1 <= n2,
2148 Op2EvalData {
2149 op,
2150 arg1: Closure {
2151 value: value1,
2152 env: env1,
2153 },
2154 arg2: Closure {
2155 value: value2,
2156 env: env2,
2157 },
2158 orig_pos_arg1,
2159 orig_pos_arg2,
2160 pos_op,
2161 },
2162 ),
2163 BinaryOp::GreaterThan => self.number_cmp2(
2164 |n1, n2| n1 > n2,
2165 Op2EvalData {
2166 op,
2167 arg1: Closure {
2168 value: value1,
2169 env: env1,
2170 },
2171 arg2: Closure {
2172 value: value2,
2173 env: env2,
2174 },
2175 orig_pos_arg1,
2176 orig_pos_arg2,
2177 pos_op,
2178 },
2179 ),
2180 BinaryOp::GreaterOrEq => self.number_cmp2(
2181 |n1, n2| n1 >= n2,
2182 Op2EvalData {
2183 op,
2184 arg1: Closure {
2185 value: value1,
2186 env: env1,
2187 },
2188 arg2: Closure {
2189 value: value2,
2190 env: env2,
2191 },
2192 orig_pos_arg1,
2193 orig_pos_arg2,
2194 pos_op,
2195 },
2196 ),
2197 BinaryOp::LabelGoField => {
2198 let Some(field) = value1.as_string() else {
2199 return mk_type_error!("String", 1, value1);
2200 };
2201
2202 let Some(label) = value2.as_label() else {
2203 return mk_type_error!("Label", 2, value2);
2204 };
2205
2206 let mut label = label.clone();
2207 label
2208 .path
2209 .push(ty_path::Elem::Field(field.clone().into_inner().into()));
2210 Ok(NickelValue::label(label, pos_op_inh).into())
2211 }
2212 BinaryOp::RecordGet => {
2213 let Some(id) = value1.as_string() else {
2217 return mk_type_error!("String", 1, value1);
2218 };
2219
2220 let Some(container) = value2.as_record() else {
2221 return Err(Box::new(EvalErrorKind::TypeError {
2223 expected: String::from("Record"),
2224 message: String::from("field access only makes sense for records"),
2225 orig_pos: orig_pos_arg2,
2226 term: value2,
2227 }));
2228 };
2229
2230 let ident = LocIdent::from(id);
2231
2232 let Container::Alloc(record) = container else {
2233 return Err(Box::new(EvalErrorKind::FieldMissing {
2234 id: ident,
2235 field_names: Vec::new(),
2236 operator: BinaryOp::RecordGet.to_string(),
2237 pos_record: pos2,
2238 pos_op,
2239 }));
2240 };
2241
2242 match record
2248 .get_value_with_ctrs(&ident)
2249 .map_err(|missing_field_err| missing_field_err.into_eval_err(pos2, pos_op))?
2250 {
2251 Some(value) => {
2252 self.call_stack
2253 .enter_field(ident, pos2, value.pos_idx(), pos_op);
2254 Ok(Closure { value, env: env2 })
2255 }
2256 None => match record.sealed_tail.as_ref() {
2257 Some(t) if t.has_dyn_field(id) => {
2258 Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
2259 action: IllegalPolymorphicTailAction::FieldAccess {
2260 field: id.to_string(),
2261 },
2262 evaluated_arg: t.label.get_evaluated_arg(&self.context.cache),
2263 label: t.label.clone(),
2264 }))
2265 }
2266 _ => Err(Box::new(EvalErrorKind::FieldMissing {
2267 id: ident,
2268 field_names: record.field_names(RecordOpKind::IgnoreEmptyOpt),
2269 operator: BinaryOp::RecordGet.to_string(),
2270 pos_record: pos2,
2271 pos_op,
2272 })),
2273 },
2274 }
2275 }
2276 BinaryOp::RecordInsert {
2277 metadata,
2278 pending_contracts,
2279 ext_kind,
2280 op_kind,
2281 } => {
2282 let op_name = || {
2286 BinaryOp::RecordInsert {
2287 ext_kind,
2288 op_kind,
2289 metadata: SharedMetadata::empty(),
2290 pending_contracts: Vec::new(),
2291 }
2292 .to_string()
2293 };
2294
2295 let Some(id) = value1.as_string() else {
2296 return mk_type_error!(op_name = op_name(), "String", 1, value1);
2297 };
2298
2299 let mut value2 = value2;
2300
2301 if value2.is_inline_empty_record() {
2302 value2 = NickelValue::empty_record_block(pos2);
2305 }
2306
2307 let ValueContentRefMut::Record(Container::Alloc(record)) =
2308 value2.content_make_mut()
2309 else {
2310 return mk_type_error!(op_name = op_name(), "Record", 2, value2);
2313 };
2314
2315 let value = if let RecordExtKind::WithValue = ext_kind {
2318 let (value_closure, _) =
2319 self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
2320 Box::new(EvalErrorKind::NotEnoughArgs(
2321 3,
2322 String::from("insert"),
2323 pos_op,
2324 ))
2325 })?;
2326
2327 let closurized = value_closure
2328 .value
2329 .closurize(&mut self.context.cache, value_closure.env);
2330 Some(closurized)
2331 } else {
2332 None
2333 };
2334
2335 match record.fields.insert(
2336 LocIdent::from(id),
2337 Field {
2338 value,
2339 metadata,
2340 pending_contracts,
2341 },
2342 ) {
2343 Some(t)
2344 if matches!(op_kind, RecordOpKind::ConsiderAllFields)
2345 || !t.is_empty_optional() =>
2346 {
2347 Err(Box::new(EvalErrorKind::Other(
2348 format!(
2349 "{}: \
2350 tried to extend a record with the field {id}, \
2351 but it already exists",
2352 op_name(),
2353 ),
2354 pos_op,
2355 )))
2356 }
2357 _ => Ok(Closure {
2358 value: value2,
2360 env: env2,
2361 }),
2362 }
2363 }
2364 BinaryOp::RecordRemove(op_kind) => {
2365 let Some(id) = value1.as_string() else {
2366 return mk_type_error!("String", 1, value1);
2367 };
2368
2369 let mut value2 = value2;
2370
2371 if value2.is_inline_empty_record() {
2372 value2 = NickelValue::empty_record_block(pos2);
2375 }
2376
2377 let ValueContentRefMut::Record(Container::Alloc(record)) =
2378 value2.content_make_mut()
2379 else {
2380 return mk_type_error!("Record", 2, value2);
2383 };
2384
2385 let fetched = record.fields.swap_remove(&LocIdent::from(id));
2386
2387 if fetched.is_none()
2388 || matches!(
2389 (op_kind, fetched),
2390 (
2391 RecordOpKind::IgnoreEmptyOpt,
2392 Some(Field {
2393 value: None,
2394 metadata, ..
2395 })
2396 ) if metadata.opt()
2397 )
2398 {
2399 match record.sealed_tail.as_ref() {
2400 Some(t) if t.has_dyn_field(id) => {
2401 Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
2402 action: IllegalPolymorphicTailAction::FieldRemove {
2403 field: id.to_string(),
2404 },
2405 evaluated_arg: t.label.get_evaluated_arg(&self.context.cache),
2406 label: t.label.clone(),
2407 }))
2408 }
2409 _ => Err(Box::new(EvalErrorKind::FieldMissing {
2410 id: id.into(),
2411 field_names: record.field_names(op_kind),
2412 operator: String::from("record/remove"),
2413 pos_record: pos2,
2414 pos_op,
2415 })),
2416 }
2417 } else {
2418 Ok(Closure {
2419 value: value2,
2420 env: env2,
2421 })
2422 }
2423 }
2424 BinaryOp::RecordHasField(op_kind) => {
2425 let Some(id) = value1.as_string() else {
2426 return mk_type_error!("String", 1, value1);
2427 };
2428
2429 let Some(container) = value2.as_record() else {
2430 return mk_type_error!("Record", 2, value2);
2431 };
2432
2433 Ok(NickelValue::bool_value(matches!(
2434 container.get(id.clone().into()),
2435 Some(field) if matches!(op_kind, RecordOpKind::ConsiderAllFields) || !field.is_empty_optional()
2436 ),
2437 pos_op_inh
2438 ).into())
2439 }
2440 BinaryOp::RecordFieldIsDefined(op_kind) => {
2441 let Some(id) = value1.as_string() else {
2442 return mk_type_error!("String", 1, value1);
2443 };
2444
2445 let Some(container) = value2.as_record() else {
2446 return mk_type_error!("Record", 2, value2);
2447 };
2448
2449 Ok(NickelValue::bool_value(
2450 matches!(
2451 container.get(id.clone().into()),
2452 Some(field @ Field { value: Some(_), ..}) if matches!(op_kind, RecordOpKind::ConsiderAllFields) || !field.is_empty_optional()
2453 ),
2454 pos_op_inh,
2455 ).into())
2456 }
2457 BinaryOp::ArrayConcat => {
2458 let Some(container1) = value1.as_array() else {
2459 return mk_type_error!("Array", 1, value1);
2460 };
2461
2462 let Some(container2) = value2.as_array() else {
2463 return mk_type_error!("Array", 2, value2);
2464 };
2465
2466 let Container::Alloc(array_data1) = container1 else {
2468 return Ok(value2.into());
2469 };
2470
2471 let Container::Alloc(array_data2) = container2 else {
2472 return Ok(value1.into());
2473 };
2474
2475 if array_data1.pending_contracts.len() == array_data2.pending_contracts.len()
2484 && array_data1
2485 .pending_contracts
2486 .iter()
2487 .zip(array_data2.pending_contracts.iter())
2488 .all(|(ctr1, ctr2)| {
2489 !ctr1.can_have_poly_ctrs()
2490 && contract_eq(&ctr1.contract, &env1, &ctr2.contract, &env2)
2491 })
2492 {
2493 let mut result = array_data1.array.clone();
2496 result.extend(array_data2.array.iter().cloned());
2497
2498 Ok(NickelValue::array(
2499 result,
2500 array_data1.pending_contracts.clone(),
2501 pos_op_inh,
2502 )
2503 .into())
2504 } else {
2505 let mut result: Array = container1
2517 .iter()
2518 .cloned()
2519 .map(|elt| {
2520 RuntimeContract::apply_all(
2521 elt,
2522 container1.iter_pending_contracts().cloned(),
2523 pos1,
2524 )
2525 .closurize(&mut self.context.cache, env1.clone())
2526 })
2527 .collect();
2528
2529 result.extend(container2.iter().cloned().map(|elt| {
2530 RuntimeContract::apply_all(
2531 elt,
2532 container2.iter_pending_contracts().cloned(),
2533 pos2,
2534 )
2535 .closurize(&mut self.context.cache, env2.clone())
2536 }));
2537
2538 Ok(NickelValue::array(result, Vec::new(), pos_op_inh).into())
2539 }
2540 }
2541 BinaryOp::ArrayAt => {
2542 let Some(container) = value1.as_array() else {
2543 return mk_type_error!("Array", 1, value1);
2544 };
2545
2546 let Some(n) = value2.as_number() else {
2547 return mk_type_error!("Number", 2, value2);
2548 };
2549
2550 let Ok(n_as_usize) = usize::try_from(n) else {
2551 return Err(Box::new(EvalErrorKind::Other(
2552 format!(
2553 "array/at expects its second argument to be a \
2554 positive integer smaller than {}, got {n}",
2555 usize::MAX
2556 ),
2557 pos_op,
2558 )));
2559 };
2560
2561 let Container::Alloc(array_data) = container else {
2562 return Err(Box::new(EvalErrorKind::Other(
2563 "array/at: index out of bounds. \
2564 Can't index into an empty array."
2565 .to_owned(),
2566 pos_op,
2567 )));
2568 };
2569
2570 if n_as_usize >= array_data.array.len() {
2571 return Err(Box::new(EvalErrorKind::Other(
2572 format!(
2573 "array/at: index out of bounds. \
2574 Expected an index between 0 and {}, got {}",
2575 array_data.array.len(),
2576 n
2577 ),
2578 pos_op,
2579 )));
2580 }
2581
2582 let elem_with_ctr = RuntimeContract::apply_all(
2583 array_data.array.get(n_as_usize).unwrap().clone(),
2584 array_data.pending_contracts.iter().cloned(),
2585 pos1.to_inherited(),
2586 );
2587
2588 Ok(Closure {
2589 value: elem_with_ctr,
2590 env: env1,
2591 })
2592 }
2593 BinaryOp::Merge(merge_label) => self.merge(
2594 value1,
2595 env1,
2596 value2,
2597 env2,
2598 pos_op,
2599 MergeMode::Standard(merge_label),
2600 ),
2601 BinaryOp::Hash => {
2602 let mk_err_fst =
2603 || mk_type_error!("[| 'Md5, 'Sha1, 'Sha256, 'Sha512 |]", 1, value1.clone());
2604
2605 let Some(enum_data) = value1.as_enum_variant() else {
2606 return mk_err_fst();
2607 };
2608
2609 if enum_data.arg.is_some() {
2610 return mk_err_fst();
2611 }
2612
2613 let Some(s) = value2.as_string() else {
2614 return mk_type_error!("String", 2, value2);
2615 };
2616
2617 let result = match enum_data.tag.as_ref() {
2618 "Md5" => {
2619 let mut hasher = md5::Md5::new();
2620 hasher.update(s.as_ref());
2621 format!("{:x}", hasher.finalize())
2622 }
2623 "Sha1" => {
2624 let mut hasher = sha1::Sha1::new();
2625 hasher.update(s.as_ref());
2626 format!("{:x}", hasher.finalize())
2627 }
2628 "Sha256" => {
2629 let mut hasher = sha2::Sha256::new();
2630 hasher.update(s.as_ref());
2631 format!("{:x}", hasher.finalize())
2632 }
2633 "Sha512" => {
2634 let mut hasher = sha2::Sha512::new();
2635 hasher.update(s.as_ref());
2636 format!("{:x}", hasher.finalize())
2637 }
2638 _ => return mk_err_fst(),
2639 };
2640
2641 Ok(NickelValue::string(result, pos_op_inh).into())
2642 }
2643 BinaryOp::Serialize => {
2644 let mk_err_fst = || mk_type_error!(ENUM_FORMAT, 1, value1.clone());
2645
2646 let Some(enum_data) = value1.as_enum_variant() else {
2647 return mk_err_fst();
2648 };
2649
2650 if enum_data.arg.is_some() {
2651 return mk_err_fst();
2652 }
2653
2654 let initial_env = Environment::new();
2656 let v2_subst = subst(
2657 &self.context.pos_table,
2658 &self.context.cache,
2659 value2,
2660 &initial_env,
2661 &env2,
2662 );
2663
2664 let format = match enum_data.tag.to_string().as_str() {
2665 "Json" => ExportFormat::Json,
2666 "Yaml" => ExportFormat::Yaml,
2667 "YamlDocuments" => ExportFormat::YamlDocuments,
2668 "Toml" => ExportFormat::Toml,
2669 _ => return mk_err_fst(),
2670 };
2671
2672 serialize::validate(format, &v2_subst)?;
2673
2674 Ok(
2675 NickelValue::string(serialize::to_string(format, &v2_subst)?, pos_op_inh)
2676 .into(),
2677 )
2678 }
2679 BinaryOp::Deserialize => {
2680 let mk_err_fst = || mk_type_error!(ENUM_FORMAT, 1, value1.clone());
2681
2682 let Some(enum_data) = value1.as_enum_variant() else {
2683 return mk_err_fst();
2684 };
2685
2686 if enum_data.arg.is_some() {
2687 return mk_err_fst();
2688 }
2689
2690 let Some(s) = value2.as_string() else {
2691 return mk_type_error!("String", 2, value2);
2692 };
2693
2694 let deser: NickelValue = match enum_data.tag.label() {
2695 "Json" => serde_json::from_str(s).map_err(|err| {
2696 Box::new(EvalErrorKind::DeserializationError(
2697 String::from("json"),
2698 format!("{err}"),
2699 pos_op,
2700 ))
2701 })?,
2702 tag @ ("Yaml" | "YamlDocuments") => {
2713 let listify = if tag == "Yaml" {
2714 Listify::Auto
2715 } else {
2716 Listify::Always
2717 };
2718 crate::serialize::yaml::load_yaml_value(
2719 &mut self.context.pos_table,
2720 s,
2721 None,
2722 listify,
2723 )
2724 .map_err(|err| {
2725 Box::new(EvalErrorKind::DeserializationErrorWithInner {
2726 format: InputFormat::Yaml,
2727 inner: err,
2728 pos: pos_op,
2729 })
2730 })?
2731 }
2732 "Toml" => toml::from_str(s).map_err(|err| {
2733 Box::new(EvalErrorKind::DeserializationError(
2734 String::from("toml"),
2735 format!("{err}"),
2736 pos_op,
2737 ))
2738 })?,
2739 _ => return mk_err_fst(),
2740 };
2741
2742 Ok(deser.with_pos_idx(pos_op_inh).into())
2743 }
2744 BinaryOp::StringSplit => self.string_fn2(
2745 |input, sep| NickelValue::array(input.split(sep), Vec::new(), pos_op_inh),
2746 Op2EvalData {
2747 op,
2748 arg1: Closure {
2749 value: value1,
2750 env: env1,
2751 },
2752 arg2: Closure {
2753 value: value2,
2754 env: env2,
2755 },
2756 orig_pos_arg1,
2757 orig_pos_arg2,
2758 pos_op,
2759 },
2760 ),
2761 BinaryOp::StringContains => self.string_fn2(
2762 |s1, s2| NickelValue::bool_value(s1.contains(s2.as_str()), pos_op_inh),
2763 Op2EvalData {
2764 op,
2765 arg1: Closure {
2766 value: value1,
2767 env: env1,
2768 },
2769 arg2: Closure {
2770 value: value2,
2771 env: env2,
2772 },
2773 orig_pos_arg1,
2774 orig_pos_arg2,
2775 pos_op,
2776 },
2777 ),
2778 BinaryOp::StringCompare => {
2779 let as_term_pos = self.context.pos_table.get(pos_op_inh);
2780
2781 self.string_fn2(
2782 |s1, s2| {
2783 use std::cmp::Ordering;
2784
2785 NickelValue::enum_tag(
2786 LocIdent::new_with_pos(
2787 match s1.cmp(s2) {
2788 Ordering::Less => "Lesser",
2789 Ordering::Equal => "Equal",
2790 Ordering::Greater => "Greater",
2791 },
2792 as_term_pos,
2793 ),
2794 pos_op_inh,
2795 )
2796 },
2797 Op2EvalData {
2798 op,
2799 arg1: Closure {
2800 value: value1,
2801 env: env1,
2802 },
2803 arg2: Closure {
2804 value: value2,
2805 env: env2,
2806 },
2807 orig_pos_arg1,
2808 orig_pos_arg2,
2809 pos_op,
2810 },
2811 )
2812 }
2813 BinaryOp::StringBase64Encode => {
2814 let mk_err_fst = || {
2815 mk_type_error!(
2816 "[| 'Standard, 'UrlSafe, 'NoPad, 'UrlSafeNoPad |]",
2817 1,
2818 value1.clone()
2819 )
2820 };
2821
2822 let Some(b64_variant) = value1.as_enum_variant() else {
2823 return mk_err_fst();
2824 };
2825
2826 if b64_variant.arg.is_some() {
2827 return mk_err_fst();
2828 }
2829
2830 let Some(s) = value2.as_string() else {
2831 return mk_type_error!("String", 2, value2);
2832 };
2833
2834 let result = match b64_variant.tag.as_ref() {
2835 "Standard" => base64::prelude::BASE64_STANDARD.encode(s.as_str()),
2836 "UrlSafe" => base64::prelude::BASE64_URL_SAFE.encode(s.as_str()),
2837 "NoPad" => base64::prelude::BASE64_STANDARD_NO_PAD.encode(s.as_str()),
2838 "UrlSafeNoPad" => base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(s.as_str()),
2839 _ => return mk_err_fst(),
2840 };
2841
2842 Ok(NickelValue::string(result, pos_op_inh).into())
2843 }
2844 BinaryOp::StringBase64Decode => {
2845 let mk_err_fst = || {
2846 mk_type_error!(
2847 "[| 'Standard, 'UrlSafe, 'NoPad, 'UrlSafeNoPad |]",
2848 1,
2849 value1.clone()
2850 )
2851 };
2852
2853 let Some(b64_variant) = value1.as_enum_variant() else {
2854 return mk_err_fst();
2855 };
2856
2857 if b64_variant.arg.is_some() {
2858 return mk_err_fst();
2859 }
2860
2861 let Some(s) = value2.as_string() else {
2862 return mk_type_error!("String", 2, value2);
2863 };
2864
2865 let decode_with = |engine: base64::engine::GeneralPurpose| {
2866 let decode_result = engine
2867 .decode(s.as_str())
2868 .map_err(|err| format!("Base64 decoding failed: {err}"))
2869 .and_then(|decoded_bytes| {
2870 String::from_utf8(decoded_bytes).map_err(|err| {
2871 format!("Could not convert decoded value to string: {err}")
2872 })
2873 });
2874 match decode_result {
2875 Ok(decoded_string) => Ok(NickelValue::enum_variant(
2876 "Ok",
2877 Some(NickelValue::string(decoded_string, pos_op_inh)),
2878 pos_op_inh,
2879 )
2880 .into()),
2881 Err(err) => Ok(NickelValue::enum_variant(
2882 "Error",
2883 Some(
2884 mk_record!(("message", NickelValue::string_posless(err)))
2885 .with_pos_idx(pos_op_inh),
2886 ),
2887 pos_op_inh,
2888 )
2889 .into()),
2890 }
2891 };
2892
2893 match b64_variant.tag.as_ref() {
2894 "Standard" => decode_with(base64::prelude::BASE64_STANDARD),
2895 "UrlSafe" => decode_with(base64::prelude::BASE64_URL_SAFE),
2896 "NoPad" => decode_with(base64::prelude::BASE64_STANDARD_NO_PAD),
2897 "UrlSafeNoPad" => decode_with(base64::prelude::BASE64_URL_SAFE_NO_PAD),
2898 _ => mk_err_fst(),
2899 }
2900 }
2901 BinaryOp::ContractArrayLazyApp => {
2902 let (ctr, _) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
2903 Box::new(EvalErrorKind::NotEnoughArgs(
2904 3,
2905 String::from("contract/array_lazy_app"),
2906 pos_op,
2907 ))
2908 })?;
2909
2910 let Closure {
2911 value: ctr_val,
2912 env: env_ctr,
2913 } = ctr;
2914
2915 let Some(label) = value1.as_label() else {
2916 return mk_type_error!("Label", 1, value1);
2917 };
2918
2919 if value2.is_inline_empty_array() {
2920 value2 = NickelValue::empty_array_block(pos2);
2923 }
2924
2925 let ValueContentRefMut::Array(Container::Alloc(array_data)) =
2926 value2.content_make_mut()
2927 else {
2928 return mk_type_error!("Array", 2, value2);
2931 };
2932
2933 let contract = ctr_val.closurize(&mut self.context.cache, env_ctr);
2935 RuntimeContract::push_dedup(
2936 &mut array_data.pending_contracts,
2937 &env2,
2938 RuntimeContract::new(contract, label.clone()),
2939 &Environment::new(),
2940 );
2941
2942 let array_with_ctr = Closure {
2943 value: value2,
2944 env: env2,
2945 };
2946
2947 Ok(array_with_ctr)
2948 }
2949 BinaryOp::ContractRecordLazyApp => {
2950 let (
2953 Closure {
2954 value: ctr_val,
2955 env: ctr_env,
2956 },
2957 _,
2958 ) = self.stack.pop_arg(&self.context.cache).ok_or_else(|| {
2959 Box::new(EvalErrorKind::NotEnoughArgs(
2960 3,
2961 String::from("contract/record_lazy_app"),
2962 pos_op,
2963 ))
2964 })?;
2965
2966 let Some(label) = value1.as_label() else {
2967 return mk_type_error!("Label", 1, value1);
2968 };
2969
2970 let Some(container) = value2.as_record() else {
2971 return mk_type_error!("Record", 2, value2);
2972 };
2973
2974 let Container::Alloc(record_data) = container else {
2975 return Ok(NickelValue::empty_record().with_pos_idx(pos2).into());
2976 };
2977
2978 let mut record_data = record_data.clone();
2979
2980 record_data.attrs.frozen = false;
2984
2985 let mut contract_at_field = |id: LocIdent| {
2986 let pos = ctr_val.pos_idx();
2987 mk_app!(
2988 ctr_val.clone(),
2989 NickelValue::string(id, self.context.pos_table.push(id.pos))
2990 )
2991 .with_pos_idx(pos)
2992 .closurize(&mut self.context.cache, ctr_env.clone())
2993 };
2994
2995 for (id, field) in record_data.fields.iter_mut() {
2996 let runtime_ctr = RuntimeContract {
2997 contract: contract_at_field(*id),
2998 label: label.clone(),
2999 };
3000
3001 RuntimeContract::push_dedup(
3002 &mut field.pending_contracts,
3003 &env2,
3004 runtime_ctr,
3005 &ctr_env,
3006 );
3007 }
3008
3009 let reverted = super::fixpoint::revert(&mut self.context.cache, record_data);
3018
3019 Ok(NickelValue::term(reverted, pos2).into())
3020 }
3021 BinaryOp::LabelWithMessage => {
3022 let Some(message) = value1.as_string() else {
3023 return mk_type_error!("String", 1, value1);
3024 };
3025
3026 let ValueContentRefMut::Label(label) = value2.content_make_mut() else {
3027 return mk_type_error!("Label", 2, value2);
3028 };
3029
3030 label.set_diagnostic_message(message.clone().into_inner());
3031 Ok(value2.into())
3032 }
3033 BinaryOp::LabelWithNotes => {
3034 let val1_subst = subst(
3038 &self.context.pos_table,
3039 &self.context.cache,
3040 value1.clone(),
3041 &Environment::new(),
3042 &env1,
3043 );
3044
3045 let Some(array_data) = val1_subst.as_array() else {
3046 return mk_type_error!("Array", 1, value1);
3047 };
3048
3049 let ValueContentRefMut::Label(label) = value2.content_make_mut() else {
3050 return mk_type_error!("Label", 2, value2);
3051 };
3052
3053 let notes = array_data
3054 .iter()
3055 .map(|element| {
3056 if let Some(s) = element.as_string() {
3057 Ok(s.clone().into_inner())
3058 } else {
3059 mk_type_error!("String", 1, element.clone())
3060 }
3061 })
3062 .collect::<Result<Vec<_>, _>>()?;
3063 label.set_diagnostic_notes(notes);
3064
3065 Ok(value2.into())
3066 }
3067 BinaryOp::LabelAppendNote => {
3068 let Some(note) = value1.as_string() else {
3069 return mk_type_error!("String", 1, value1);
3070 };
3071
3072 let ValueContentRefMut::Label(label) = value2.content_make_mut() else {
3073 return mk_type_error!("Label", 2, value2);
3074 };
3075
3076 label.append_diagnostic_note(note);
3077 Ok(value2.into())
3078 }
3079 BinaryOp::LabelLookupTypeVar => {
3080 let Some(key) = value1.as_sealing_key() else {
3081 return mk_type_error!("SealingKey", 1, value1);
3082 };
3083
3084 let Some(label) = value2.as_label() else {
3085 return mk_type_error!("Label", 2, value2);
3086 };
3087
3088 Ok(NickelValue::from(
3089 label
3090 .type_environment
3091 .iter()
3092 .rev()
3095 .find_map(|(k, data)| (k == key).then_some(data))
3096 .unwrap(),
3097 )
3098 .with_pos_idx(pos_op_inh)
3099 .into())
3100 }
3101 BinaryOp::RecordSplitPair => {
3102 let Some(cont1) = value1.as_record() else {
3103 return mk_type_error!("Record", 1, value1);
3104 };
3105
3106 let Some(cont2) = value2.as_record() else {
3107 return mk_type_error!("Record", 2, value2);
3108 };
3109
3110 let record1 = cont1.into_opt();
3111 let record2 = cont2.into_opt();
3112
3113 let split::SplitResult {
3114 left,
3115 center,
3116 right,
3117 } = split::split_ref(
3118 record1
3119 .as_ref()
3120 .map(|r| &r.fields)
3121 .unwrap_or(&Default::default()),
3122 record2
3123 .as_ref()
3124 .map(|r| &r.fields)
3125 .unwrap_or(&Default::default()),
3126 );
3127
3128 let left_only = NickelValue::record_posless(RecordData {
3129 fields: left,
3130 sealed_tail: record1
3131 .as_ref()
3132 .map(|r| r.sealed_tail.clone())
3133 .unwrap_or_default(),
3134 attrs: record1.as_ref().map(|r| r.attrs).unwrap_or_default(),
3135 });
3136
3137 let right_only = NickelValue::record_posless(RecordData {
3138 fields: right,
3139 sealed_tail: record2
3140 .as_ref()
3141 .map(|r| r.sealed_tail.clone())
3142 .unwrap_or_default(),
3143 attrs: record2.as_ref().map(|r| r.attrs).unwrap_or_default(),
3144 });
3145
3146 let (center1, center2): (IndexMap<LocIdent, Field>, IndexMap<LocIdent, Field>) =
3147 center
3148 .into_iter()
3149 .map(|(id, (left, right))| ((id, left), (id, right)))
3150 .unzip();
3151
3152 let left_center = NickelValue::record_posless(RecordData {
3153 fields: center1,
3154 sealed_tail: None,
3155 attrs: RecordAttrs::default(),
3156 });
3157
3158 let right_center = NickelValue::record_posless(RecordData {
3159 fields: center2,
3160 sealed_tail: None,
3161 attrs: RecordAttrs::default(),
3162 });
3163
3164 Ok(closurize_container(NickelValue::record(
3165 RecordData {
3166 fields: IndexMap::from([
3167 (LocIdent::from("left_only"), Field::from(left_only)),
3168 (LocIdent::from("left_center"), Field::from(left_center)),
3169 (LocIdent::from("right_center"), Field::from(right_center)),
3170 (LocIdent::from("right_only"), Field::from(right_only)),
3171 ]),
3172 attrs: RecordAttrs::default(),
3173 sealed_tail: None,
3174 },
3175 pos_op_inh,
3176 ))
3177 .into())
3178 }
3179 BinaryOp::RecordDisjointMerge => {
3180 let Some(container1) = value1.as_record() else {
3181 return mk_type_error!("Record", 1, value1);
3182 };
3183
3184 let Some(container2) = value2.as_record() else {
3185 return mk_type_error!("Record", 2, value2);
3186 };
3187
3188 let (record1, record2) = match (container1, container2) {
3189 (Container::Alloc(record1), Container::Alloc(record2)) => (record1, record2),
3190 (Container::Empty, _) => {
3191 return Ok(Closure {
3192 value: value2.with_pos_idx(pos_op_inh),
3193 env: env2,
3194 });
3195 }
3196 (_, Container::Empty) => {
3197 return Ok(Closure {
3198 value: value1.with_pos_idx(pos_op_inh),
3199 env: env1,
3200 });
3201 }
3202 };
3203
3204 let sealed_tail = match (&record1.sealed_tail, &record2.sealed_tail) {
3214 (Some(tail1), Some(_)) => {
3215 return Err(Box::new(EvalErrorKind::IllegalPolymorphicTailAccess {
3216 action: IllegalPolymorphicTailAction::Merge,
3217 evaluated_arg: tail1.label.get_evaluated_arg(&self.context.cache),
3218 label: tail1.label.clone(),
3219 }));
3220 }
3221 (tail1, tail2) => tail1.as_ref().or(tail2.as_ref()).cloned(),
3222 };
3223
3224 let mut fields =
3229 IndexMap::with_capacity(record1.fields.len() + record2.fields.len());
3230
3231 fields.extend(record1.fields.iter().map(|(k, v)| (*k, v.clone())));
3232 fields.extend(record2.fields.iter().map(|(k, v)| (*k, v.clone())));
3233
3234 let attrs = Combine::combine(record1.attrs, record2.attrs);
3235
3236 Ok(NickelValue::record(
3237 RecordData {
3238 fields,
3239 attrs,
3240 sealed_tail,
3241 },
3242 pos_op_inh,
3243 )
3244 .into())
3245 }
3246 }
3247 }
3248
3249 fn number_cmp2<F>(&mut self, f: F, eval_data: Op2EvalData) -> Result<Closure, ErrorKind>
3250 where
3251 F: Fn(&Number, &Number) -> bool,
3252 {
3253 let pos_op_inh = eval_data.pos_op.to_inherited();
3254
3255 self.number_fn2(
3256 |n1, n2| NickelValue::bool_value(f(n1, n2), pos_op_inh),
3257 eval_data,
3258 )
3259 }
3260
3261 fn number_op2<F>(&mut self, f: F, eval_data: Op2EvalData) -> Result<Closure, ErrorKind>
3262 where
3263 F: Fn(&Number, &Number) -> Number,
3264 {
3265 let pos_op_inh = eval_data.pos_op.to_inherited();
3266
3267 self.number_fn2(
3268 |n1, n2| NickelValue::number(f(n1, n2), pos_op_inh),
3269 eval_data,
3270 )
3271 }
3272
3273 fn number_fn2<F>(&mut self, f: F, eval_data: Op2EvalData) -> Result<Closure, ErrorKind>
3274 where
3275 F: Fn(&Number, &Number) -> NickelValue,
3276 {
3277 let Op2EvalData {
3278 op,
3279 arg1: Closure {
3280 value: value1,
3281 env: _,
3282 },
3283 arg2: Closure {
3284 value: value2,
3285 env: _,
3286 },
3287 orig_pos_arg1,
3288 orig_pos_arg2,
3289 pos_op,
3290 } = eval_data;
3291
3292 let Some(n1) = value1.as_number() else {
3293 return Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
3294 primop: op.to_string(),
3295 expected: "Number".to_owned(),
3296 arg_number: 1,
3297 pos_arg: orig_pos_arg1,
3298 arg_evaluated: value1,
3299 pos_op,
3300 }));
3301 };
3302
3303 let Some(n2) = value2.as_number() else {
3304 return Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
3305 primop: op.to_string(),
3306 expected: "Number".to_owned(),
3307 arg_number: 2,
3308 pos_arg: orig_pos_arg2,
3309 arg_evaluated: value2,
3310 pos_op,
3311 }));
3312 };
3313
3314 Ok(f(n1, n2).into())
3315 }
3316
3317 fn string_fn2<F>(&mut self, f: F, eval_data: Op2EvalData) -> Result<Closure, ErrorKind>
3318 where
3319 F: Fn(&NickelString, &NickelString) -> NickelValue,
3320 {
3321 let Op2EvalData {
3322 op,
3323 arg1: Closure {
3324 value: value1,
3325 env: _,
3326 },
3327 arg2: Closure {
3328 value: value2,
3329 env: _,
3330 },
3331 orig_pos_arg1,
3332 orig_pos_arg2,
3333 pos_op,
3334 } = eval_data;
3335
3336 let Some(s1) = value1.as_string() else {
3337 return Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
3338 primop: op.to_string(),
3339 expected: "String".to_owned(),
3340 arg_number: 1,
3341 pos_arg: orig_pos_arg1,
3342 arg_evaluated: value1,
3343 pos_op,
3344 }));
3345 };
3346
3347 let Some(s2) = value2.as_string() else {
3348 return Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
3349 primop: op.to_string(),
3350 expected: "String".to_owned(),
3351 arg_number: 2,
3352 pos_arg: orig_pos_arg2,
3353 arg_evaluated: value2,
3354 pos_op,
3355 }));
3356 };
3357
3358 Ok(f(s1, s2).into())
3359 }
3360
3361 fn eval_opn(&mut self, eval_data: OpNEvalData) -> Result<Closure, ErrorKind> {
3365 let OpNEvalData { op, args, pos_op } = eval_data;
3366 increment!(format!("primop:{op}"));
3367 let pos_op_inh = pos_op.to_inherited();
3368
3369 let mk_type_error =
3370 |expected: &str, arg_number: usize, pos_arg: PosIdx, arg_evaluated: NickelValue| {
3371 Err(Box::new(EvalErrorKind::NAryPrimopTypeError {
3372 primop: op.to_string(),
3373 expected: expected.to_owned(),
3374 arg_number,
3375 pos_arg,
3376 arg_evaluated,
3377 pos_op,
3378 }))
3379 };
3380
3381 match op {
3384 NAryOp::StringReplace | NAryOp::StringReplaceRegex => {
3385 let mut args_wo_env = args.into_iter().map(|(arg, pos)| (arg.value, pos));
3386 let (arg1, arg_pos1) = args_wo_env.next().unwrap();
3387 let (arg2, arg_pos2) = args_wo_env.next().unwrap();
3388 let (arg3, arg_pos3) = args_wo_env.next().unwrap();
3389 debug_assert!(args_wo_env.next().is_none());
3390
3391 let Some(s) = arg1.as_string() else {
3392 return mk_type_error("String", 1, arg_pos1, arg1);
3393 };
3394
3395 let Some(from) = arg2.as_string() else {
3396 return mk_type_error("String", 2, arg_pos2, arg2);
3397 };
3398
3399 let Some(to) = arg3.as_string() else {
3400 return mk_type_error("String", 3, arg_pos3, arg3);
3401 };
3402
3403 let result = if let NAryOp::StringReplace = op {
3404 s.replace(from.as_str(), to.as_str())
3405 } else {
3406 let re = regex::Regex::new(from)
3407 .map_err(|err| Box::new(EvalErrorKind::Other(err.to_string(), pos_op)))?;
3408
3409 s.replace_regex(&CompiledRegex(re), to)
3410 };
3411
3412 Ok(NickelValue::string(result, pos_op_inh).into())
3413 }
3414 NAryOp::StringSubstr => {
3415 let mut args_wo_env = args.into_iter().map(|(arg, pos)| (arg.value, pos));
3416 let (arg1, arg_pos1) = args_wo_env.next().unwrap();
3417 let (arg2, arg_pos2) = args_wo_env.next().unwrap();
3418 let (arg3, arg_pos3) = args_wo_env.next().unwrap();
3419 debug_assert!(args_wo_env.next().is_none());
3420
3421 let Some(s) = arg1.as_string() else {
3422 return mk_type_error("String", 1, arg_pos1, arg1);
3423 };
3424
3425 let Some(start) = arg2.as_number() else {
3426 return mk_type_error("Number", 2, arg_pos2, arg2);
3427 };
3428
3429 let Some(end) = arg3.as_number() else {
3430 return mk_type_error("Number", 3, arg_pos3, arg3);
3431 };
3432
3433 s.substring(start, end)
3434 .map(|substr| NickelValue::string(substr, pos_op_inh).into())
3435 .map_err(|e| Box::new(EvalErrorKind::Other(format!("{e}"), pos_op)))
3436 }
3437 NAryOp::MergeContract => {
3438 let mut args_iter = args.into_iter();
3439
3440 let (
3441 Closure {
3442 value: arg1,
3443 env: _,
3444 },
3445 _,
3446 ) = args_iter.next().unwrap();
3447
3448 let (
3449 Closure {
3450 value: arg2,
3451 env: env2,
3452 },
3453 _,
3454 ) = args_iter.next().unwrap();
3455
3456 let (
3457 Closure {
3458 value: arg3,
3459 env: env3,
3460 },
3461 _,
3462 ) = args_iter.next().unwrap();
3463
3464 debug_assert!(args_iter.next().is_none());
3465
3466 let Some(label) = arg1.as_label() else {
3467 return Err(Box::new(EvalErrorKind::InternalError(
3468 format!(
3469 "The {op} operator was expecting \
3470 a first argument of type Label, got {}",
3471 arg1.type_of().unwrap_or("<unevaluated>")
3472 ),
3473 pos_op,
3474 )));
3475 };
3476
3477 self.merge(
3478 arg2,
3479 env2,
3480 arg3,
3481 env3,
3482 pos_op,
3483 MergeMode::Contract(label.clone()),
3484 )
3485 }
3486 NAryOp::RecordSealTail => {
3487 let mut args = args.into_iter();
3488 let (
3489 Closure {
3490 value: arg1,
3491 env: _,
3492 },
3493 arg_pos1,
3494 ) = args.next().unwrap();
3495
3496 let (
3497 Closure {
3498 value: arg2,
3499 env: _,
3500 },
3501 arg_pos2,
3502 ) = args.next().unwrap();
3503
3504 let (
3505 Closure {
3506 value: mut arg3,
3507 env: env3,
3508 },
3509 arg_pos3,
3510 ) = args.next().unwrap();
3511
3512 let (
3513 Closure {
3514 value: arg4,
3515 env: env4,
3516 },
3517 arg_pos4,
3518 ) = args.next().unwrap();
3519
3520 debug_assert!(args.next().is_none());
3521
3522 let Some(s) = arg1.as_sealing_key() else {
3523 return mk_type_error("SealingKey", 1, arg_pos1, arg1);
3524 };
3525
3526 let Some(label) = arg2.as_label() else {
3527 return mk_type_error("Label", 2, arg_pos2, arg2);
3528 };
3529
3530 if arg3.is_inline_empty_record() {
3531 arg3 = NickelValue::empty_record_block(arg3.pos_idx());
3534 }
3535
3536 let ValueContentRefMut::Record(Container::Alloc(r)) = arg3.content_make_mut()
3537 else {
3538 return mk_type_error("Record", 3, arg_pos3, arg3);
3539 };
3540
3541 let Some(tail) = arg4.as_record() else {
3545 return mk_type_error("Record", 4, arg_pos4, arg4);
3546 };
3547
3548 let tail_closurized = arg4.clone().closurize(&mut self.context.cache, env4);
3549 let fields = tail
3550 .into_opt()
3551 .map(|r| r.fields.keys().map(|s| s.ident()).collect())
3552 .unwrap_or_default();
3553 r.sealed_tail = Some(Rc::new(record::SealedTail::new(
3554 *s,
3555 label.clone(),
3556 tail_closurized,
3557 fields,
3558 )));
3559
3560 Ok(Closure {
3561 value: arg3,
3562 env: env3,
3563 })
3564 }
3565 NAryOp::RecordUnsealTail => {
3566 let mut args = args.into_iter();
3567 let (
3568 Closure {
3569 value: arg1,
3570 env: _,
3571 },
3572 arg_pos1,
3573 ) = args.next().unwrap();
3574 let (
3575 Closure {
3576 value: arg2,
3577 env: _,
3578 },
3579 arg_pos2,
3580 ) = args.next().unwrap();
3581 let (
3582 Closure {
3583 value: arg3,
3584 env: env3,
3585 },
3586 arg_pos3,
3587 ) = args.next().unwrap();
3588
3589 debug_assert!(args.next().is_none());
3590
3591 let Some(s) = arg1.as_sealing_key() else {
3592 return mk_type_error("SealingKey", 1, arg_pos1, arg1);
3593 };
3594
3595 let Some(label) = arg2.as_label() else {
3596 return mk_type_error("Label", 2, arg_pos2, arg2);
3597 };
3598
3599 let Some(container) = arg3.as_record() else {
3600 return mk_type_error("Record", 3, arg_pos3, arg3);
3601 };
3602
3603 container
3604 .into_opt()
3605 .and_then(|record| record.sealed_tail.as_ref())
3606 .and_then(|tail| tail.unseal(s).cloned())
3607 .ok_or_else(|| {
3608 Box::new(EvalErrorKind::BlameError {
3609 evaluated_arg: label.get_evaluated_arg(&self.context.cache),
3610 label: label.clone(),
3611 })
3612 })
3613 .map(|tail_unsealed| Closure {
3614 value: tail_unsealed,
3615 env: env3,
3616 })
3617 }
3618 NAryOp::LabelInsertTypeVar => {
3619 let mut args = args.into_iter();
3620
3621 let (
3622 Closure {
3623 value: arg1,
3624 env: _,
3625 },
3626 arg_pos1,
3627 ) = args.next().unwrap();
3628
3629 let (
3630 Closure {
3631 value: arg2,
3632 env: _,
3633 },
3634 arg_pos2,
3635 ) = args.next().unwrap();
3636
3637 let (
3638 Closure {
3639 value: mut arg3,
3640 env: _,
3641 },
3642 arg_pos3,
3643 ) = args.next().unwrap();
3644
3645 debug_assert!(args.next().is_none());
3646
3647 let Some(key) = arg1.as_sealing_key() else {
3648 return mk_type_error("SealingKey", 1, arg_pos1, arg1);
3649 };
3650
3651 let Ok(polarity) = Polarity::try_from(&arg2) else {
3652 return mk_type_error("Polarity", 2, arg_pos2, arg2);
3653 };
3654
3655 let ValueContentRefMut::Label(label) = arg3.content_make_mut() else {
3656 return mk_type_error("Label", 3, arg_pos3, arg3);
3657 };
3658
3659 label
3660 .type_environment
3661 .push((*key, TypeVarData { polarity }));
3662
3663 Ok(arg3.with_pos_idx(arg_pos3.to_inherited()).into())
3664 }
3665 NAryOp::ArraySlice => {
3666 let mut args = args.into_iter();
3667
3668 let (
3669 Closure {
3670 value: arg1,
3671 env: _,
3672 },
3673 arg_pos1,
3674 ) = args.next().unwrap();
3675
3676 let (
3677 Closure {
3678 value: arg2,
3679 env: _,
3680 },
3681 arg_pos2,
3682 ) = args.next().unwrap();
3683
3684 let (
3685 Closure {
3686 value: mut arg3,
3687 env: env3,
3688 },
3689 arg_pos3,
3690 ) = args.next().unwrap();
3691
3692 debug_assert!(args.next().is_none());
3693
3694 let Some(start) = arg1.as_number() else {
3695 return mk_type_error("Number", 1, arg_pos1, arg1);
3696 };
3697
3698 let Some(end) = arg2.as_number() else {
3699 return mk_type_error("Number", 2, arg_pos2, arg2);
3700 };
3701
3702 if arg3.is_inline_empty_array() {
3703 arg3 = NickelValue::empty_array_block(arg3.pos_idx());
3706 }
3707
3708 let ValueContentRefMut::Array(Container::Alloc(ArrayData { array, .. })) =
3709 arg3.content_make_mut()
3710 else {
3711 return mk_type_error("Array", 3, arg_pos3, arg3);
3712 };
3713
3714 let Ok(start_as_usize) = usize::try_from(start) else {
3715 return Err(Box::new(EvalErrorKind::Other(
3716 format!(
3717 "{op} expects its first argument (start) to be a \
3718 positive integer smaller than {}, got {start}",
3719 usize::MAX
3720 ),
3721 pos_op,
3722 )));
3723 };
3724
3725 let Ok(end_as_usize) = usize::try_from(end) else {
3726 return Err(Box::new(EvalErrorKind::Other(
3727 format!(
3728 "{op} expects its second argument (end) to be a \
3729 positive integer smaller than {}, got {end}",
3730 usize::MAX
3731 ),
3732 pos_op,
3733 )));
3734 };
3735
3736 if end_as_usize < start_as_usize || end_as_usize > array.len() {
3737 return Err(Box::new(EvalErrorKind::Other(
3738 format!(
3739 "{op}: index out of bounds. Expected `start <= end <= {}`, but \
3740 got `start={start}` and `end={end}`.",
3741 array.len()
3742 ),
3743 pos_op,
3744 )));
3745 }
3746
3747 array.slice(start_as_usize, end_as_usize);
3748
3749 Ok(Closure {
3750 value: arg3.with_pos_idx(pos_op_inh),
3751 env: env3,
3752 })
3753 }
3754 }
3755 }
3756}
3757
3758fn type_tag(v: &NickelValue) -> &'static str {
3764 match v.content_ref() {
3765 ValueContentRef::Null => "Other",
3766 ValueContentRef::Bool(_) => "Bool",
3767 ValueContentRef::Number(_) => "Number",
3768 ValueContentRef::Array(_) => "Array",
3769 ValueContentRef::Record(_) => "Record",
3770 ValueContentRef::String(_) => "String",
3771 ValueContentRef::Term(term) => match term {
3772 Term::RecRecord(..) => "Record",
3773 Term::Fun(..) => "Function",
3774 _ => "Other",
3775 },
3776 ValueContentRef::Label(_) => "Label",
3777 ValueContentRef::EnumVariant(_) => "Enum",
3778 ValueContentRef::ForeignId(_) => "ForeignId",
3779 ValueContentRef::CustomContract(_) => "CustomContract",
3780 ValueContentRef::Type(_) => "Type",
3781 _ => "Other",
3782 }
3783}
3784
3785fn eq<C: Cache>(
3816 cache: &mut C,
3817 c1: Closure,
3818 c2: Closure,
3819 pos_op: PosIdx,
3820) -> Result<EqResult, ErrorKind> {
3821 let Closure {
3822 value: value1,
3823 env: env1,
3824 } = c1;
3825
3826 let Closure {
3827 value: value2,
3828 env: env2,
3829 } = c2;
3830
3831 fn gen_eqs<I, C: Cache>(
3834 cache: &mut C,
3835 mut it: I,
3836 env1: Environment,
3837 env2: Environment,
3838 ) -> EqResult
3839 where
3840 I: Iterator<Item = (NickelValue, NickelValue)>,
3841 {
3842 if let Some((v1, v2)) = it.next() {
3843 let eqs = it
3844 .map(|(v1, v2)| {
3845 (
3846 Closure {
3847 value: v1,
3848 env: env1.clone(),
3849 },
3850 Closure {
3851 value: v2,
3852 env: env2.clone(),
3853 },
3854 )
3855 })
3856 .collect();
3857
3858 EqResult::Eqs(v1.closurize(cache, env1), v2.closurize(cache, env2), eqs)
3859 } else {
3860 EqResult::Bool(true)
3861 }
3862 }
3863
3864 match (value1.content_ref(), value2.content_ref()) {
3865 (ValueContentRef::Null, ValueContentRef::Null) => Ok(EqResult::Bool(true)),
3866 (ValueContentRef::Bool(b1), ValueContentRef::Bool(b2)) => Ok(EqResult::Bool(b1 == b2)),
3867 (ValueContentRef::Number(n1), ValueContentRef::Number(n2)) => Ok(EqResult::Bool(n1 == n2)),
3868 (ValueContentRef::String(s1), ValueContentRef::String(s2)) => Ok(EqResult::Bool(s1 == s2)),
3869 (ValueContentRef::Label(l1), ValueContentRef::Label(l2)) => Ok(EqResult::Bool(l1 == l2)),
3870 (ValueContentRef::SealingKey(k1), ValueContentRef::SealingKey(k2)) => {
3871 Ok(EqResult::Bool(k1 == k2))
3872 }
3873 (
3874 ValueContentRef::EnumVariant(EnumVariantData {
3875 tag: tag1,
3876 arg: None,
3877 }),
3878 ValueContentRef::EnumVariant(EnumVariantData {
3879 tag: tag2,
3880 arg: None,
3881 }),
3882 ) => Ok(EqResult::Bool(tag1.ident() == tag2.ident())),
3883 (
3884 ValueContentRef::EnumVariant(EnumVariantData {
3885 tag: tag1,
3886 arg: Some(arg1),
3887 }),
3888 ValueContentRef::EnumVariant(EnumVariantData {
3889 tag: tag2,
3890 arg: Some(arg2),
3891 }),
3892 ) if tag1.ident() == tag2.ident() => Ok(gen_eqs(
3893 cache,
3894 std::iter::once((arg1.clone(), arg2.clone())),
3895 env1,
3896 env2,
3897 )),
3898 (ValueContentRef::Record(container1), ValueContentRef::Record(container2))
3904 if container1.has_only_empty_opts() && container2.has_only_empty_opts() =>
3905 {
3906 Ok(EqResult::Bool(true))
3907 }
3908 (
3909 ValueContentRef::Record(Container::Alloc(r1)),
3910 ValueContentRef::Record(Container::Alloc(r2)),
3911 ) => {
3912 let merge::split::SplitResult {
3913 left,
3914 center,
3915 right,
3916 } = merge::split::split_ref(&r1.fields, &r2.fields);
3917
3918 if !left.values().all(Field::is_empty_optional)
3920 || !right.values().all(Field::is_empty_optional)
3921 {
3922 Ok(EqResult::Bool(false))
3923 } else if center.is_empty() {
3924 Ok(EqResult::Bool(true))
3925 } else {
3926 let eqs: Result<Vec<_>, _> = center
3930 .into_iter()
3931 .filter_map(|(id, (field1, field2))| match (field1, field2) {
3932 (
3933 Field {
3934 value: Some(value1),
3935 pending_contracts: pending_contracts1,
3936 ..
3937 },
3938 Field {
3939 value: Some(value2),
3940 pending_contracts: pending_contracts2,
3941 ..
3942 },
3943 ) => {
3944 let pos1 = value1.pos_idx();
3945 let pos2 = value2.pos_idx();
3946
3947 let value1_with_ctr =
3948 RuntimeContract::apply_all(value1, pending_contracts1, pos1);
3949 let value2_with_ctr =
3950 RuntimeContract::apply_all(value2, pending_contracts2, pos2);
3951 Some(Ok((value1_with_ctr, value2_with_ctr)))
3952 }
3953 (Field { value: None, .. }, Field { value: None, .. }) => None,
3954 (
3955 Field {
3956 value: v1 @ None,
3957 metadata,
3958 ..
3959 },
3960 Field { value: Some(_), .. },
3961 )
3962 | (
3963 Field {
3964 value: v1 @ Some(_),
3965 ..
3966 },
3967 Field {
3968 value: None,
3969 metadata,
3970 ..
3971 },
3972 ) => {
3973 let pos_record = if v1.is_none() {
3974 value1.pos_idx()
3975 } else {
3976 value2.pos_idx()
3977 };
3978
3979 Some(Err(Box::new(EvalErrorKind::MissingFieldDef {
3980 id,
3981 metadata: metadata.into_inner(),
3982 pos_record,
3983 pos_access: pos_op,
3984 })))
3985 }
3986 })
3987 .collect();
3988
3989 Ok(gen_eqs(cache, eqs?.into_iter(), env1, env2))
3990 }
3991 }
3992 (ValueContentRef::Array(container1), ValueContentRef::Array(container2))
3994 if container1.is_empty() && container2.is_empty() =>
3995 {
3996 Ok(EqResult::Bool(true))
3997 }
3998 (
3999 ValueContentRef::Array(Container::Alloc(array_data1)),
4000 ValueContentRef::Array(Container::Alloc(array_data2)),
4001 ) if array_data1.array.len() == array_data2.array.len() => {
4002 let mut eqs = array_data1
4009 .array
4010 .iter()
4011 .cloned()
4012 .map(|elt| {
4013 let pos = elt.pos_idx().to_inherited();
4014 RuntimeContract::apply_all(
4015 elt,
4016 array_data1.pending_contracts.iter().cloned(),
4017 pos,
4018 )
4019 .closurize(cache, env1.clone())
4020 })
4021 .collect::<Vec<_>>()
4022 .into_iter()
4023 .zip(array_data2.array.iter().cloned().map(|elt| {
4024 let pos = elt.pos_idx().to_inherited();
4025 RuntimeContract::apply_all(
4026 elt,
4027 array_data2.pending_contracts.iter().cloned(),
4028 pos,
4029 )
4030 .closurize(cache, env2.clone())
4031 }))
4032 .collect::<Vec<_>>();
4033
4034 match eqs.pop() {
4035 None => Ok(EqResult::Bool(true)),
4036 Some((v1, v2)) => {
4037 let eqs = eqs
4038 .into_iter()
4039 .map(|(v1, v2)| (v1.into(), v2.into()))
4040 .collect::<Vec<(Closure, Closure)>>();
4041
4042 Ok(EqResult::Eqs(v1, v2, eqs))
4043 }
4044 }
4045 }
4046 (ValueContentRef::ForeignId(_), ValueContentRef::ForeignId(_))
4048 | (ValueContentRef::CustomContract(_), ValueContentRef::CustomContract(_))
4049 | (ValueContentRef::Term(Term::Fun(..)), ValueContentRef::Term(Term::Fun(..))) => {
4050 Err(Box::new(EvalErrorKind::IncomparableValues {
4051 eq_pos: pos_op,
4052 left: value1,
4053 right: value2,
4054 }))
4055 }
4056 (_, _) => Ok(EqResult::Bool(false)),
4057 }
4058}
4059
4060fn eta_expand(op: UnaryOp, pos_op: PosIdx) -> Term {
4067 let param = LocIdent::fresh();
4068
4069 Term::fun(
4070 param,
4071 NickelValue::term(
4072 Term::op1(op, NickelValue::term(Term::Var(param), pos_op)),
4073 pos_op,
4074 ),
4075 )
4076}
4077
4078trait MapValuesClosurize: Sized {
4079 fn map_values_closurize<F, C: Cache>(
4088 self,
4089 cache: &mut C,
4090 env: &Environment,
4091 f: F,
4092 ) -> Result<IndexMap<LocIdent, Field>, record::MissingFieldDefError>
4093 where
4094 F: FnMut(LocIdent, NickelValue) -> NickelValue;
4095}
4096
4097impl<Iter> MapValuesClosurize for Iter
4098where
4099 Iter: IntoIterator<Item = (LocIdent, Field)>,
4100{
4101 fn map_values_closurize<F, C: Cache>(
4102 self,
4103 cache: &mut C,
4104 env: &Environment,
4105 mut f: F,
4106 ) -> Result<IndexMap<LocIdent, Field>, record::MissingFieldDefError>
4107 where
4108 F: FnMut(LocIdent, NickelValue) -> NickelValue,
4109 {
4110 self.into_iter()
4111 .map(|(id, field)| {
4112 let value = field
4113 .value
4114 .map(|value| {
4115 let pos = value.pos_idx();
4116 let value_with_ctrs = RuntimeContract::apply_all(
4117 value,
4118 field.pending_contracts.iter().cloned(),
4119 pos,
4120 );
4121 f(id, value_with_ctrs)
4122 })
4123 .ok_or(record::MissingFieldDefErrorData {
4124 id,
4125 metadata: field.metadata.clone_inner(),
4126 })?;
4127
4128 let field = Field {
4129 value: Some(value),
4130 pending_contracts: Vec::new(),
4131 ..field
4132 }
4133 .closurize(cache, env.clone());
4134
4135 Ok((id, field))
4136 })
4137 .collect()
4138 }
4139}
4140
4141fn closurize_container(value: NickelValue) -> NickelValue {
4143 let pos_idx = value.pos_idx();
4144 NickelValue::term(Term::Closurize(value), pos_idx)
4145}
4146
4147#[cfg(test)]
4148mod tests {
4149 use super::*;
4150 use crate::{
4151 cache::resolvers::DummyResolver,
4152 error::NullReporter,
4153 eval::{Environment, VmContext, cache::CacheImpl},
4154 };
4155
4156 const NO_APP_INFO: PrimopAppInfo = PrimopAppInfo {
4157 call_stack_size: 0,
4158 pos_idx: PosIdx::NONE,
4159 };
4160
4161 fn with_vm(test: impl FnOnce(VirtualMachine<'_, DummyResolver, CacheImpl>)) {
4163 let mut vm_ctxt = VmContext::new(DummyResolver {}, std::io::sink(), NullReporter {});
4164 let vm = VirtualMachine::new_empty_env(&mut vm_ctxt);
4165 test(vm);
4166 }
4167
4168 #[test]
4169 fn ite_operation() {
4170 with_vm(|mut vm| {
4171 vm.stack.push_arg(mk_term::integer(5).into(), PosIdx::NONE);
4172 vm.stack.push_arg(mk_term::integer(46).into(), PosIdx::NONE);
4173 vm.stack.push_op1_cont(Op1ContItem {
4174 op: UnaryOp::IfThenElse,
4175 orig_pos_arg: PosIdx::NONE,
4176 app_info: NO_APP_INFO,
4177 });
4178
4179 assert_eq!(
4180 vm.continue_op(NickelValue::bool_true().into()),
4181 Ok(mk_term::integer(46).into())
4182 );
4183 assert_eq!(0, vm.stack.count_args());
4184 });
4185 }
4186
4187 #[test]
4188 fn plus_first_term_operation() {
4189 with_vm(|mut vm| {
4190 vm.stack.push_op2_first_cont(Op2FirstContItem {
4191 op: BinaryOp::Plus,
4192 app_info: NO_APP_INFO,
4193 arg2: Closure {
4194 value: mk_term::integer(6),
4195 env: Environment::new(),
4196 },
4197 orig_pos_arg1: PosIdx::NONE,
4198 });
4199
4200 assert_eq!(
4201 vm.continue_op(mk_term::integer(7).into()),
4202 Ok(mk_term::integer(6).into())
4203 );
4204 assert_eq!(1, vm.stack.count_conts());
4205 assert_eq!(
4206 (Op2SecondContItem {
4207 op: BinaryOp::Plus,
4208 app_info: NO_APP_INFO,
4209 arg1_evaled: mk_term::integer(7).into(),
4210 orig_pos_arg1: PosIdx::NONE,
4211 orig_pos_arg2: PosIdx::NONE,
4212 }),
4213 vm.stack
4214 .pop_op2_second_cont()
4215 .expect("Condition already checked.")
4216 );
4217 });
4218 }
4219
4220 #[test]
4221 fn plus_second_term_operation() {
4222 with_vm(|mut vm| {
4223 vm.stack.push_op2_second_cont(Op2SecondContItem {
4224 op: BinaryOp::Plus,
4225 app_info: NO_APP_INFO,
4226 arg1_evaled: mk_term::integer(7).into(),
4227 orig_pos_arg1: PosIdx::NONE,
4228 orig_pos_arg2: PosIdx::NONE,
4229 });
4230
4231 assert_eq!(
4232 vm.continue_op(mk_term::integer(6).into()),
4233 Ok(Closure {
4234 value: mk_term::integer(13),
4235 env: Environment::new()
4236 })
4237 );
4238 });
4239 }
4240}