Skip to main content

vm/builtins/runtime/
core.rs

1use super::typed::{
2    VmArrayHandle, VmArrayRef, VmBytesHandle, VmBytesRef, VmMapHandle, VmMapRef, VmStringRef,
3    VmValueOwned, VmValueRef, take_arg,
4};
5use super::{AnyValue, UnknownValue, VmArray, VmBytes, VmMap, arg, return_one};
6use crate::bytecode::{SharedArray, SharedMap};
7use crate::vm::{CallReturn, Value, VmError, VmResult};
8use pd_host_function::pd_host_function;
9use rt_format::{Format, FormatArgument, NoNamedArguments, ParsedFormat, Specifier};
10use std::sync::Arc;
11
12/// Return the length of a string, array, or map.
13#[pd_host_function(name = "len")]
14pub(super) fn builtin_len_string_impl(text: VmStringRef<'_>) -> i64 {
15    text.chars().count() as i64
16}
17
18/// Return the length of an array.
19#[pd_host_function(name = "len")]
20pub(super) fn builtin_len_array_impl(items: VmArrayRef<'_>) -> i64 {
21    items.len() as i64
22}
23
24/// Return the length of a byte sequence.
25#[pd_host_function(name = "len")]
26pub(super) fn builtin_len_bytes_impl(items: VmBytesRef<'_>) -> i64 {
27    items.len() as i64
28}
29
30/// Return the number of entries in a map.
31#[pd_host_function(name = "len")]
32pub(super) fn builtin_len_map_impl(entries: VmMapRef<'_>) -> i64 {
33    entries.len() as i64
34}
35
36pub(super) fn builtin_len(args: &[Value]) -> VmResult<CallReturn> {
37    let value = arg::<&Value>(args, 0, "len value")?;
38    match value {
39        Value::String(text) => Ok(return_one(builtin_len_string_impl(text.as_str()))),
40        Value::Bytes(values) => Ok(return_one(values.len())),
41        Value::Array(values) => Ok(return_one(values.len())),
42        Value::Map(entries) => Ok(return_one(entries.len())),
43        _ => Err(VmError::TypeMismatch("string/bytes/array/map")),
44    }
45}
46
47fn slice_bounds(start: i64, length: i64) -> VmResult<Option<(usize, usize)>> {
48    if start < 0 || length <= 0 {
49        return Ok(None);
50    }
51    let start = usize::try_from(start).map_err(|_| {
52        VmError::HostError("slice start overflow while converting to usize".to_string())
53    })?;
54    let length = usize::try_from(length).map_err(|_| {
55        VmError::HostError("slice length overflow while converting to usize".to_string())
56    })?;
57    Ok(Some((start, length)))
58}
59
60/// Slice a string from the given start and length.
61#[pd_host_function(name = "slice")]
62pub(super) fn builtin_slice_string_impl(
63    text: VmStringRef<'_>,
64    start: i64,
65    length: i64,
66) -> VmResult<String> {
67    let Some((start, length)) = slice_bounds(start, length)? else {
68        return Ok(String::new());
69    };
70    Ok(text.chars().skip(start).take(length).collect::<String>())
71}
72
73/// Slice an array from the given start and length.
74#[pd_host_function(name = "slice")]
75pub(super) fn builtin_slice_array_impl(
76    items: VmArrayRef<'_>,
77    start: i64,
78    length: i64,
79) -> VmResult<VmArray> {
80    let Some((start, length)) = slice_bounds(start, length)? else {
81        return Ok(Vec::new());
82    };
83    Ok(items
84        .iter()
85        .skip(start)
86        .take(length)
87        .cloned()
88        .collect::<Vec<_>>())
89}
90
91/// Slice bytes from the given start and length.
92#[pd_host_function(name = "slice")]
93pub(super) fn builtin_slice_bytes_impl(
94    items: VmBytesRef<'_>,
95    start: i64,
96    length: i64,
97) -> VmResult<VmBytes> {
98    let Some((start, length)) = slice_bounds(start, length)? else {
99        return Ok(Vec::new());
100    };
101    Ok(items
102        .iter()
103        .skip(start)
104        .take(length)
105        .copied()
106        .collect::<Vec<_>>())
107}
108
109pub(super) fn builtin_slice(args: &[Value]) -> VmResult<CallReturn> {
110    let source = arg::<&Value>(args, 0, "slice source")?;
111    let start = arg::<i64>(args, 1, "slice start")?;
112    let length = arg::<i64>(args, 2, "slice length")?;
113    match source {
114        Value::String(text) => {
115            builtin_slice_string_impl(text.as_str(), start, length).map(return_one)
116        }
117        Value::Array(values) => {
118            let Some((start, length)) = slice_bounds(start, length)? else {
119                return Ok(return_one(Vec::<Value>::new()));
120            };
121            Ok(return_one(
122                values
123                    .iter()
124                    .skip(start)
125                    .take(length)
126                    .cloned()
127                    .collect::<Vec<_>>(),
128            ))
129        }
130        Value::Bytes(values) => {
131            let Some((start, length)) = slice_bounds(start, length)? else {
132                return Ok(return_one(Vec::<u8>::new()));
133            };
134            Ok(return_one(
135                values
136                    .iter()
137                    .skip(start)
138                    .take(length)
139                    .copied()
140                    .collect::<Vec<_>>(),
141            ))
142        }
143        _ => Err(VmError::TypeMismatch("string/bytes/array")),
144    }
145}
146
147/// Concatenate two strings.
148#[pd_host_function(name = "concat")]
149pub(super) fn builtin_concat_string_impl(left: VmStringRef<'_>, right: VmStringRef<'_>) -> String {
150    let mut out = String::with_capacity(left.len() + right.len());
151    out.push_str(left);
152    out.push_str(right);
153    out
154}
155
156/// Concatenate two arrays.
157#[pd_host_function(name = "concat")]
158pub(super) fn builtin_concat_array_impl(
159    mut left: VmArrayHandle,
160    right: VmArrayHandle,
161) -> VmArrayHandle {
162    Arc::make_mut(&mut left).extend(right.iter().cloned());
163    left
164}
165
166/// Concatenate two byte sequences.
167#[pd_host_function(name = "concat")]
168pub(super) fn builtin_concat_bytes_impl(
169    mut left: VmBytesHandle,
170    right: VmBytesHandle,
171) -> VmBytesHandle {
172    Arc::make_mut(&mut left).extend(right.iter().copied());
173    left
174}
175
176pub(super) fn builtin_concat(args: &[Value]) -> VmResult<CallReturn> {
177    let left = arg::<&Value>(args, 0, "concat left")?;
178    let right = arg::<&Value>(args, 1, "concat right")?;
179    match (left, right) {
180        (Value::String(left), Value::String(right)) => Ok(return_one(builtin_concat_string_impl(
181            left.as_str(),
182            right.as_str(),
183        ))),
184        (Value::Array(left), Value::Array(right)) => {
185            let mut values = Vec::with_capacity(left.len() + right.len());
186            values.extend(left.iter().cloned());
187            values.extend(right.iter().cloned());
188            Ok(return_one(values))
189        }
190        (Value::Bytes(left), Value::Bytes(right)) => {
191            let mut values = Vec::with_capacity(left.len() + right.len());
192            values.extend(left.iter().copied());
193            values.extend(right.iter().copied());
194            Ok(return_one(values))
195        }
196        _ => Err(VmError::TypeMismatch(
197            "string/string or bytes/bytes or array/array",
198        )),
199    }
200}
201
202/// Create an empty array.
203#[pd_host_function(name = "array_new")]
204pub(super) fn builtin_array_new_impl() -> VmArray {
205    Vec::new()
206}
207
208/// Append a value to an array and return the updated array.
209#[pd_host_function(name = "array_push")]
210pub(super) fn builtin_array_push_typed_impl(
211    mut items: VmArrayHandle,
212    value: VmValueOwned,
213) -> VmArrayHandle {
214    Arc::make_mut(&mut items).push(value);
215    items
216}
217
218fn builtin_array_push_shared_impl(mut items: SharedArray, value: AnyValue) -> SharedArray {
219    Arc::make_mut(&mut items).push(value);
220    items
221}
222
223pub(super) fn builtin_array_push(args: &mut [Value]) -> VmResult<CallReturn> {
224    let items = match take_arg(args, 0, "array_push array")? {
225        Value::Array(values) => values,
226        _ => return Err(VmError::TypeMismatch("array")),
227    };
228    let value = take_arg(args, 1, "array_push value")?;
229    Ok(return_one(Value::Array(builtin_array_push_shared_impl(
230        items, value,
231    ))))
232}
233
234/// Create an empty map.
235#[pd_host_function(name = "map_new")]
236pub(super) fn builtin_map_new_impl() -> VmMap {
237    VmMap::new()
238}
239
240/// Read a string entry.
241#[pd_host_function(name = "get")]
242pub(super) fn builtin_get_string_impl(text: VmStringRef<'_>, index: i64) -> VmResult<String> {
243    if index < 0 {
244        return Err(VmError::HostError(
245            "string index must be non-negative".to_string(),
246        ));
247    }
248    let index = usize::try_from(index)
249        .map_err(|_| VmError::HostError("string index overflow".to_string()))?;
250    text.chars()
251        .nth(index)
252        .map(|ch| ch.to_string())
253        .ok_or_else(|| VmError::HostError(format!("string index {index} out of bounds")))
254}
255
256/// Read an array element by index.
257#[pd_host_function(name = "get")]
258pub(super) fn builtin_get_array_impl(items: VmArrayRef<'_>, index: i64) -> VmResult<UnknownValue> {
259    if index < 0 {
260        return Err(VmError::HostError(
261            "array index must be non-negative".to_string(),
262        ));
263    }
264    let index = usize::try_from(index)
265        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
266    items
267        .get(index)
268        .cloned()
269        .ok_or_else(|| VmError::HostError(format!("array index {index} out of bounds")))
270}
271
272/// Read a byte value by index.
273#[pd_host_function(name = "get")]
274pub(super) fn builtin_get_bytes_impl(items: VmBytesRef<'_>, index: i64) -> VmResult<i64> {
275    if index < 0 {
276        return Err(VmError::HostError(
277            "bytes index must be non-negative".to_string(),
278        ));
279    }
280    let index = usize::try_from(index)
281        .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
282    items
283        .get(index)
284        .copied()
285        .map(i64::from)
286        .ok_or_else(|| VmError::HostError(format!("bytes index {index} out of bounds")))
287}
288
289/// Read a map value by key.
290#[pd_host_function(name = "get")]
291pub(super) fn builtin_get_map_impl(
292    entries: VmMapRef<'_>,
293    key: VmValueRef<'_>,
294) -> VmResult<UnknownValue> {
295    entries
296        .get(key)
297        .cloned()
298        .ok_or_else(|| VmError::HostError("map key not found".to_string()))
299}
300
301/// Check whether an array contains a valid index.
302#[pd_host_function(name = "has")]
303pub(super) fn builtin_has_array_impl(items: VmArrayRef<'_>, index: i64) -> bool {
304    if index < 0 {
305        return false;
306    }
307    usize::try_from(index)
308        .ok()
309        .is_some_and(|index| index < items.len())
310}
311
312/// Check whether bytes contain a valid index.
313#[pd_host_function(name = "has")]
314pub(super) fn builtin_has_bytes_impl(items: VmBytesRef<'_>, index: i64) -> bool {
315    if index < 0 {
316        return false;
317    }
318    usize::try_from(index)
319        .ok()
320        .is_some_and(|index| index < items.len())
321}
322
323/// Check whether a map contains a key.
324#[pd_host_function(name = "has")]
325pub(super) fn builtin_has_map_impl(entries: VmMapRef<'_>, key: VmValueRef<'_>) -> bool {
326    entries.get(key).is_some()
327}
328
329pub(super) fn builtin_has(args: &[Value]) -> VmResult<CallReturn> {
330    let container = arg::<&Value>(args, 0, "has container")?;
331    let key = arg::<&Value>(args, 1, "has key")?;
332    match container {
333        Value::Array(values) => {
334            let index = key.as_int()?;
335            let present = if index < 0 {
336                false
337            } else {
338                usize::try_from(index)
339                    .ok()
340                    .is_some_and(|index| index < values.len())
341            };
342            Ok(return_one(present))
343        }
344        Value::Bytes(values) => {
345            let index = key.as_int()?;
346            let present = if index < 0 {
347                false
348            } else {
349                usize::try_from(index)
350                    .ok()
351                    .is_some_and(|index| index < values.len())
352            };
353            Ok(return_one(present))
354        }
355        Value::Map(entries) => Ok(return_one(entries.get(key).is_some())),
356        _ => Err(VmError::TypeMismatch("bytes/array/map")),
357    }
358}
359
360pub(super) fn builtin_get(args: &[Value]) -> VmResult<CallReturn> {
361    let container = arg::<&Value>(args, 0, "get container")?;
362    let key = arg::<&Value>(args, 1, "get key")?;
363    match container {
364        Value::Array(values) => {
365            let index = key.as_int()?;
366            if index < 0 {
367                return Err(VmError::HostError(
368                    "array index must be non-negative".to_string(),
369                ));
370            }
371            let index = usize::try_from(index)
372                .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
373            Ok(return_one(values.get(index).cloned().ok_or_else(|| {
374                VmError::HostError(format!("array index {index} out of bounds"))
375            })?))
376        }
377        Value::Map(entries) => {
378            Ok(return_one(entries.get(key).cloned().ok_or_else(|| {
379                VmError::HostError("map key not found".to_string())
380            })?))
381        }
382        Value::Bytes(values) => {
383            let index = key.as_int()?;
384            if index < 0 {
385                return Err(VmError::HostError(
386                    "bytes index must be non-negative".to_string(),
387                ));
388            }
389            let index = usize::try_from(index)
390                .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
391            Ok(return_one(i64::from(
392                values.get(index).copied().ok_or_else(|| {
393                    VmError::HostError(format!("bytes index {index} out of bounds"))
394                })?,
395            )))
396        }
397        Value::String(text) => {
398            builtin_get_string_impl(text.as_str(), key.as_int()?).map(return_one)
399        }
400        _ => Err(VmError::TypeMismatch("bytes/array/map/string")),
401    }
402}
403
404/// Return the runtime type name of a value.
405#[pd_host_function(name = "type")]
406pub(super) fn builtin_type_of_impl(value: VmValueRef<'_>) -> String {
407    match value {
408        Value::Null => "null",
409        Value::Int(_) => "int",
410        Value::Float(_) => "float",
411        Value::Bool(_) => "bool",
412        Value::String(_) => "string",
413        Value::Bytes(_) => "bytes",
414        Value::Array(_) => "array",
415        Value::Map(_) => "map",
416    }
417    .to_string()
418}
419
420/// Convert a value into a display string.
421#[pd_host_function(name = "__to_string")]
422pub(super) fn builtin_to_string_impl(value: VmValueRef<'_>) -> String {
423    render_value_for_display(value)
424}
425
426/// Render a format template against an array of positional values.
427#[pd_host_function(name = "__format_template")]
428pub(super) fn builtin_format_template_impl(
429    template: &str,
430    values: VmArrayRef<'_>,
431) -> VmResult<String> {
432    ParsedFormat::parse(template, values, &NoNamedArguments)
433        .map(|parsed| parsed.to_string())
434        .map_err(|offset| {
435            VmError::HostError(format!(
436                "format string and arguments are incompatible at byte {offset}: {template}"
437            ))
438        })
439}
440
441fn render_value_for_display(value: &Value) -> String {
442    match value {
443        Value::Null => "null".to_string(),
444        Value::Int(v) => v.to_string(),
445        Value::Float(v) => v.to_string(),
446        Value::Bool(v) => v.to_string(),
447        Value::String(v) => v.as_str().to_string(),
448        Value::Bytes(v) => render_bytes_for_display(v.as_ref()),
449        Value::Array(values) => {
450            let parts = values
451                .iter()
452                .map(render_value_for_display)
453                .collect::<Vec<_>>()
454                .join(", ");
455            format!("[{parts}]")
456        }
457        Value::Map(entries) => {
458            let parts = entries
459                .iter()
460                .map(|(key, value)| {
461                    format!(
462                        "{}: {}",
463                        render_value_for_display(key),
464                        render_value_for_display(value)
465                    )
466                })
467                .collect::<Vec<_>>()
468                .join(", ");
469            format!("{{{parts}}}")
470        }
471    }
472}
473
474fn render_bytes_for_display(bytes: &[u8]) -> String {
475    let preview_len = bytes.len().min(16);
476    let mut preview = String::with_capacity(preview_len * 2);
477    for byte in &bytes[..preview_len] {
478        preview.push(hex_nibble(byte >> 4));
479        preview.push(hex_nibble(byte & 0x0F));
480    }
481    if bytes.len() > preview_len {
482        format!("bytes[len={} hex={}..]", bytes.len(), preview)
483    } else {
484        format!("bytes[len={} hex={}]", bytes.len(), preview)
485    }
486}
487
488fn hex_nibble(value: u8) -> char {
489    match value {
490        0..=9 => char::from(b'0' + value),
491        10..=15 => char::from(b'a' + (value - 10)),
492        _ => unreachable!("hex nibble out of range"),
493    }
494}
495
496impl FormatArgument for Value {
497    fn supports_format(&self, specifier: &Specifier) -> bool {
498        match self {
499            Value::Null => matches!(specifier.format, Format::Display | Format::Debug),
500            Value::Int(_) => true,
501            Value::Float(_) => matches!(
502                specifier.format,
503                Format::Display | Format::Debug | Format::LowerExp | Format::UpperExp
504            ),
505            Value::Bool(_) => matches!(specifier.format, Format::Display | Format::Debug),
506            Value::String(_) | Value::Bytes(_) => {
507                matches!(specifier.format, Format::Display | Format::Debug)
508            }
509            Value::Array(_) | Value::Map(_) => {
510                matches!(specifier.format, Format::Display | Format::Debug)
511            }
512        }
513    }
514
515    fn fmt_display(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
516        match self {
517            Value::Null => f.write_str("null"),
518            Value::Int(value) => std::fmt::Display::fmt(value, f),
519            Value::Float(value) => std::fmt::Display::fmt(value, f),
520            Value::Bool(value) => std::fmt::Display::fmt(value, f),
521            Value::String(value) => std::fmt::Display::fmt(value.as_str(), f),
522            Value::Bytes(_) | Value::Array(_) | Value::Map(_) => {
523                f.write_str(render_value_for_display(self).as_str())
524            }
525        }
526    }
527
528    fn fmt_debug(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
529        match self {
530            Value::Null => f.write_str("null"),
531            Value::Int(value) => std::fmt::Debug::fmt(value, f),
532            Value::Float(value) => std::fmt::Debug::fmt(value, f),
533            Value::Bool(value) => std::fmt::Debug::fmt(value, f),
534            Value::String(value) => std::fmt::Debug::fmt(value.as_str(), f),
535            Value::Bytes(_) => f.write_str(render_value_for_display(self).as_str()),
536            Value::Array(values) => {
537                let mut list = f.debug_list();
538                for value in values.iter() {
539                    list.entry(value);
540                }
541                list.finish()
542            }
543            Value::Map(entries) => {
544                let mut map = f.debug_map();
545                for (key, value) in entries.iter() {
546                    map.entry(key, value);
547                }
548                map.finish()
549            }
550        }
551    }
552
553    fn fmt_octal(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
554        match self {
555            Value::Int(value) => std::fmt::Octal::fmt(value, f),
556            _ => Err(std::fmt::Error),
557        }
558    }
559
560    fn fmt_lower_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
561        match self {
562            Value::Int(value) => std::fmt::LowerHex::fmt(value, f),
563            _ => Err(std::fmt::Error),
564        }
565    }
566
567    fn fmt_upper_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
568        match self {
569            Value::Int(value) => std::fmt::UpperHex::fmt(value, f),
570            _ => Err(std::fmt::Error),
571        }
572    }
573
574    fn fmt_binary(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
575        match self {
576            Value::Int(value) => std::fmt::Binary::fmt(value, f),
577            _ => Err(std::fmt::Error),
578        }
579    }
580
581    fn fmt_lower_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
582        match self {
583            Value::Int(value) => std::fmt::LowerExp::fmt(value, f),
584            Value::Float(value) => std::fmt::LowerExp::fmt(value, f),
585            _ => Err(std::fmt::Error),
586        }
587    }
588
589    fn fmt_upper_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
590        match self {
591            Value::Int(value) => std::fmt::UpperExp::fmt(value, f),
592            Value::Float(value) => std::fmt::UpperExp::fmt(value, f),
593            _ => Err(std::fmt::Error),
594        }
595    }
596
597    fn to_usize(&self) -> Result<usize, ()> {
598        match self {
599            Value::Int(value) => usize::try_from(*value).map_err(|_| ()),
600            _ => Err(()),
601        }
602    }
603}
604
605/// Update an array entry and return the updated array.
606#[pd_host_function(name = "set")]
607pub(super) fn builtin_set_array_impl(
608    mut items: VmArrayHandle,
609    index: i64,
610    value: VmValueOwned,
611) -> VmResult<VmArrayHandle> {
612    let items_mut = Arc::make_mut(&mut items);
613    if index < 0 {
614        return Err(VmError::HostError(
615            "array index must be non-negative".to_string(),
616        ));
617    }
618    let index = usize::try_from(index)
619        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
620    if index < items_mut.len() {
621        items_mut[index] = value;
622    } else if index == items_mut.len() {
623        items_mut.push(value);
624    } else {
625        return Err(VmError::HostError(format!(
626            "array index {index} out of bounds"
627        )));
628    }
629    Ok(items)
630}
631
632fn builtin_set_array_shared_impl(
633    mut items: SharedArray,
634    index: i64,
635    value: AnyValue,
636) -> VmResult<SharedArray> {
637    let items_mut = Arc::make_mut(&mut items);
638    if index < 0 {
639        return Err(VmError::HostError(
640            "array index must be non-negative".to_string(),
641        ));
642    }
643    let index = usize::try_from(index)
644        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
645    if index < items_mut.len() {
646        items_mut[index] = value;
647    } else if index == items_mut.len() {
648        items_mut.push(value);
649    } else {
650        return Err(VmError::HostError(format!(
651            "array index {index} out of bounds"
652        )));
653    }
654    Ok(items)
655}
656
657/// Update a map entry and return the updated map.
658#[pd_host_function(name = "set")]
659pub(super) fn builtin_set_map_impl(
660    mut entries: VmMapHandle,
661    key: VmValueOwned,
662    value: VmValueOwned,
663) -> VmMapHandle {
664    let entries_mut = Arc::make_mut(&mut entries);
665    if matches!(value, Value::Null) {
666        entries_mut.remove(&key);
667    } else {
668        entries_mut.insert(key, value);
669    }
670    entries
671}
672
673fn builtin_set_map_shared_impl(
674    mut entries: SharedMap,
675    key: AnyValue,
676    value: AnyValue,
677) -> SharedMap {
678    let entries_mut = Arc::make_mut(&mut entries);
679    if matches!(value, Value::Null) {
680        entries_mut.remove(&key);
681    } else {
682        entries_mut.insert(key, value);
683    }
684    entries
685}
686
687pub(super) fn builtin_set(args: &mut [Value]) -> VmResult<CallReturn> {
688    let container: Value = take_arg(args, 0, "set container")?;
689    let key: Value = take_arg(args, 1, "set key")?;
690    let value: Value = take_arg(args, 2, "set value")?;
691    match container {
692        Value::Array(values) => builtin_set_array_shared_impl(values, key.as_int()?, value)
693            .map(|values| return_one(Value::Array(values))),
694        Value::Map(entries) => Ok(return_one(Value::Map(builtin_set_map_shared_impl(
695            entries, key, value,
696        )))),
697        _ => Err(VmError::TypeMismatch("array/map")),
698    }
699}
700
701/// Return an array of container keys or indices.
702#[pd_host_function(name = "keys")]
703pub(super) fn builtin_keys_array_impl(items: VmArrayRef<'_>) -> VmArray {
704    (0..items.len())
705        .map(|index| Value::Int(index as i64))
706        .collect::<Vec<_>>()
707}
708
709/// Return an array of map keys.
710#[pd_host_function(name = "keys")]
711pub(super) fn builtin_keys_map_impl(entries: VmMapRef<'_>) -> VmArray {
712    entries
713        .iter()
714        .map(|(key, _)| key.clone())
715        .collect::<Vec<_>>()
716}
717
718pub(super) fn builtin_keys(args: &[Value]) -> VmResult<CallReturn> {
719    let container = arg::<&Value>(args, 0, "keys container")?;
720    match container {
721        Value::Array(values) => Ok(return_one(
722            (0..values.len())
723                .map(|index| Value::Int(index as i64))
724                .collect::<Vec<_>>(),
725        )),
726        Value::Map(entries) => Ok(return_one(
727            entries
728                .iter()
729                .map(|(key, _)| key.clone())
730                .collect::<Vec<_>>(),
731        )),
732        _ => Err(VmError::TypeMismatch("array/map")),
733    }
734}
735
736/// Return the number of entries in an array or map.
737#[pd_host_function(name = "count")]
738pub(super) fn builtin_count_array_impl(items: VmArrayRef<'_>) -> i64 {
739    items.len() as i64
740}
741
742/// Return the number of entries in a map.
743#[pd_host_function(name = "count")]
744pub(super) fn builtin_count_map_impl(entries: VmMapRef<'_>) -> i64 {
745    entries.len() as i64
746}
747
748pub(super) fn builtin_count(args: &[Value]) -> VmResult<CallReturn> {
749    let container = arg::<&Value>(args, 0, "count container")?;
750    match container {
751        Value::Array(values) => Ok(return_one(values.len())),
752        Value::Map(entries) => Ok(return_one(entries.len())),
753        _ => Err(VmError::TypeMismatch("array/map")),
754    }
755}
756
757/// Abort execution if the condition is false.
758#[pd_host_function(name = "assert")]
759pub(super) fn builtin_assert_impl(condition: bool) -> VmResult<()> {
760    if condition {
761        Ok(())
762    } else {
763        Err(VmError::HostError("assertion failed".to_string()))
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use super::*;
770    use std::sync::Arc;
771
772    #[test]
773    fn array_push_detaches_shared_array_before_write() {
774        let shared = Value::array(vec![Value::Int(1)]);
775        let alias = shared.clone();
776
777        let mut args = [shared, Value::Int(2)];
778        let out = builtin_array_push(&mut args).expect("array push should work");
779        let [Value::Array(result)] = out.as_slice() else {
780            panic!("expected array result");
781        };
782        let Value::Array(alias_values) = &alias else {
783            panic!("expected array alias");
784        };
785
786        assert_eq!(alias_values.as_ref(), &vec![Value::Int(1)]);
787        assert_eq!(result.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
788        assert!(
789            !Arc::ptr_eq(alias_values, result),
790            "mutating a shared array should detach backing storage"
791        );
792    }
793
794    #[test]
795    fn set_detaches_shared_array_before_write() {
796        let shared = Value::array(vec![Value::Int(1), Value::Int(2)]);
797        let alias = shared.clone();
798
799        let mut args = [shared, Value::Int(0), Value::Int(9)];
800        let out = builtin_set(&mut args).expect("array set should work");
801        let [Value::Array(result)] = out.as_slice() else {
802            panic!("expected array result");
803        };
804        let Value::Array(alias_values) = &alias else {
805            panic!("expected array alias");
806        };
807
808        assert_eq!(alias_values.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
809        assert_eq!(result.as_ref(), &vec![Value::Int(9), Value::Int(2)]);
810        assert!(
811            !Arc::ptr_eq(alias_values, result),
812            "mutating a shared array should detach backing storage"
813        );
814    }
815
816    #[test]
817    fn set_detaches_shared_map_before_write() {
818        let shared = Value::map(vec![(Value::string("k"), Value::Int(1))]);
819        let alias = shared.clone();
820
821        let mut args = [shared, Value::string("k"), Value::Int(9)];
822        let out = builtin_set(&mut args).expect("map set should work");
823        let [Value::Map(result)] = out.as_slice() else {
824            panic!("expected map result");
825        };
826        let Value::Map(alias_entries) = &alias else {
827            panic!("expected map alias");
828        };
829
830        assert_eq!(alias_entries.len(), 1);
831        assert_eq!(alias_entries.get(&Value::string("k")), Some(&Value::Int(1)));
832        assert_eq!(result.len(), 1);
833        assert_eq!(result.get(&Value::string("k")), Some(&Value::Int(9)));
834        assert!(
835            !Arc::ptr_eq(alias_entries, result),
836            "mutating a shared map should detach backing storage"
837        );
838    }
839
840    #[test]
841    fn set_map_null_removes_entry() {
842        let shared = Value::map(vec![(Value::string("drop"), Value::Int(1))]);
843        let alias = shared.clone();
844
845        let mut args = [shared, Value::string("drop"), Value::Null];
846        let out = builtin_set(&mut args).expect("map null set should work");
847        let [Value::Map(result)] = out.as_slice() else {
848            panic!("expected map result");
849        };
850        let Value::Map(alias_entries) = &alias else {
851            panic!("expected map alias");
852        };
853
854        assert_eq!(alias_entries.len(), 1);
855        assert_eq!(
856            alias_entries.get(&Value::string("drop")),
857            Some(&Value::Int(1))
858        );
859        assert_eq!(result.len(), 0);
860        assert_eq!(result.get(&Value::string("drop")), None);
861        assert!(
862            !Arc::ptr_eq(alias_entries, result),
863            "mutating a shared map should detach backing storage"
864        );
865    }
866
867    #[test]
868    fn has_map_uses_identity_for_heap_keys() {
869        let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
870        let alias = key.clone();
871        let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
872        let map = VmMap::from(vec![(key, Value::Bool(true))]);
873
874        assert!(builtin_has_map_impl(&map, &alias));
875        assert!(!builtin_has_map_impl(&map, &structural_peer));
876    }
877
878    #[test]
879    fn has_dispatch_uses_identity_for_heap_keys() {
880        let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
881        let alias = key.clone();
882        let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
883        let map = Value::map(vec![(key, Value::Bool(true))]);
884
885        let alias_result = builtin_has(&[map.clone(), alias]).expect("builtin has should succeed");
886        let [Value::Bool(alias_present)] = alias_result.as_slice() else {
887            panic!("expected bool result");
888        };
889        assert!(*alias_present, "shared heap key should match by identity");
890
891        let peer_result = builtin_has(&[map, structural_peer]).expect("builtin has should succeed");
892        let [Value::Bool(peer_present)] = peer_result.as_slice() else {
893            panic!("expected bool result");
894        };
895        assert!(
896            !peer_present,
897            "structural peer should not match a map key stored by heap identity"
898        );
899    }
900
901    #[test]
902    fn len_and_count_dispatch_return_shared_container_sizes() {
903        let array = Value::array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
904        let map = Value::map(vec![
905            (Value::string("a"), Value::Int(1)),
906            (Value::string("b"), Value::Int(2)),
907        ]);
908
909        let array_len =
910            builtin_len(std::slice::from_ref(&array)).expect("array len should succeed");
911        let [Value::Int(array_len)] = array_len.as_slice() else {
912            panic!("expected int result");
913        };
914        assert_eq!(*array_len, 3);
915
916        let array_count = builtin_count(&[array]).expect("array count should succeed");
917        let [Value::Int(array_count)] = array_count.as_slice() else {
918            panic!("expected int result");
919        };
920        assert_eq!(*array_count, 3);
921
922        let map_len = builtin_len(std::slice::from_ref(&map)).expect("map len should succeed");
923        let [Value::Int(map_len)] = map_len.as_slice() else {
924            panic!("expected int result");
925        };
926        assert_eq!(*map_len, 2);
927
928        let map_count = builtin_count(&[map]).expect("map count should succeed");
929        let [Value::Int(map_count)] = map_count.as_slice() else {
930            panic!("expected int result");
931        };
932        assert_eq!(*map_count, 2);
933    }
934}