Skip to main content

mq_lang/eval/
runtime_value.rs

1use super::env::Env;
2use crate::{AstParams, Ident, Program, Shared, SharedCell, ast, number::Number};
3use mq_markdown::Node;
4use smol_str::SmolStr;
5use std::{
6    borrow::Cow,
7    cmp::Ordering,
8    collections::BTreeMap,
9    ops::{Index, IndexMut},
10};
11
12/// Runtime selector for indexing into markdown nodes.
13#[derive(Debug, Clone, PartialEq)]
14pub enum Selector {
15    /// Selects a child node at the specified index.
16    Index(usize),
17}
18
19/// Represents a module's runtime environment with its exports.
20#[derive(Clone, Debug)]
21pub struct ModuleEnv {
22    name: SmolStr,
23    exports: Shared<SharedCell<Env>>,
24}
25
26impl ModuleEnv {
27    /// Creates a new module environment with the given name and exports.
28    pub fn new(name: &str, exports: Shared<SharedCell<Env>>) -> Self {
29        Self {
30            name: SmolStr::new(name),
31            exports,
32        }
33    }
34
35    /// Returns the name of the module.
36    pub fn name(&self) -> &str {
37        &self.name
38    }
39
40    /// Returns a reference to the module's exports environment.
41    pub fn exports(&self) -> &Shared<SharedCell<Env>> {
42        &self.exports
43    }
44
45    /// Returns the number of exports in this module.
46    pub fn len(&self) -> usize {
47        #[cfg(not(feature = "sync"))]
48        {
49            self.exports.borrow().len()
50        }
51
52        #[cfg(feature = "sync")]
53        {
54            self.exports.read().unwrap().len()
55        }
56    }
57}
58
59impl PartialEq for ModuleEnv {
60    fn eq(&self, other: &Self) -> bool {
61        #[cfg(not(feature = "sync"))]
62        let exports = self.exports().borrow();
63        #[cfg(feature = "sync")]
64        let exports = self.exports().read().unwrap();
65
66        #[cfg(not(feature = "sync"))]
67        let other_exports = other.exports().borrow();
68        #[cfg(feature = "sync")]
69        let other_exports = other.exports().read().unwrap();
70
71        self.name == other.name && std::ptr::eq(&*exports, &*other_exports)
72    }
73}
74
75/// A value in the mq runtime.
76///
77/// This enum represents all possible value types that can exist during
78/// program execution, including numbers, strings, markdown nodes, functions,
79/// and more complex data structures.
80#[derive(Clone, Default)]
81pub enum RuntimeValue {
82    /// A numeric value.
83    Number(Number),
84    /// A boolean value (`true` or `false`).
85    Boolean(bool),
86    /// A string value.
87    String(String),
88    /// A symbol (interned identifier).
89    Symbol(Ident),
90    /// An array of runtime values.
91    Array(Vec<RuntimeValue>),
92    /// A markdown node with an optional selector for indexing.
93    Markdown(Box<Node>, Option<Selector>),
94    /// A user-defined function with parameters, body (program), and captured environment.
95    Function(Box<AstParams>, Program, Shared<SharedCell<Env>>),
96    /// A built-in native function identified by name.
97    NativeFunction(Ident),
98    /// A dictionary mapping identifiers to runtime values.
99    Dict(BTreeMap<Ident, RuntimeValue>),
100    /// A module with its exports.
101    Module(ModuleEnv),
102    /// An AST node (quoted expression).
103    Ast(Shared<ast::node::Node>),
104    /// Raw binary data (e.g. CBOR byte strings).
105    Bytes(Vec<u8>),
106    /// An empty or null value.
107    #[default]
108    None,
109}
110
111// Custom PartialEq implementation to avoid comparing Env pointers
112impl PartialEq for RuntimeValue {
113    fn eq(&self, other: &Self) -> bool {
114        match (self, other) {
115            (RuntimeValue::Number(a), RuntimeValue::Number(b)) => a == b,
116            (RuntimeValue::Boolean(a), RuntimeValue::Boolean(b)) => a == b,
117            (RuntimeValue::String(a), RuntimeValue::String(b)) => a == b,
118            (RuntimeValue::Symbol(a), RuntimeValue::Symbol(b)) => a == b,
119            (RuntimeValue::Array(a), RuntimeValue::Array(b)) => a == b,
120            (RuntimeValue::Markdown(a, sa), RuntimeValue::Markdown(b, sb)) => a == b && sa == sb,
121            (RuntimeValue::Function(a1, b1, _), RuntimeValue::Function(a2, b2, _)) => a1 == a2 && b1 == b2,
122            (RuntimeValue::NativeFunction(a), RuntimeValue::NativeFunction(b)) => a == b,
123            (RuntimeValue::Dict(a), RuntimeValue::Dict(b)) => a == b,
124            (RuntimeValue::Module(a), RuntimeValue::Module(b)) => a == b,
125            (RuntimeValue::Ast(a), RuntimeValue::Ast(b)) => a == b,
126            (RuntimeValue::Bytes(a), RuntimeValue::Bytes(b)) => a == b,
127            (RuntimeValue::None, RuntimeValue::None) => true,
128            _ => false,
129        }
130    }
131}
132
133impl From<Node> for RuntimeValue {
134    fn from(node: Node) -> Self {
135        RuntimeValue::new_markdown(node)
136    }
137}
138
139impl From<bool> for RuntimeValue {
140    fn from(b: bool) -> Self {
141        RuntimeValue::Boolean(b)
142    }
143}
144
145impl From<String> for RuntimeValue {
146    fn from(s: String) -> Self {
147        RuntimeValue::String(s)
148    }
149}
150
151impl From<&str> for RuntimeValue {
152    fn from(s: &str) -> Self {
153        RuntimeValue::String(s.to_string())
154    }
155}
156
157impl From<&mut str> for RuntimeValue {
158    fn from(s: &mut str) -> Self {
159        RuntimeValue::String(s.to_string())
160    }
161}
162
163impl From<Number> for RuntimeValue {
164    fn from(n: Number) -> Self {
165        RuntimeValue::Number(n)
166    }
167}
168
169impl From<Ident> for RuntimeValue {
170    fn from(i: Ident) -> Self {
171        RuntimeValue::Symbol(i)
172    }
173}
174
175impl From<usize> for RuntimeValue {
176    fn from(n: usize) -> Self {
177        RuntimeValue::Number(Number::from(n))
178    }
179}
180
181impl From<Vec<RuntimeValue>> for RuntimeValue {
182    fn from(arr: Vec<RuntimeValue>) -> Self {
183        RuntimeValue::Array(arr)
184    }
185}
186
187impl From<BTreeMap<Ident, RuntimeValue>> for RuntimeValue {
188    fn from(map: BTreeMap<Ident, RuntimeValue>) -> Self {
189        RuntimeValue::Dict(map)
190    }
191}
192
193impl From<Vec<(String, Number)>> for RuntimeValue {
194    fn from(v: Vec<(String, Number)>) -> Self {
195        RuntimeValue::Dict(
196            v.into_iter()
197                .map(|(k, v)| (Ident::new(&k), RuntimeValue::Number(v)))
198                .collect::<BTreeMap<Ident, RuntimeValue>>(),
199        )
200    }
201}
202
203impl From<mq_markdown::AttrValue> for RuntimeValue {
204    fn from(attr_value: mq_markdown::AttrValue) -> Self {
205        match attr_value {
206            mq_markdown::AttrValue::String(s) => RuntimeValue::String(s),
207            mq_markdown::AttrValue::Number(n) => RuntimeValue::Number(n.into()),
208            mq_markdown::AttrValue::Integer(n) => RuntimeValue::Number(n.into()),
209            mq_markdown::AttrValue::Boolean(b) => RuntimeValue::Boolean(b),
210            mq_markdown::AttrValue::Array(arr) => {
211                RuntimeValue::Array(arr.into_iter().map(RuntimeValue::from).collect())
212            }
213            mq_markdown::AttrValue::Null => RuntimeValue::NONE,
214        }
215    }
216}
217
218impl From<yaml_rust2::Yaml> for RuntimeValue {
219    fn from(value: yaml_rust2::Yaml) -> Self {
220        match value {
221            yaml_rust2::Yaml::Null | yaml_rust2::Yaml::BadValue => RuntimeValue::NONE,
222            yaml_rust2::Yaml::Boolean(b) => RuntimeValue::Boolean(b),
223            yaml_rust2::Yaml::Integer(i) => RuntimeValue::Number((i as f64).into()),
224            yaml_rust2::Yaml::Real(s) => s
225                .parse::<f64>()
226                .map(|f| RuntimeValue::Number(f.into()))
227                .unwrap_or(RuntimeValue::NONE),
228            yaml_rust2::Yaml::String(s) => RuntimeValue::String(s),
229            yaml_rust2::Yaml::Array(arr) => RuntimeValue::Array(arr.into_iter().map(RuntimeValue::from).collect()),
230            yaml_rust2::Yaml::Hash(map) => {
231                let mut btree = BTreeMap::new();
232                for (k, v) in map {
233                    let key = match k {
234                        yaml_rust2::Yaml::String(s) => s,
235                        other => format!("{other:?}"),
236                    };
237                    btree.insert(Ident::new(&key), RuntimeValue::from(v));
238                }
239                RuntimeValue::Dict(btree)
240            }
241            yaml_rust2::Yaml::Alias(_) => RuntimeValue::NONE,
242        }
243    }
244}
245
246impl From<serde_json::Value> for RuntimeValue {
247    fn from(value: serde_json::Value) -> Self {
248        match value {
249            serde_json::Value::Null => RuntimeValue::NONE,
250            serde_json::Value::Bool(b) => RuntimeValue::Boolean(b),
251            serde_json::Value::Number(n) => {
252                if let Some(f) = n.as_f64() {
253                    RuntimeValue::Number(f.into())
254                } else {
255                    RuntimeValue::Number(0.into())
256                }
257            }
258            serde_json::Value::String(s) => RuntimeValue::String(s),
259            serde_json::Value::Array(arr) => RuntimeValue::Array(arr.into_iter().map(RuntimeValue::from).collect()),
260            serde_json::Value::Object(obj) => {
261                let mut map = BTreeMap::new();
262                for (k, v) in obj {
263                    map.insert(Ident::new(&k), RuntimeValue::from(v));
264                }
265                RuntimeValue::Dict(map)
266            }
267        }
268    }
269}
270
271impl From<ciborium::Value> for RuntimeValue {
272    fn from(value: ciborium::Value) -> Self {
273        match value {
274            ciborium::Value::Null => RuntimeValue::NONE,
275            ciborium::Value::Bool(b) => RuntimeValue::Boolean(b),
276            ciborium::Value::Integer(i) => {
277                let n: i128 = i.into();
278                RuntimeValue::Number(crate::number::Number::from(n as f64))
279            }
280            ciborium::Value::Float(f) => RuntimeValue::Number(crate::number::Number::from(f)),
281            ciborium::Value::Text(s) => RuntimeValue::String(s),
282            ciborium::Value::Bytes(b) => RuntimeValue::Bytes(b),
283            ciborium::Value::Array(arr) => {
284                let items = arr.into_iter().map(Into::into).collect();
285                RuntimeValue::Array(items)
286            }
287            ciborium::Value::Map(pairs) => {
288                let mut map = BTreeMap::new();
289                for (k, v) in pairs {
290                    let key = match k {
291                        ciborium::Value::Text(s) => Ident::new(&s),
292                        other => Ident::new(&format!("{:?}", other)),
293                    };
294                    map.insert(key, v.into());
295                }
296                RuntimeValue::Dict(map)
297            }
298            ciborium::Value::Tag(_, inner) => (*inner).into(),
299            _ => RuntimeValue::NONE,
300        }
301    }
302}
303
304impl PartialOrd for RuntimeValue {
305    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
306        match (self, other) {
307            (RuntimeValue::Number(a), RuntimeValue::Number(b)) => a.partial_cmp(b),
308            (RuntimeValue::Boolean(a), RuntimeValue::Boolean(b)) => a.partial_cmp(b),
309            (RuntimeValue::String(a), RuntimeValue::String(b)) => a.partial_cmp(b),
310            (RuntimeValue::Symbol(a), RuntimeValue::Symbol(b)) => a.partial_cmp(b),
311            (RuntimeValue::Array(a), RuntimeValue::Array(b)) => a.partial_cmp(b),
312            (RuntimeValue::Markdown(a, _), RuntimeValue::Markdown(b, _)) => {
313                let a = a.to_string();
314                let b = b.to_string();
315                a.to_string().partial_cmp(&b)
316            }
317            (RuntimeValue::Function(a1, b1, _), RuntimeValue::Function(a2, b2, _)) => match a1.partial_cmp(a2) {
318                Some(Ordering::Equal) => b1.partial_cmp(b2),
319                Some(Ordering::Greater) => Some(Ordering::Greater),
320                Some(Ordering::Less) => Some(Ordering::Less),
321                _ => None,
322            },
323            (RuntimeValue::Bytes(a), RuntimeValue::Bytes(b)) => a.partial_cmp(b),
324            (RuntimeValue::Dict(_), _) => None,
325            (_, RuntimeValue::Dict(_)) => None,
326            (RuntimeValue::Module(a), RuntimeValue::Module(b)) => a.name.partial_cmp(&b.name),
327            (RuntimeValue::Ast(_), _) => None,
328            (_, RuntimeValue::Ast(_)) => None,
329            _ => None,
330        }
331    }
332}
333
334impl std::fmt::Display for RuntimeValue {
335    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
336        let value: Cow<'_, str> = match self {
337            Self::Number(n) => Cow::Owned(n.to_string()),
338            Self::Boolean(b) => Cow::Owned(b.to_string()),
339            Self::String(s) => Cow::Borrowed(s),
340            Self::Symbol(i) => Cow::Owned(format!(":{}", i)),
341            Self::Array(_) => self.string(),
342            Self::Markdown(m, ..) => Cow::Owned(m.to_string()),
343            Self::None => Cow::Borrowed(""),
344            Self::Function(params, ..) => Cow::Owned(format!("function/{}", params.len())),
345            Self::NativeFunction(_) => Cow::Borrowed("native_function"),
346            Self::Dict(_) => self.string(),
347            Self::Module(module_name) => Cow::Owned(format!(r#"module "{}""#, module_name.name)),
348            Self::Ast(node) => Cow::Owned(node.to_code()),
349            Self::Bytes(b) => Cow::Owned(bytes_to_hex(b)),
350        };
351        write!(f, "{}", value)
352    }
353}
354
355impl std::fmt::Debug for RuntimeValue {
356    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
357        let v: Cow<'_, str> = match self {
358            Self::None => Cow::Borrowed("None"),
359            Self::String(s) => Cow::Owned(format!("{:?}", s)),
360            Self::Array(arr) => Cow::Owned(format!("{:?}", arr)),
361            Self::Bytes(b) => Cow::Owned(format!("bytes({})", bytes_to_hex(b))),
362            a => a.string(),
363        };
364        write!(f, "{}", v)
365    }
366}
367
368fn bytes_to_hex(bytes: &[u8]) -> String {
369    use std::fmt::Write;
370    bytes.iter().fold(String::with_capacity(bytes.len() * 2), |mut s, b| {
371        write!(s, "{b:02x}").unwrap();
372        s
373    })
374}
375
376impl RuntimeValue {
377    /// An empty array constant.
378    pub const EMPTY_ARRAY: RuntimeValue = Self::Array(Vec::new());
379    /// The boolean `false` value.
380    pub const FALSE: RuntimeValue = Self::Boolean(false);
381    /// The `None` (null) value.
382    pub const NONE: RuntimeValue = Self::None;
383    /// The boolean `true` value.
384    pub const TRUE: RuntimeValue = Self::Boolean(true);
385
386    /// Creates a new empty dictionary.
387    #[inline(always)]
388    pub fn new_dict() -> RuntimeValue {
389        RuntimeValue::Dict(BTreeMap::new())
390    }
391
392    /// Creates a new markdown runtime value from the given node.
393    pub fn new_markdown(node: Node) -> RuntimeValue {
394        RuntimeValue::Markdown(Box::new(node), None)
395    }
396
397    /// Returns the type name of this runtime value as a string.
398    #[inline(always)]
399    pub fn name(&self) -> &str {
400        match self {
401            RuntimeValue::Number(_) => "number",
402            RuntimeValue::Boolean(_) => "bool",
403            RuntimeValue::String(_) => "string",
404            RuntimeValue::Symbol(_) => "symbol",
405            RuntimeValue::Markdown(_, _) => "markdown",
406            RuntimeValue::Array(_) => "array",
407            RuntimeValue::None => "None",
408            RuntimeValue::Function(_, _, _) => "function",
409            RuntimeValue::NativeFunction(_) => "native_function",
410            RuntimeValue::Dict(_) => "dict",
411            RuntimeValue::Module(_) => "module",
412            RuntimeValue::Ast(_) => "ast",
413            RuntimeValue::Bytes(_) => "bytes",
414        }
415    }
416
417    /// Returns `true` if this value is `None`.
418    #[inline(always)]
419    pub fn is_none(&self) -> bool {
420        matches!(self, RuntimeValue::None)
421    }
422
423    /// Returns `true` if this value is a user-defined function.
424    #[inline(always)]
425    pub fn is_function(&self) -> bool {
426        matches!(self, RuntimeValue::Function(_, _, _))
427    }
428
429    /// Returns `true` if this value is a native (built-in) function.
430    #[inline(always)]
431    pub fn is_native_function(&self) -> bool {
432        matches!(self, RuntimeValue::NativeFunction(_))
433    }
434
435    /// Returns `true` if this value is an array.
436    #[inline(always)]
437    pub fn is_array(&self) -> bool {
438        matches!(self, RuntimeValue::Array(_))
439    }
440
441    /// Returns `true` if this value is a dict.
442    #[inline(always)]
443    pub fn is_dict(&self) -> bool {
444        matches!(self, RuntimeValue::Dict(_))
445    }
446
447    /// Returns `true` if this value is considered empty.
448    ///
449    /// Empty values include empty arrays, empty strings, empty markdown nodes,
450    /// empty dictionaries, and `None`.
451    #[inline(always)]
452    pub fn is_empty(&self) -> bool {
453        match self {
454            RuntimeValue::Array(a) => a.is_empty(),
455            RuntimeValue::String(s) => s.is_empty(),
456            RuntimeValue::Markdown(m, _) => m.value().is_empty(),
457            RuntimeValue::Dict(m) => m.is_empty(),
458            RuntimeValue::Bytes(b) => b.is_empty(),
459            RuntimeValue::None => true,
460            _ => false,
461        }
462    }
463
464    /// Returns `true` if this value is considered truthy in conditional contexts.
465    ///
466    /// Truthy values include non-zero numbers, non-empty strings and arrays,
467    /// `true`, functions, symbols, and modules. Falsy values include `false`,
468    /// zero, empty collections, and `None`.
469    #[inline(always)]
470    pub fn is_truthy(&self) -> bool {
471        match self {
472            RuntimeValue::Boolean(b) => *b,
473            RuntimeValue::Number(n) => n.value() != 0.0,
474            RuntimeValue::String(s) => !s.is_empty(),
475            RuntimeValue::Array(a) => !a.is_empty(),
476            RuntimeValue::Markdown(node, selector) => match selector {
477                Some(Selector::Index(i)) => node.find_at_index(*i).is_some(),
478                None => true,
479            },
480            RuntimeValue::Symbol(_)
481            | RuntimeValue::Function(_, _, _)
482            | RuntimeValue::NativeFunction(_)
483            | RuntimeValue::Dict(_) => true,
484            RuntimeValue::Module(_) => true,
485            RuntimeValue::Ast(_) => true,
486            RuntimeValue::Bytes(b) => !b.is_empty(),
487            RuntimeValue::None => false,
488        }
489    }
490
491    /// Returns the length of this value.
492    ///
493    /// For numbers, returns the value as `usize`. For strings and arrays, returns
494    /// the number of elements. For dictionaries, returns the number of entries.
495    #[inline(always)]
496    pub fn len(&self) -> usize {
497        match self {
498            RuntimeValue::Number(n) => n.value() as usize,
499            RuntimeValue::Boolean(_) => 1,
500            RuntimeValue::String(s) => s.len(),
501            RuntimeValue::Symbol(i) => i.as_str().len(),
502            RuntimeValue::Array(a) => a.len(),
503            RuntimeValue::Markdown(m, _) => m.value().len(),
504            RuntimeValue::Dict(m) => m.len(),
505            RuntimeValue::Bytes(b) => b.len(),
506            RuntimeValue::None => 0,
507            RuntimeValue::Function(..) => 0,
508            RuntimeValue::Module(m) => m.len(),
509            RuntimeValue::NativeFunction(..) => 0,
510            RuntimeValue::Ast(_) => 0,
511        }
512    }
513
514    /// Extracts the markdown node from this value, if it is a markdown value.
515    ///
516    /// If a selector is present, returns the selected child node.
517    #[inline(always)]
518    pub fn markdown_node(&self) -> Option<Node> {
519        match self {
520            RuntimeValue::Markdown(n, Some(Selector::Index(i))) => n.find_at_index(*i),
521            RuntimeValue::Markdown(n, _) => Some((**n).clone()),
522            _ => None,
523        }
524    }
525
526    /// Updates the value of a markdown node, returning a new runtime value.
527    ///
528    /// If this is not a markdown value, returns `None`.
529    #[inline(always)]
530    pub fn update_markdown_value(&self, value: &str) -> RuntimeValue {
531        match self {
532            RuntimeValue::Markdown(n, Some(Selector::Index(i))) => {
533                RuntimeValue::Markdown(Box::new(n.with_children_value(value, *i)), Some(Selector::Index(*i)))
534            }
535            RuntimeValue::Markdown(n, selector) => {
536                RuntimeValue::Markdown(Box::new(n.with_value(value)), selector.clone())
537            }
538            _ => RuntimeValue::NONE,
539        }
540    }
541
542    /// Returns the position information for a markdown node, if available.
543    #[inline(always)]
544    pub fn position(&self) -> Option<mq_markdown::Position> {
545        match self {
546            RuntimeValue::Markdown(node, _) => node.position(),
547            _ => None,
548        }
549    }
550
551    /// Sets the position information for a markdown node.
552    ///
553    /// Only affects markdown values; other value types are unaffected.
554    #[inline(always)]
555    pub fn set_position(&mut self, position: Option<mq_markdown::Position>) {
556        if let RuntimeValue::Markdown(node, _) = self {
557            node.set_position(position);
558        }
559    }
560
561    #[inline(always)]
562    fn string(&self) -> Cow<'_, str> {
563        match self {
564            Self::Number(n) => Cow::Owned(n.to_string()),
565            Self::Boolean(b) => Cow::Owned(b.to_string()),
566            Self::String(s) => Cow::Owned(format!(r#""{}""#, s)),
567            Self::Symbol(i) => Cow::Owned(format!(":{}", i)),
568            Self::Array(a) => Cow::Owned(format!(
569                "[{}]",
570                a.iter().map(|v| v.string()).collect::<Vec<Cow<str>>>().join(", ")
571            )),
572            Self::Markdown(m, ..) => Cow::Owned(m.to_string()),
573            Self::None => Cow::Borrowed(""),
574            Self::Function(f, _, _) => Cow::Owned(format!("function/{}", f.len())),
575            Self::NativeFunction(_) => Cow::Borrowed("native_function"),
576            Self::Module(m) => Cow::Owned(format!("module/{}", m.name())),
577            Self::Ast(node) => Cow::Owned(node.to_code()),
578            Self::Bytes(b) => Cow::Owned(bytes_to_hex(b)),
579            Self::Dict(map) => {
580                let items = map
581                    .iter()
582                    .map(|(k, v)| format!("\"{}\": {}", k, v.string()))
583                    .collect::<Vec<String>>()
584                    .join(", ");
585                Cow::Owned(format!("{{{}}}", items))
586            }
587        }
588    }
589
590    /// Returns a new runtime value that is the logical negation of this value.
591    pub fn negated(&self) -> Self {
592        match self {
593            RuntimeValue::Boolean(b) => RuntimeValue::Boolean(!b),
594            RuntimeValue::Number(n) => RuntimeValue::Number((-n.value()).into()),
595            RuntimeValue::String(s) => RuntimeValue::String(s.chars().rev().collect()),
596            _ => self.clone(),
597        }
598    }
599
600    pub fn to_json_value(self) -> serde_json::Value {
601        use base64::Engine;
602        match self {
603            RuntimeValue::None => serde_json::Value::Null,
604            RuntimeValue::Boolean(b) => serde_json::Value::Bool(b),
605            RuntimeValue::Number(n) => serde_json::Number::from_f64(n.value())
606                .map(serde_json::Value::Number)
607                .unwrap_or(serde_json::Value::Null),
608            RuntimeValue::String(s) => serde_json::Value::String(s),
609            RuntimeValue::Symbol(i) => serde_json::Value::String(i.to_string()),
610            RuntimeValue::Array(arr) => serde_json::Value::Array(arr.into_iter().map(Self::to_json_value).collect()),
611            RuntimeValue::Dict(map) => {
612                let obj: serde_json::Map<String, serde_json::Value> = map
613                    .into_iter()
614                    .map(|(k, v)| (k.to_string(), v.to_json_value()))
615                    .collect();
616                serde_json::Value::Object(obj)
617            }
618            RuntimeValue::Bytes(b) => serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b)),
619            _ => serde_json::Value::Null,
620        }
621    }
622
623    pub fn to_cbor_value(self) -> ciborium::Value {
624        match self {
625            RuntimeValue::None => ciborium::Value::Null,
626            RuntimeValue::Boolean(b) => ciborium::Value::Bool(b),
627            RuntimeValue::Number(n) => ciborium::Value::Float(n.value()),
628            RuntimeValue::String(s) => ciborium::Value::Text(s),
629            RuntimeValue::Symbol(i) => ciborium::Value::Text(i.to_string()),
630            RuntimeValue::Bytes(b) => ciborium::Value::Bytes(b),
631            RuntimeValue::Array(arr) => ciborium::Value::Array(arr.into_iter().map(Self::to_cbor_value).collect()),
632            RuntimeValue::Dict(map) => ciborium::Value::Map(
633                map.into_iter()
634                    .map(|(k, v)| (ciborium::Value::Text(k.to_string()), v.to_cbor_value()))
635                    .collect(),
636            ),
637            _ => ciborium::Value::Null,
638        }
639    }
640}
641
642/// A collection of runtime values.
643///
644/// Provides utilities for working with multiple values, such as filtering
645/// and updating operations.
646#[derive(Debug, Clone, PartialEq)]
647pub struct RuntimeValues(Vec<RuntimeValue>);
648
649impl From<Vec<RuntimeValue>> for RuntimeValues {
650    fn from(values: Vec<RuntimeValue>) -> Self {
651        Self(values)
652    }
653}
654
655impl Index<usize> for RuntimeValues {
656    type Output = RuntimeValue;
657
658    fn index(&self, index: usize) -> &Self::Output {
659        &self.0[index]
660    }
661}
662
663impl IndexMut<usize> for RuntimeValues {
664    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
665        &mut self.0[index]
666    }
667}
668
669impl IntoIterator for RuntimeValues {
670    type IntoIter = std::vec::IntoIter<RuntimeValue>;
671    type Item = RuntimeValue;
672
673    fn into_iter(self) -> Self::IntoIter {
674        self.0.into_iter()
675    }
676}
677
678impl RuntimeValues {
679    /// Returns a compacted version of this collection, removing `None` and empty values.
680    pub fn compact(&self) -> Vec<RuntimeValue> {
681        self.0
682            .iter()
683            .filter(|v| !v.is_none() && !v.is_empty())
684            .cloned()
685            .collect::<Vec<_>>()
686    }
687
688    /// Returns a reference to the underlying vector of values.
689    pub fn values(&self) -> &Vec<RuntimeValue> {
690        &self.0
691    }
692
693    /// Returns the number of values in this collection.
694    pub fn len(&self) -> usize {
695        self.0.len()
696    }
697
698    /// Returns `true` if this collection contains no values.
699    pub fn is_empty(&self) -> bool {
700        self.0.len() == 0
701    }
702
703    /// Updates this collection with values from another collection.
704    ///
705    /// Pairs corresponding elements from both collections and applies special
706    /// update logic for markdown nodes.
707    pub fn update_with(&self, other: Self) -> Self {
708        self.0
709            .clone()
710            .into_iter()
711            .zip(other)
712            .map(|(current_value, mut updated_value)| {
713                updated_value.set_position(current_value.position());
714
715                if let RuntimeValue::Markdown(node, _) = &current_value {
716                    match &updated_value {
717                        RuntimeValue::None
718                        | RuntimeValue::Function(_, _, _)
719                        | RuntimeValue::Module(_)
720                        | RuntimeValue::Ast(_)
721                        | RuntimeValue::NativeFunction(_) => current_value.clone(),
722                        RuntimeValue::Markdown(node, _) if node.is_empty() => current_value.clone(),
723                        RuntimeValue::Markdown(node, _) => {
724                            if node.is_fragment() {
725                                if let RuntimeValue::Markdown(mut current_node, selector) = current_value {
726                                    current_node.apply_fragment((**node).clone());
727                                    RuntimeValue::Markdown(current_node, selector)
728                                } else {
729                                    updated_value
730                                }
731                            } else {
732                                updated_value
733                            }
734                        }
735                        RuntimeValue::String(s) => RuntimeValue::new_markdown(node.with_value(s)),
736                        RuntimeValue::Symbol(i) => RuntimeValue::new_markdown(node.with_value(&i.as_str())),
737                        RuntimeValue::Boolean(b) => RuntimeValue::new_markdown(node.with_value(b.to_string().as_str())),
738                        RuntimeValue::Number(n) => RuntimeValue::new_markdown(node.with_value(n.to_string().as_str())),
739                        RuntimeValue::Array(array) => RuntimeValue::Array(
740                            array
741                                .iter()
742                                .filter_map(|o| {
743                                    if o.is_none() {
744                                        None
745                                    } else {
746                                        Some(RuntimeValue::Markdown(
747                                            Box::new(node.with_value(o.to_string().as_str())),
748                                            None,
749                                        ))
750                                    }
751                                })
752                                .collect::<Vec<_>>(),
753                        ),
754                        RuntimeValue::Bytes(b) => RuntimeValue::new_markdown(node.with_value(bytes_to_hex(b).as_str())),
755                        RuntimeValue::Dict(map) => {
756                            let mut new_dict = BTreeMap::new();
757                            for (k, v) in map {
758                                if !v.is_none() && !v.is_empty() {
759                                    new_dict.insert(
760                                        *k,
761                                        RuntimeValue::new_markdown(node.with_value(v.to_string().as_str())),
762                                    );
763                                }
764                            }
765                            RuntimeValue::Dict(new_dict)
766                        }
767                    }
768                } else {
769                    updated_value
770                }
771            })
772            .collect::<Vec<_>>()
773            .into()
774    }
775}
776
777#[cfg(test)]
778mod tests {
779    use crate::ast::node::{IdentWithToken, Param};
780    use rstest::rstest;
781    use smallvec::{SmallVec, smallvec};
782
783    use super::*;
784
785    #[test]
786    fn test_runtime_value_from() {
787        assert_eq!(RuntimeValue::from(true), RuntimeValue::Boolean(true));
788        assert_eq!(RuntimeValue::from(false), RuntimeValue::Boolean(false));
789        assert_eq!(
790            RuntimeValue::from(String::from("test")),
791            RuntimeValue::String(String::from("test"))
792        );
793        assert_eq!(
794            RuntimeValue::from(Number::from(42.0)),
795            RuntimeValue::Number(Number::from(42.0))
796        );
797    }
798
799    #[rstest]
800    #[case(RuntimeValue::Number(Number::from(42.0)), "42")]
801    #[case(RuntimeValue::Boolean(true), "true")]
802    #[case(RuntimeValue::Boolean(false), "false")]
803    #[case(RuntimeValue::String("hello".to_string()), r#""hello""#)]
804    #[case(RuntimeValue::None, "")]
805    #[case(RuntimeValue::Array(vec![
806            RuntimeValue::Number(Number::from(1.0)),
807            RuntimeValue::String("test".to_string())
808        ]), r#"[1, "test"]"#)]
809    #[case(RuntimeValue::Dict({
810            let mut map = BTreeMap::new();
811            map.insert(Ident::new("key1"), RuntimeValue::String("value1".to_string()));
812            map.insert(Ident::new("key2"), RuntimeValue::Number(Number::from(42.0)));
813            map
814        }), r#"{"key1": "value1", "key2": 42}"#)]
815    fn test_string_method(#[case] value: RuntimeValue, #[case] expected: &str) {
816        assert_eq!(value.string(), expected);
817    }
818
819    #[test]
820    fn test_runtime_value_display() {
821        assert_eq!(format!("{}", RuntimeValue::Boolean(true)), "true");
822        assert_eq!(format!("{}", RuntimeValue::Number(Number::from(42.0))), "42");
823        assert_eq!(format!("{}", RuntimeValue::String(String::from("test"))), "test");
824        assert_eq!(format!("{}", RuntimeValue::None), "");
825        let map_val = RuntimeValue::Dict(BTreeMap::default());
826        assert_eq!(format!("{}", map_val), "{}");
827    }
828
829    #[test]
830    fn test_runtime_value_debug() {
831        assert_eq!(format!("{:?}", RuntimeValue::Boolean(true)), "true");
832        assert_eq!(format!("{:?}", RuntimeValue::Number(Number::from(42.0))), "42");
833        assert_eq!(format!("{:?}", RuntimeValue::String(String::from("test"))), "\"test\"");
834        assert_eq!(format!("{:?}", RuntimeValue::None), "None");
835
836        let mut map = BTreeMap::default();
837        map.insert(Ident::new("name"), RuntimeValue::String("MQ".to_string()));
838        map.insert(Ident::new("version"), RuntimeValue::Number(Number::from(1.0)));
839        let map_val = RuntimeValue::Dict(map);
840        let debug_str = format!("{:?}", map_val);
841        assert!(debug_str == r#"{"name": "MQ", "version": 1}"# || debug_str == r#"{"version": 1, "name": "MQ"}"#);
842    }
843
844    #[test]
845    fn test_runtime_value_name() {
846        assert_eq!(RuntimeValue::Boolean(true).name(), "bool");
847        assert_eq!(RuntimeValue::Number(Number::from(42.0)).name(), "number");
848        assert_eq!(RuntimeValue::String(String::from("test")).name(), "string");
849        assert_eq!(RuntimeValue::None.name(), "None");
850        assert_eq!(
851            RuntimeValue::Function(
852                Box::new(SmallVec::new()),
853                Vec::new(),
854                Shared::new(SharedCell::new(Env::default()))
855            )
856            .name(),
857            "function"
858        );
859        assert_eq!(
860            RuntimeValue::NativeFunction(Ident::new("name")).name(),
861            "native_function"
862        );
863        assert_eq!(
864            RuntimeValue::Markdown(
865                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
866                    value: "".to_string(),
867                    position: None
868                })),
869                None
870            )
871            .name(),
872            "markdown"
873        );
874        assert_eq!(RuntimeValue::Dict(BTreeMap::default()).name(), "dict");
875    }
876
877    #[test]
878    fn test_runtime_value_is_true() {
879        assert!(RuntimeValue::Boolean(true).is_truthy());
880        assert!(!RuntimeValue::Boolean(false).is_truthy());
881        assert!(RuntimeValue::Number(Number::from(42.0)).is_truthy());
882        assert!(!RuntimeValue::Number(Number::from(0.0)).is_truthy());
883        assert!(RuntimeValue::String(String::from("test")).is_truthy());
884        assert!(!RuntimeValue::String(String::from("")).is_truthy());
885        assert!(RuntimeValue::Array(vec!["".to_string().into()]).is_truthy());
886        assert!(!RuntimeValue::Array(Vec::new()).is_truthy());
887        assert!(
888            RuntimeValue::Markdown(
889                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
890                    value: "".to_string(),
891                    position: None
892                })),
893                None
894            )
895            .is_truthy()
896        );
897        assert!(
898            !RuntimeValue::Markdown(
899                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
900                    value: "".to_string(),
901                    position: None
902                })),
903                Some(Selector::Index(1))
904            )
905            .is_truthy()
906        );
907        assert!(!RuntimeValue::Array(Vec::new()).is_truthy());
908        assert!(!RuntimeValue::None.is_truthy());
909        assert!(RuntimeValue::NativeFunction(Ident::new("name")).is_truthy());
910        assert!(
911            RuntimeValue::Function(
912                Box::new(SmallVec::new()),
913                Vec::new(),
914                Shared::new(SharedCell::new(Env::default()))
915            )
916            .is_truthy()
917        );
918        assert!(RuntimeValue::Dict(BTreeMap::default()).is_truthy());
919    }
920
921    #[test]
922    fn test_runtime_value_partial_ord() {
923        assert!(RuntimeValue::Number(Number::from(1.0)) < RuntimeValue::Number(Number::from(2.0)));
924        assert!(RuntimeValue::String(String::from("a")) < RuntimeValue::String(String::from("b")));
925        assert!(RuntimeValue::Array(Vec::new()) < RuntimeValue::Array(vec!["a".to_string().into()]));
926        assert!(
927            RuntimeValue::Markdown(
928                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
929                    value: "test".to_string(),
930                    position: None
931                })),
932                None
933            ) < RuntimeValue::Markdown(
934                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
935                    value: "test2".to_string(),
936                    position: None
937                })),
938                None
939            )
940        );
941        assert!(RuntimeValue::Boolean(false) < RuntimeValue::Boolean(true));
942        assert!(
943            RuntimeValue::Function(
944                Box::new(SmallVec::new()),
945                Vec::new(),
946                Shared::new(SharedCell::new(Env::default()))
947            ) < RuntimeValue::Function(
948                Box::new(smallvec![Param::new(IdentWithToken::new("test"))]),
949                Vec::new(),
950                Shared::new(SharedCell::new(Env::default()))
951            )
952        );
953    }
954
955    #[test]
956    fn test_runtime_value_len() {
957        assert_eq!(RuntimeValue::Number(Number::from(42.0)).len(), 42);
958        assert_eq!(RuntimeValue::String(String::from("test")).len(), 4);
959        assert_eq!(RuntimeValue::Boolean(true).len(), 1);
960        assert_eq!(RuntimeValue::Array(vec![RuntimeValue::None]).len(), 1);
961        assert_eq!(
962            RuntimeValue::Markdown(
963                Box::new(mq_markdown::Node::Text(mq_markdown::Text {
964                    value: "a".to_string(),
965                    position: None
966                })),
967                None
968            )
969            .len(),
970            1
971        );
972        let mut map = BTreeMap::default();
973        map.insert(Ident::new("a"), RuntimeValue::String("alpha".to_string()));
974        map.insert(Ident::new("b"), RuntimeValue::String("beta".to_string()));
975        assert_eq!(RuntimeValue::Dict(map).len(), 2);
976    }
977
978    #[test]
979    fn test_negated() {
980        assert_eq!(
981            RuntimeValue::Number(Number::from(42.0)).negated(),
982            RuntimeValue::Number(Number::from(-42.0))
983        );
984        assert_eq!(RuntimeValue::Boolean(true).negated(), RuntimeValue::Boolean(false));
985        assert_eq!(RuntimeValue::Boolean(false).negated(), RuntimeValue::Boolean(true));
986    }
987
988    #[test]
989    fn test_runtime_value_debug_output() {
990        let array = RuntimeValue::Array(vec![
991            RuntimeValue::Number(Number::from(1.0)),
992            RuntimeValue::String("hello".to_string()),
993        ]);
994        assert_eq!(format!("{:?}", array), r#"[1, "hello"]"#);
995
996        let node = mq_markdown::Node::Text(mq_markdown::Text {
997            value: "test markdown".to_string(),
998            position: None,
999        });
1000        let markdown = RuntimeValue::new_markdown(node);
1001        assert_eq!(format!("{:?}", markdown), "test markdown");
1002
1003        let function = RuntimeValue::Function(
1004            Box::new(SmallVec::new()),
1005            Vec::new(),
1006            Shared::new(SharedCell::new(Env::default())),
1007        );
1008        assert_eq!(format!("{:?}", function), "function/0");
1009
1010        let native_fn = RuntimeValue::NativeFunction(Ident::new("debug"));
1011        assert_eq!(format!("{:?}", native_fn), "native_function");
1012
1013        let mut map = BTreeMap::default();
1014        map.insert(Ident::new("a"), RuntimeValue::String("alpha".to_string()));
1015        let map_val = RuntimeValue::Dict(map);
1016        assert_eq!(format!("{:?}", map_val), r#"{"a": "alpha"}"#);
1017    }
1018
1019    #[test]
1020    fn test_runtime_value_markdown() {
1021        let markdown = RuntimeValue::new_markdown("test markdown".to_string().into());
1022        assert_eq!(markdown.markdown_node().unwrap().value(), "test markdown");
1023
1024        let updated = markdown.update_markdown_value("updated markdown");
1025        match &updated {
1026            RuntimeValue::Markdown(node, selector) => {
1027                assert_eq!(node.value(), "updated markdown");
1028                assert_eq!(*selector, None);
1029            }
1030            _ => panic!("Expected Markdown variant"),
1031        }
1032    }
1033
1034    #[test]
1035    fn test_runtime_value_markdown_with_selector() {
1036        let child1 = mq_markdown::Node::Text(mq_markdown::Text {
1037            value: "child1".to_string(),
1038            position: None,
1039        });
1040        let child2 = mq_markdown::Node::Text(mq_markdown::Text {
1041            value: "child2".to_string(),
1042            position: None,
1043        });
1044
1045        let parent = mq_markdown::Node::Strong(mq_markdown::Strong {
1046            values: vec![child1, child2],
1047            position: None,
1048        });
1049
1050        let markdown_with_selector = RuntimeValue::Markdown(Box::new(parent.clone()), Some(Selector::Index(1)));
1051
1052        let selected = markdown_with_selector.markdown_node();
1053        assert!(selected.is_some());
1054        assert_eq!(selected.unwrap().value(), "child2");
1055
1056        let updated = markdown_with_selector.update_markdown_value("updated child");
1057        match &updated {
1058            RuntimeValue::Markdown(node, selector) => {
1059                assert_eq!(selector, &Some(Selector::Index(1)));
1060                assert_eq!(node.find_at_index(1).unwrap().value(), "updated child");
1061            }
1062            _ => panic!("Expected Markdown variant"),
1063        }
1064    }
1065
1066    #[test]
1067    fn test_update_markdown_value_non_markdown() {
1068        assert_eq!(
1069            RuntimeValue::Number(Number::from(42.0)).update_markdown_value("test"),
1070            RuntimeValue::NONE
1071        );
1072        assert_eq!(
1073            RuntimeValue::String("hello".to_string()).update_markdown_value("test"),
1074            RuntimeValue::NONE
1075        );
1076        assert_eq!(
1077            RuntimeValue::Boolean(true).update_markdown_value("test"),
1078            RuntimeValue::NONE
1079        );
1080        assert_eq!(RuntimeValue::None.update_markdown_value("test"), RuntimeValue::NONE);
1081    }
1082
1083    #[test]
1084    fn test_runtime_value_map_creation_and_equality() {
1085        let mut map1_data = BTreeMap::default();
1086        map1_data.insert(Ident::new("a"), RuntimeValue::Number(Number::from(1.0)));
1087        map1_data.insert(Ident::new("b"), RuntimeValue::String("hello".to_string()));
1088        let map1 = RuntimeValue::Dict(map1_data);
1089
1090        let mut map2_data = BTreeMap::default();
1091        map2_data.insert(Ident::new("a"), RuntimeValue::Number(Number::from(1.0)));
1092        map2_data.insert(Ident::new("b"), RuntimeValue::String("hello".to_string()));
1093        let map2 = RuntimeValue::Dict(map2_data);
1094
1095        let mut map3_data = BTreeMap::default();
1096        map3_data.insert(Ident::new("a"), RuntimeValue::Number(Number::from(1.0)));
1097        map3_data.insert(Ident::new("c"), RuntimeValue::String("world".to_string()));
1098        let map3 = RuntimeValue::Dict(map3_data);
1099
1100        assert_eq!(map1, map2);
1101        assert_ne!(map1, map3);
1102    }
1103
1104    #[test]
1105    fn test_runtime_value_map_is_empty() {
1106        let empty_map = RuntimeValue::Dict(BTreeMap::default());
1107        assert!(empty_map.is_empty());
1108
1109        let mut map_data = BTreeMap::default();
1110        map_data.insert(Ident::new("a"), RuntimeValue::Number(Number::from(1.0)));
1111        let non_empty_map = RuntimeValue::Dict(map_data);
1112        assert!(!non_empty_map.is_empty());
1113    }
1114
1115    #[test]
1116    fn test_runtime_value_map_partial_ord() {
1117        let mut map1_data = BTreeMap::default();
1118        map1_data.insert(Ident::new("a"), RuntimeValue::Number(Number::from(1.0)));
1119        let map1 = RuntimeValue::Dict(map1_data);
1120
1121        let mut map2_data = BTreeMap::default();
1122        map2_data.insert(Ident::new("b"), RuntimeValue::Number(Number::from(2.0)));
1123        let map2 = RuntimeValue::Dict(map2_data);
1124
1125        assert_eq!(map1.partial_cmp(&map2), None);
1126        assert_eq!(map2.partial_cmp(&map1), None);
1127        assert_eq!(map1.partial_cmp(&map1), None);
1128
1129        let num_val = RuntimeValue::Number(Number::from(5.0));
1130        assert_eq!(map1.partial_cmp(&num_val), None);
1131        assert_eq!(num_val.partial_cmp(&map1), None);
1132    }
1133
1134    #[test]
1135    fn test_bytes_name() {
1136        assert_eq!(RuntimeValue::Bytes(vec![]).name(), "bytes");
1137        assert_eq!(RuntimeValue::Bytes(vec![1, 2, 3]).name(), "bytes");
1138    }
1139
1140    #[test]
1141    fn test_bytes_is_empty() {
1142        assert!(RuntimeValue::Bytes(vec![]).is_empty());
1143        assert!(!RuntimeValue::Bytes(vec![0]).is_empty());
1144    }
1145
1146    #[test]
1147    fn test_bytes_is_truthy() {
1148        assert!(!RuntimeValue::Bytes(vec![]).is_truthy());
1149        assert!(RuntimeValue::Bytes(vec![0]).is_truthy());
1150        assert!(RuntimeValue::Bytes(vec![1, 2, 3]).is_truthy());
1151    }
1152
1153    #[test]
1154    fn test_bytes_len() {
1155        assert_eq!(RuntimeValue::Bytes(vec![]).len(), 0);
1156        assert_eq!(RuntimeValue::Bytes(vec![1, 2, 3]).len(), 3);
1157    }
1158
1159    #[test]
1160    fn test_bytes_display() {
1161        assert_eq!(
1162            format!("{}", RuntimeValue::Bytes(vec![0xde, 0xad, 0xbe, 0xef])),
1163            "deadbeef"
1164        );
1165        assert_eq!(format!("{}", RuntimeValue::Bytes(vec![])), "");
1166    }
1167
1168    #[test]
1169    fn test_bytes_debug() {
1170        assert_eq!(format!("{:?}", RuntimeValue::Bytes(vec![0xca, 0xfe])), "bytes(cafe)");
1171    }
1172
1173    #[test]
1174    fn test_bytes_partial_eq() {
1175        assert_eq!(RuntimeValue::Bytes(vec![1, 2]), RuntimeValue::Bytes(vec![1, 2]));
1176        assert_ne!(RuntimeValue::Bytes(vec![1, 2]), RuntimeValue::Bytes(vec![1, 3]));
1177        assert_ne!(
1178            RuntimeValue::Bytes(vec![1, 2]),
1179            RuntimeValue::String("0102".to_string())
1180        );
1181    }
1182
1183    #[test]
1184    fn test_bytes_partial_ord() {
1185        assert!(RuntimeValue::Bytes(vec![1]) < RuntimeValue::Bytes(vec![2]));
1186        assert!(RuntimeValue::Bytes(vec![1, 2]) > RuntimeValue::Bytes(vec![1]));
1187        assert_eq!(
1188            RuntimeValue::Bytes(vec![1]).partial_cmp(&RuntimeValue::Bytes(vec![1])),
1189            Some(std::cmp::Ordering::Equal)
1190        );
1191        assert_eq!(RuntimeValue::Bytes(vec![]).partial_cmp(&RuntimeValue::None), None);
1192    }
1193
1194    #[rstest]
1195    #[case(RuntimeValue::None, serde_json::Value::Null)]
1196    #[case(RuntimeValue::Boolean(true), serde_json::Value::Bool(true))]
1197    #[case(RuntimeValue::Boolean(false), serde_json::Value::Bool(false))]
1198    #[case(RuntimeValue::String("hi".to_string()), serde_json::Value::String("hi".to_string()))]
1199    #[case(RuntimeValue::Symbol(Ident::new("sym")), serde_json::Value::String("sym".to_string()))]
1200    #[case(RuntimeValue::NativeFunction(Ident::new("f")), serde_json::Value::Null)]
1201    fn test_to_json_value_scalars(#[case] value: RuntimeValue, #[case] expected: serde_json::Value) {
1202        assert_eq!(value.to_json_value(), expected);
1203    }
1204
1205    #[test]
1206    fn test_to_json_value_array() {
1207        let arr = RuntimeValue::Array(vec![RuntimeValue::Boolean(true), RuntimeValue::String("x".to_string())]);
1208        match arr.to_json_value() {
1209            serde_json::Value::Array(items) => {
1210                assert_eq!(items[0], serde_json::Value::Bool(true));
1211                assert_eq!(items[1], serde_json::Value::String("x".to_string()));
1212            }
1213            other => panic!("expected Array, got {other:?}"),
1214        }
1215    }
1216
1217    #[test]
1218    fn test_to_json_value_dict() {
1219        let mut map = BTreeMap::new();
1220        map.insert(Ident::new("k"), RuntimeValue::Boolean(false));
1221        let obj = RuntimeValue::Dict(map).to_json_value();
1222        match obj {
1223            serde_json::Value::Object(m) => {
1224                assert_eq!(m["k"], serde_json::Value::Bool(false));
1225            }
1226            other => panic!("expected Object, got {other:?}"),
1227        }
1228    }
1229
1230    #[test]
1231    fn test_to_json_value_bytes_base64() {
1232        let b = RuntimeValue::Bytes(vec![0x00, 0xff]);
1233        match b.to_json_value() {
1234            serde_json::Value::String(s) => assert!(!s.is_empty()),
1235            other => panic!("expected String, got {other:?}"),
1236        }
1237    }
1238
1239    #[rstest]
1240    #[case(RuntimeValue::None, true)]
1241    #[case(RuntimeValue::Boolean(true), false)]
1242    #[case(RuntimeValue::String("".to_string()), true)]
1243    #[case(RuntimeValue::Array(vec![]), true)]
1244    #[case(RuntimeValue::Dict(BTreeMap::new()), true)]
1245    #[case(RuntimeValue::Bytes(vec![]), true)]
1246    #[case(RuntimeValue::Bytes(vec![1]), false)]
1247    fn test_is_empty(#[case] value: RuntimeValue, #[case] expected: bool) {
1248        assert_eq!(value.is_empty(), expected);
1249    }
1250
1251    #[rstest]
1252    #[case(RuntimeValue::None, false)]
1253    #[case(RuntimeValue::Boolean(true), true)]
1254    #[case(RuntimeValue::Boolean(false), false)]
1255    #[case(RuntimeValue::String("hi".to_string()), true)]
1256    #[case(RuntimeValue::String("".to_string()), false)]
1257    #[case(RuntimeValue::Array(vec![RuntimeValue::None]), true)]
1258    #[case(RuntimeValue::Array(vec![]), false)]
1259    #[case(RuntimeValue::Symbol(Ident::new("s")), true)]
1260    #[case(RuntimeValue::NativeFunction(Ident::new("f")), true)]
1261    fn test_is_truthy_variants(#[case] value: RuntimeValue, #[case] expected: bool) {
1262        assert_eq!(value.is_truthy(), expected);
1263    }
1264
1265    #[rstest]
1266    #[case(RuntimeValue::Symbol(Ident::new("abc")), 3)]
1267    #[case(RuntimeValue::NativeFunction(Ident::new("f")), 0)]
1268    #[case(RuntimeValue::Ast(crate::Shared::new(crate::AstNode { token_id: crate::arena::ArenaId::new(0), expr: crate::Shared::new(crate::AstExpr::Self_) })), 0)]
1269    fn test_len_less_common(#[case] value: RuntimeValue, #[case] expected: usize) {
1270        assert_eq!(value.len(), expected);
1271    }
1272
1273    #[test]
1274    fn test_is_none_predicate() {
1275        assert!(RuntimeValue::None.is_none());
1276        assert!(!RuntimeValue::Boolean(false).is_none());
1277    }
1278
1279    #[test]
1280    fn test_is_function_native() {
1281        assert!(RuntimeValue::NativeFunction(Ident::new("f")).is_native_function());
1282        assert!(!RuntimeValue::NativeFunction(Ident::new("f")).is_function());
1283        assert!(!RuntimeValue::None.is_native_function());
1284    }
1285
1286    #[test]
1287    fn test_is_array_dict() {
1288        assert!(RuntimeValue::Array(vec![]).is_array());
1289        assert!(!RuntimeValue::None.is_array());
1290        assert!(RuntimeValue::Dict(BTreeMap::new()).is_dict());
1291        assert!(!RuntimeValue::None.is_dict());
1292    }
1293
1294    #[test]
1295    fn test_new_dict_and_new_markdown() {
1296        assert!(RuntimeValue::new_dict().is_dict());
1297        let node = mq_markdown::Node::Empty;
1298        assert!(matches!(
1299            RuntimeValue::new_markdown(node),
1300            RuntimeValue::Markdown(_, None)
1301        ));
1302    }
1303
1304    #[test]
1305    fn test_from_vec_runtime_value() {
1306        let arr: RuntimeValue = vec![RuntimeValue::None, RuntimeValue::Boolean(true)].into();
1307        assert!(arr.is_array());
1308        assert_eq!(arr.len(), 2);
1309    }
1310
1311    #[test]
1312    fn test_from_btree_map() {
1313        let mut map = BTreeMap::new();
1314        map.insert(Ident::new("x"), RuntimeValue::Boolean(true));
1315        let dict: RuntimeValue = map.into();
1316        assert!(dict.is_dict());
1317    }
1318
1319    #[test]
1320    fn test_from_usize() {
1321        let v: RuntimeValue = 42usize.into();
1322        assert!(matches!(v, RuntimeValue::Number(_)));
1323        assert_eq!(v.len(), 42);
1324    }
1325
1326    #[test]
1327    fn test_markdown_node_with_no_selector() {
1328        let node = mq_markdown::Node::Empty;
1329        let v = RuntimeValue::Markdown(Box::new(node), None);
1330        assert!(v.markdown_node().is_some());
1331    }
1332
1333    #[test]
1334    fn test_markdown_node_non_markdown_returns_none() {
1335        assert!(RuntimeValue::None.markdown_node().is_none());
1336        assert!(RuntimeValue::String("x".to_string()).markdown_node().is_none());
1337    }
1338
1339    #[test]
1340    fn test_runtime_values_index() {
1341        let values: RuntimeValues =
1342            vec![RuntimeValue::Boolean(true), RuntimeValue::String("second".to_string())].into();
1343        assert_eq!(values[0], RuntimeValue::Boolean(true));
1344        assert_eq!(values[1], RuntimeValue::String("second".to_string()));
1345    }
1346
1347    #[test]
1348    fn test_runtime_values_index_mut() {
1349        let mut values: RuntimeValues = vec![RuntimeValue::None, RuntimeValue::None].into();
1350        values[0] = RuntimeValue::Boolean(true);
1351        assert_eq!(values[0], RuntimeValue::Boolean(true));
1352    }
1353
1354    #[test]
1355    fn test_runtime_values_is_empty() {
1356        let empty: RuntimeValues = vec![].into();
1357        assert!(empty.is_empty());
1358        let non_empty: RuntimeValues = vec![RuntimeValue::None].into();
1359        assert!(!non_empty.is_empty());
1360    }
1361
1362    fn text_node(s: &str) -> mq_markdown::Node {
1363        mq_markdown::Node::Text(mq_markdown::Text {
1364            value: s.to_string(),
1365            position: None,
1366        })
1367    }
1368
1369    fn md(s: &str) -> RuntimeValue {
1370        RuntimeValue::new_markdown(text_node(s))
1371    }
1372
1373    #[test]
1374    fn test_negated_string_reverses() {
1375        let v = RuntimeValue::String("abc".to_string()).negated();
1376        assert_eq!(v, RuntimeValue::String("cba".to_string()));
1377    }
1378
1379    #[test]
1380    fn test_negated_none_returns_self() {
1381        assert_eq!(RuntimeValue::None.negated(), RuntimeValue::None);
1382    }
1383
1384    #[test]
1385    fn test_negated_array_returns_self() {
1386        let arr = RuntimeValue::Array(vec![RuntimeValue::Number(1.into())]);
1387        assert_eq!(arr.clone().negated(), arr);
1388    }
1389
1390    #[test]
1391    fn test_position_non_markdown_returns_none() {
1392        assert!(RuntimeValue::None.position().is_none());
1393        assert!(RuntimeValue::Number(1.into()).position().is_none());
1394        assert!(RuntimeValue::String("x".to_string()).position().is_none());
1395    }
1396
1397    #[test]
1398    fn test_set_position_non_markdown_is_noop() {
1399        let mut v = RuntimeValue::Number(1.into());
1400        v.set_position(None); // should not panic
1401        assert_eq!(v, RuntimeValue::Number(1.into()));
1402    }
1403
1404    #[test]
1405    fn test_to_cbor_value_scalars() {
1406        assert_eq!(RuntimeValue::None.to_cbor_value(), ciborium::Value::Null);
1407        assert_eq!(RuntimeValue::Boolean(true).to_cbor_value(), ciborium::Value::Bool(true));
1408        assert_eq!(
1409            RuntimeValue::Number(1.5.into()).to_cbor_value(),
1410            ciborium::Value::Float(1.5)
1411        );
1412        assert_eq!(
1413            RuntimeValue::String("hi".to_string()).to_cbor_value(),
1414            ciborium::Value::Text("hi".to_string())
1415        );
1416        assert_eq!(
1417            RuntimeValue::Symbol(Ident::new("s")).to_cbor_value(),
1418            ciborium::Value::Text("s".to_string())
1419        );
1420        assert_eq!(
1421            RuntimeValue::Bytes(vec![0x01, 0x02]).to_cbor_value(),
1422            ciborium::Value::Bytes(vec![0x01, 0x02])
1423        );
1424    }
1425
1426    #[test]
1427    fn test_to_cbor_value_array() {
1428        let arr = RuntimeValue::Array(vec![RuntimeValue::Boolean(false)]);
1429        match arr.to_cbor_value() {
1430            ciborium::Value::Array(items) => {
1431                assert_eq!(items[0], ciborium::Value::Bool(false));
1432            }
1433            other => panic!("expected Array, got {other:?}"),
1434        }
1435    }
1436
1437    #[test]
1438    fn test_to_cbor_value_dict() {
1439        let mut map = BTreeMap::new();
1440        map.insert(Ident::new("k"), RuntimeValue::Boolean(true));
1441        let obj = RuntimeValue::Dict(map).to_cbor_value();
1442        match obj {
1443            ciborium::Value::Map(pairs) => {
1444                assert_eq!(pairs[0].0, ciborium::Value::Text("k".to_string()));
1445                assert_eq!(pairs[0].1, ciborium::Value::Bool(true));
1446            }
1447            other => panic!("expected Map, got {other:?}"),
1448        }
1449    }
1450
1451    #[test]
1452    fn test_to_cbor_value_other_is_null() {
1453        let native = RuntimeValue::NativeFunction(Ident::new("f")).to_cbor_value();
1454        assert_eq!(native, ciborium::Value::Null);
1455    }
1456
1457    #[test]
1458    fn test_from_yaml_scalars() {
1459        assert_eq!(RuntimeValue::from(yaml_rust2::Yaml::Null), RuntimeValue::NONE);
1460        assert_eq!(
1461            RuntimeValue::from(yaml_rust2::Yaml::Boolean(true)),
1462            RuntimeValue::Boolean(true)
1463        );
1464        assert_eq!(
1465            RuntimeValue::from(yaml_rust2::Yaml::Integer(42)),
1466            RuntimeValue::Number((42.0_f64).into())
1467        );
1468        assert_eq!(
1469            RuntimeValue::from(yaml_rust2::Yaml::String("hi".to_string())),
1470            RuntimeValue::String("hi".to_string())
1471        );
1472        assert_eq!(RuntimeValue::from(yaml_rust2::Yaml::BadValue), RuntimeValue::NONE);
1473    }
1474
1475    #[test]
1476    fn test_from_yaml_real() {
1477        let v = RuntimeValue::from(yaml_rust2::Yaml::Real("3.14".to_string()));
1478        assert!(matches!(v, RuntimeValue::Number(_)));
1479    }
1480
1481    #[test]
1482    fn test_from_yaml_array() {
1483        let yaml_arr = yaml_rust2::Yaml::Array(vec![yaml_rust2::Yaml::Integer(1), yaml_rust2::Yaml::Integer(2)]);
1484        let v = RuntimeValue::from(yaml_arr);
1485        assert!(matches!(v, RuntimeValue::Array(_)));
1486        if let RuntimeValue::Array(items) = v {
1487            assert_eq!(items.len(), 2);
1488        }
1489    }
1490
1491    #[test]
1492    fn test_from_yaml_hash() {
1493        let mut hash = yaml_rust2::yaml::Hash::new();
1494        hash.insert(
1495            yaml_rust2::Yaml::String("key".to_string()),
1496            yaml_rust2::Yaml::Integer(99),
1497        );
1498        let v = RuntimeValue::from(yaml_rust2::Yaml::Hash(hash));
1499        assert!(matches!(v, RuntimeValue::Dict(_)));
1500    }
1501
1502    #[test]
1503    fn test_from_yaml_alias() {
1504        let v = RuntimeValue::from(yaml_rust2::Yaml::Alias(0));
1505        assert_eq!(v, RuntimeValue::NONE);
1506    }
1507
1508    #[test]
1509    fn test_from_ciborium_tag_unwraps_inner() {
1510        let inner = Box::new(ciborium::Value::Bool(true));
1511        let tagged = ciborium::Value::Tag(1, inner);
1512        let v = RuntimeValue::from(tagged);
1513        assert_eq!(v, RuntimeValue::Boolean(true));
1514    }
1515
1516    #[test]
1517    fn test_from_ciborium_integer() {
1518        let v = RuntimeValue::from(ciborium::Value::Integer(42.into()));
1519        assert!(matches!(v, RuntimeValue::Number(_)));
1520    }
1521
1522    #[test]
1523    fn test_from_ciborium_null_and_unknowns() {
1524        assert_eq!(RuntimeValue::from(ciborium::Value::Null), RuntimeValue::NONE);
1525    }
1526
1527    #[test]
1528    fn test_from_ciborium_map() {
1529        let pairs = vec![(ciborium::Value::Text("k".to_string()), ciborium::Value::Bool(false))];
1530        let v = RuntimeValue::from(ciborium::Value::Map(pairs));
1531        assert!(matches!(v, RuntimeValue::Dict(_)));
1532    }
1533
1534    #[test]
1535    fn test_from_ciborium_map_non_text_key() {
1536        let pairs = vec![(ciborium::Value::Integer(1.into()), ciborium::Value::Bool(true))];
1537        let v = RuntimeValue::from(ciborium::Value::Map(pairs));
1538        assert!(matches!(v, RuntimeValue::Dict(_)));
1539    }
1540
1541    #[rstest]
1542    #[case(mq_markdown::AttrValue::String("s".to_string()), RuntimeValue::String("s".to_string()))]
1543    #[case(mq_markdown::AttrValue::Number(1.0), RuntimeValue::Number(1.0.into()))]
1544    #[case(mq_markdown::AttrValue::Boolean(true), RuntimeValue::Boolean(true))]
1545    #[case(mq_markdown::AttrValue::Null, RuntimeValue::NONE)]
1546    fn test_from_attr_value(#[case] attr: mq_markdown::AttrValue, #[case] expected: RuntimeValue) {
1547        assert_eq!(RuntimeValue::from(attr), expected);
1548    }
1549
1550    #[test]
1551    fn test_from_attr_value_integer() {
1552        let v = RuntimeValue::from(mq_markdown::AttrValue::Integer(42));
1553        assert!(matches!(v, RuntimeValue::Number(_)));
1554    }
1555
1556    #[test]
1557    fn test_from_attr_value_array() {
1558        let arr = mq_markdown::AttrValue::Array(vec![text_node("item")]);
1559        let v = RuntimeValue::from(arr);
1560        assert!(matches!(v, RuntimeValue::Array(_)));
1561    }
1562
1563    #[test]
1564    fn test_from_serde_json_number_f64() {
1565        let n = serde_json::Number::from_f64(1.5).unwrap();
1566        let v = RuntimeValue::from(serde_json::Value::Number(n));
1567        assert!(matches!(v, RuntimeValue::Number(_)));
1568    }
1569
1570    #[test]
1571    fn test_from_serde_json_object() {
1572        let mut obj = serde_json::Map::new();
1573        obj.insert("x".to_string(), serde_json::Value::Bool(true));
1574        let v = RuntimeValue::from(serde_json::Value::Object(obj));
1575        assert!(matches!(v, RuntimeValue::Dict(_)));
1576    }
1577
1578    #[test]
1579    fn test_from_vec_tuple_number() {
1580        let v = RuntimeValue::from(vec![("count".to_string(), Number::from(7.0))]);
1581        if let RuntimeValue::Dict(map) = v {
1582            assert_eq!(map.get(&Ident::new("count")), Some(&RuntimeValue::Number(7.0.into())));
1583        } else {
1584            panic!("expected dict");
1585        }
1586    }
1587
1588    #[test]
1589    fn test_update_with_non_markdown_returns_updated() {
1590        let orig: RuntimeValues = vec![RuntimeValue::Number(1.into())].into();
1591        let updated: RuntimeValues = vec![RuntimeValue::Number(99.into())].into();
1592        let result = orig.update_with(updated);
1593        assert_eq!(result[0], RuntimeValue::Number(99.into()));
1594    }
1595
1596    #[test]
1597    fn test_update_with_markdown_to_none_returns_original() {
1598        let orig: RuntimeValues = vec![md("original")].into();
1599        let updated: RuntimeValues = vec![RuntimeValue::None].into();
1600        let result = orig.update_with(updated);
1601        assert_eq!(result[0], md("original"));
1602    }
1603
1604    #[test]
1605    fn test_update_with_markdown_to_string() {
1606        let orig: RuntimeValues = vec![md("old")].into();
1607        let updated: RuntimeValues = vec![RuntimeValue::String("new".to_string())].into();
1608        let result = orig.update_with(updated);
1609        assert_eq!(result[0].markdown_node().unwrap().value(), "new");
1610    }
1611
1612    #[test]
1613    fn test_update_with_markdown_to_number() {
1614        let orig: RuntimeValues = vec![md("0")].into();
1615        let updated: RuntimeValues = vec![RuntimeValue::Number(42.into())].into();
1616        let result = orig.update_with(updated);
1617        assert_eq!(result[0].markdown_node().unwrap().value(), "42");
1618    }
1619
1620    #[test]
1621    fn test_update_with_markdown_to_boolean() {
1622        let orig: RuntimeValues = vec![md("false")].into();
1623        let updated: RuntimeValues = vec![RuntimeValue::Boolean(true)].into();
1624        let result = orig.update_with(updated);
1625        assert_eq!(result[0].markdown_node().unwrap().value(), "true");
1626    }
1627
1628    #[test]
1629    fn test_update_with_markdown_to_symbol() {
1630        let orig: RuntimeValues = vec![md("sym")].into();
1631        let updated: RuntimeValues = vec![RuntimeValue::Symbol(Ident::new("hello"))].into();
1632        let result = orig.update_with(updated);
1633        assert_eq!(result[0].markdown_node().unwrap().value(), "hello");
1634    }
1635
1636    #[test]
1637    fn test_update_with_markdown_to_bytes() {
1638        let orig: RuntimeValues = vec![md("bytes")].into();
1639        let updated: RuntimeValues = vec![RuntimeValue::Bytes(vec![0xff])].into();
1640        let result = orig.update_with(updated);
1641        assert_eq!(result[0].markdown_node().unwrap().value(), "ff");
1642    }
1643
1644    #[test]
1645    fn test_update_with_markdown_to_array_with_none_filtered() {
1646        let orig: RuntimeValues = vec![md("item")].into();
1647        let updated: RuntimeValues = vec![RuntimeValue::Array(vec![
1648            RuntimeValue::String("a".to_string()),
1649            RuntimeValue::None,
1650            RuntimeValue::String("b".to_string()),
1651        ])]
1652        .into();
1653        let result = orig.update_with(updated);
1654        if let RuntimeValue::Array(items) = &result[0] {
1655            assert_eq!(items.len(), 2);
1656        } else {
1657            panic!("expected Array");
1658        }
1659    }
1660
1661    #[test]
1662    fn test_update_with_markdown_to_dict() {
1663        let orig: RuntimeValues = vec![md("d")].into();
1664        let mut map = BTreeMap::new();
1665        map.insert(Ident::new("a"), RuntimeValue::String("val".to_string()));
1666        map.insert(Ident::new("b"), RuntimeValue::None);
1667        let updated: RuntimeValues = vec![RuntimeValue::Dict(map)].into();
1668        let result = orig.update_with(updated);
1669        if let RuntimeValue::Dict(m) = &result[0] {
1670            assert!(m.contains_key(&Ident::new("a")));
1671            assert!(!m.contains_key(&Ident::new("b"))); // None filtered out
1672        } else {
1673            panic!("expected Dict");
1674        }
1675    }
1676
1677    #[test]
1678    fn test_update_with_markdown_to_native_function_returns_original() {
1679        let orig: RuntimeValues = vec![md("orig")].into();
1680        let updated: RuntimeValues = vec![RuntimeValue::NativeFunction(Ident::new("f"))].into();
1681        let result = orig.update_with(updated);
1682        assert_eq!(result[0], md("orig"));
1683    }
1684
1685    #[test]
1686    fn test_update_with_markdown_to_empty_markdown_returns_original() {
1687        let orig: RuntimeValues = vec![md("orig")].into();
1688        let updated: RuntimeValues = vec![RuntimeValue::Markdown(Box::new(mq_markdown::Node::Empty), None)].into();
1689        let result = orig.update_with(updated);
1690        assert_eq!(result[0], md("orig"));
1691    }
1692
1693    #[test]
1694    fn test_update_with_markdown_to_non_empty_markdown_returns_updated() {
1695        let orig: RuntimeValues = vec![md("old")].into();
1696        let updated: RuntimeValues = vec![md("new")].into();
1697        let result = orig.update_with(updated);
1698        assert_eq!(result[0].markdown_node().unwrap().value(), "new");
1699    }
1700
1701    #[test]
1702    fn test_runtime_values_compact() {
1703        let values: RuntimeValues = vec![
1704            RuntimeValue::Number(1.into()),
1705            RuntimeValue::None,
1706            RuntimeValue::String("".to_string()),
1707            RuntimeValue::String("x".to_string()),
1708        ]
1709        .into();
1710        let compact = values.compact();
1711        assert_eq!(compact.len(), 2); // only Number(1) and String("x") survive
1712    }
1713
1714    #[test]
1715    fn test_runtime_values_values() {
1716        let vals = vec![RuntimeValue::Boolean(true), RuntimeValue::None];
1717        let rv: RuntimeValues = vals.clone().into();
1718        assert_eq!(rv.values(), &vals);
1719    }
1720
1721    #[test]
1722    fn test_runtime_values_into_iter() {
1723        let items: RuntimeValues = vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())].into();
1724        let sum: Vec<_> = items.into_iter().collect();
1725        assert_eq!(sum.len(), 2);
1726    }
1727
1728    #[test]
1729    fn test_module_env_name_and_len() {
1730        use crate::SharedCell;
1731        let env = Shared::new(SharedCell::new(Env::default()));
1732        let m = ModuleEnv::new("mymod", env);
1733        assert_eq!(m.name(), "mymod");
1734        assert_eq!(m.len(), 0);
1735    }
1736
1737    #[test]
1738    fn test_module_partial_cmp() {
1739        use crate::SharedCell;
1740        let e1 = Shared::new(SharedCell::new(Env::default()));
1741        let e2 = Shared::new(SharedCell::new(Env::default()));
1742        let m1 = RuntimeValue::Module(ModuleEnv::new("alpha", e1));
1743        let m2 = RuntimeValue::Module(ModuleEnv::new("beta", e2));
1744        assert!(m1 < m2);
1745    }
1746
1747    #[test]
1748    fn test_cross_type_partial_cmp_is_none() {
1749        let n = RuntimeValue::Number(1.into());
1750        let s = RuntimeValue::String("a".to_string());
1751        assert_eq!(n.partial_cmp(&s), None);
1752    }
1753
1754    #[test]
1755    fn test_ast_partial_cmp_is_none() {
1756        let ast = RuntimeValue::Ast(crate::Shared::new(crate::AstNode {
1757            token_id: crate::arena::ArenaId::new(0),
1758            expr: crate::Shared::new(crate::AstExpr::Self_),
1759        }));
1760        assert_eq!(ast.partial_cmp(&RuntimeValue::None), None);
1761        assert_eq!(RuntimeValue::None.partial_cmp(&ast), None);
1762    }
1763}