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
218pub(crate) fn builtin_array_push_shared_impl(
219    mut items: SharedArray,
220    value: AnyValue,
221) -> SharedArray {
222    Arc::make_mut(&mut items).push(value);
223    items
224}
225
226pub(crate) fn builtin_array_push_owned(items: Value, value: Value) -> VmResult<Value> {
227    let items = match items {
228        Value::Array(values) => values,
229        _ => return Err(VmError::TypeMismatch("array")),
230    };
231    Ok(Value::Array(builtin_array_push_shared_impl(items, value)))
232}
233
234pub(super) fn builtin_array_push(args: &mut [Value]) -> VmResult<CallReturn> {
235    let items = take_arg(args, 0, "array_push array")?;
236    let value = take_arg(args, 1, "array_push value")?;
237    builtin_array_push_owned(items, value).map(return_one)
238}
239
240/// Create an empty map.
241#[pd_host_function(name = "map_new")]
242pub(super) fn builtin_map_new_impl() -> VmMap {
243    VmMap::new()
244}
245
246/// Read a string entry.
247#[pd_host_function(name = "get")]
248pub(super) fn builtin_get_string_impl(text: VmStringRef<'_>, index: i64) -> VmResult<String> {
249    if index < 0 {
250        return Err(VmError::HostError(
251            "string index must be non-negative".to_string(),
252        ));
253    }
254    let index = usize::try_from(index)
255        .map_err(|_| VmError::HostError("string index overflow".to_string()))?;
256    text.chars()
257        .nth(index)
258        .map(|ch| ch.to_string())
259        .ok_or_else(|| VmError::HostError(format!("string index {index} out of bounds")))
260}
261
262/// Read an array element by index.
263#[pd_host_function(name = "get")]
264pub(super) fn builtin_get_array_impl(items: VmArrayRef<'_>, index: i64) -> VmResult<UnknownValue> {
265    if index < 0 {
266        return Err(VmError::HostError(
267            "array index must be non-negative".to_string(),
268        ));
269    }
270    let index = usize::try_from(index)
271        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
272    items
273        .get(index)
274        .cloned()
275        .ok_or_else(|| VmError::HostError(format!("array index {index} out of bounds")))
276}
277
278/// Read a byte value by index.
279#[pd_host_function(name = "get")]
280pub(super) fn builtin_get_bytes_impl(items: VmBytesRef<'_>, index: i64) -> VmResult<i64> {
281    if index < 0 {
282        return Err(VmError::HostError(
283            "bytes index must be non-negative".to_string(),
284        ));
285    }
286    let index = usize::try_from(index)
287        .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
288    items
289        .get(index)
290        .copied()
291        .map(i64::from)
292        .ok_or_else(|| VmError::HostError(format!("bytes index {index} out of bounds")))
293}
294
295/// Read a map value by key.
296#[pd_host_function(name = "get")]
297pub(super) fn builtin_get_map_impl(
298    entries: VmMapRef<'_>,
299    key: VmValueRef<'_>,
300) -> VmResult<UnknownValue> {
301    entries
302        .get(key)
303        .cloned()
304        .ok_or_else(|| VmError::HostError("map key not found".to_string()))
305}
306
307/// Check whether an array contains a valid index.
308#[pd_host_function(name = "has")]
309pub(super) fn builtin_has_array_impl(items: VmArrayRef<'_>, index: i64) -> bool {
310    if index < 0 {
311        return false;
312    }
313    usize::try_from(index)
314        .ok()
315        .is_some_and(|index| index < items.len())
316}
317
318/// Check whether bytes contain a valid index.
319#[pd_host_function(name = "has")]
320pub(super) fn builtin_has_bytes_impl(items: VmBytesRef<'_>, index: i64) -> bool {
321    if index < 0 {
322        return false;
323    }
324    usize::try_from(index)
325        .ok()
326        .is_some_and(|index| index < items.len())
327}
328
329/// Check whether a map contains a key.
330#[pd_host_function(name = "has")]
331pub(super) fn builtin_has_map_impl(entries: VmMapRef<'_>, key: VmValueRef<'_>) -> bool {
332    entries.get(key).is_some()
333}
334
335pub(super) fn builtin_has(args: &[Value]) -> VmResult<CallReturn> {
336    let container = arg::<&Value>(args, 0, "has container")?;
337    let key = arg::<&Value>(args, 1, "has key")?;
338    match container {
339        Value::Array(values) => {
340            let index = key.as_int()?;
341            let present = if index < 0 {
342                false
343            } else {
344                usize::try_from(index)
345                    .ok()
346                    .is_some_and(|index| index < values.len())
347            };
348            Ok(return_one(present))
349        }
350        Value::Bytes(values) => {
351            let index = key.as_int()?;
352            let present = if index < 0 {
353                false
354            } else {
355                usize::try_from(index)
356                    .ok()
357                    .is_some_and(|index| index < values.len())
358            };
359            Ok(return_one(present))
360        }
361        Value::Map(entries) => Ok(return_one(entries.get(key).is_some())),
362        _ => Err(VmError::TypeMismatch("bytes/array/map")),
363    }
364}
365
366pub(super) fn builtin_get(args: &[Value]) -> VmResult<CallReturn> {
367    let container = arg::<&Value>(args, 0, "get container")?;
368    let key = arg::<&Value>(args, 1, "get key")?;
369    match container {
370        Value::Array(values) => {
371            let index = key.as_int()?;
372            if index < 0 {
373                return Err(VmError::HostError(
374                    "array index must be non-negative".to_string(),
375                ));
376            }
377            let index = usize::try_from(index)
378                .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
379            Ok(return_one(values.get(index).cloned().ok_or_else(|| {
380                VmError::HostError(format!("array index {index} out of bounds"))
381            })?))
382        }
383        Value::Map(entries) => {
384            Ok(return_one(entries.get(key).cloned().ok_or_else(|| {
385                VmError::HostError("map key not found".to_string())
386            })?))
387        }
388        Value::Bytes(values) => {
389            let index = key.as_int()?;
390            if index < 0 {
391                return Err(VmError::HostError(
392                    "bytes index must be non-negative".to_string(),
393                ));
394            }
395            let index = usize::try_from(index)
396                .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
397            Ok(return_one(i64::from(
398                values.get(index).copied().ok_or_else(|| {
399                    VmError::HostError(format!("bytes index {index} out of bounds"))
400                })?,
401            )))
402        }
403        Value::String(text) => {
404            builtin_get_string_impl(text.as_str(), key.as_int()?).map(return_one)
405        }
406        _ => Err(VmError::TypeMismatch("bytes/array/map/string")),
407    }
408}
409
410/// Return the runtime type name of a value.
411#[pd_host_function(name = "type")]
412pub(super) fn builtin_type_of_impl(value: VmValueRef<'_>) -> String {
413    match value {
414        Value::Null => "null",
415        Value::Int(_) => "int",
416        Value::Float(_) => "float",
417        Value::Bool(_) => "bool",
418        Value::String(_) => "string",
419        Value::Bytes(_) => "bytes",
420        Value::Array(_) => "array",
421        Value::Map(_) => "map",
422    }
423    .to_string()
424}
425
426/// Convert a value into a display string.
427#[pd_host_function(name = "__to_string")]
428pub(super) fn builtin_to_string_impl(value: VmValueRef<'_>) -> String {
429    render_value_for_display(value)
430}
431
432/// Render a format template against an array of positional values.
433#[pd_host_function(name = "__format_template")]
434pub(super) fn builtin_format_template_impl(
435    template: &str,
436    values: VmArrayRef<'_>,
437) -> VmResult<String> {
438    ParsedFormat::parse(template, values, &NoNamedArguments)
439        .map(|parsed| parsed.to_string())
440        .map_err(|offset| {
441            VmError::HostError(format!(
442                "format string and arguments are incompatible at byte {offset}: {template}"
443            ))
444        })
445}
446
447fn render_value_for_display(value: &Value) -> String {
448    match value {
449        Value::Null => "null".to_string(),
450        Value::Int(v) => v.to_string(),
451        Value::Float(v) => v.to_string(),
452        Value::Bool(v) => v.to_string(),
453        Value::String(v) => v.as_str().to_string(),
454        Value::Bytes(v) => render_bytes_for_display(v.as_ref()),
455        Value::Array(values) => {
456            let parts = values
457                .iter()
458                .map(render_value_for_display)
459                .collect::<Vec<_>>()
460                .join(", ");
461            format!("[{parts}]")
462        }
463        Value::Map(entries) => {
464            let parts = entries
465                .iter()
466                .map(|(key, value)| {
467                    format!(
468                        "{}: {}",
469                        render_value_for_display(key),
470                        render_value_for_display(value)
471                    )
472                })
473                .collect::<Vec<_>>()
474                .join(", ");
475            format!("{{{parts}}}")
476        }
477    }
478}
479
480fn render_bytes_for_display(bytes: &[u8]) -> String {
481    let preview_len = bytes.len().min(16);
482    let mut preview = String::with_capacity(preview_len * 2);
483    for byte in &bytes[..preview_len] {
484        preview.push(hex_nibble(byte >> 4));
485        preview.push(hex_nibble(byte & 0x0F));
486    }
487    if bytes.len() > preview_len {
488        format!("bytes[len={} hex={}..]", bytes.len(), preview)
489    } else {
490        format!("bytes[len={} hex={}]", bytes.len(), preview)
491    }
492}
493
494fn hex_nibble(value: u8) -> char {
495    match value {
496        0..=9 => char::from(b'0' + value),
497        10..=15 => char::from(b'a' + (value - 10)),
498        _ => unreachable!("hex nibble out of range"),
499    }
500}
501
502impl FormatArgument for Value {
503    fn supports_format(&self, specifier: &Specifier) -> bool {
504        match self {
505            Value::Null => matches!(specifier.format, Format::Display | Format::Debug),
506            Value::Int(_) => true,
507            Value::Float(_) => matches!(
508                specifier.format,
509                Format::Display | Format::Debug | Format::LowerExp | Format::UpperExp
510            ),
511            Value::Bool(_) => matches!(specifier.format, Format::Display | Format::Debug),
512            Value::String(_) | Value::Bytes(_) => {
513                matches!(specifier.format, Format::Display | Format::Debug)
514            }
515            Value::Array(_) | Value::Map(_) => {
516                matches!(specifier.format, Format::Display | Format::Debug)
517            }
518        }
519    }
520
521    fn fmt_display(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
522        match self {
523            Value::Null => f.write_str("null"),
524            Value::Int(value) => std::fmt::Display::fmt(value, f),
525            Value::Float(value) => std::fmt::Display::fmt(value, f),
526            Value::Bool(value) => std::fmt::Display::fmt(value, f),
527            Value::String(value) => std::fmt::Display::fmt(value.as_str(), f),
528            Value::Bytes(_) | Value::Array(_) | Value::Map(_) => {
529                f.write_str(render_value_for_display(self).as_str())
530            }
531        }
532    }
533
534    fn fmt_debug(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
535        match self {
536            Value::Null => f.write_str("null"),
537            Value::Int(value) => std::fmt::Debug::fmt(value, f),
538            Value::Float(value) => std::fmt::Debug::fmt(value, f),
539            Value::Bool(value) => std::fmt::Debug::fmt(value, f),
540            Value::String(value) => std::fmt::Debug::fmt(value.as_str(), f),
541            Value::Bytes(_) => f.write_str(render_value_for_display(self).as_str()),
542            Value::Array(values) => {
543                let mut list = f.debug_list();
544                for value in values.iter() {
545                    list.entry(value);
546                }
547                list.finish()
548            }
549            Value::Map(entries) => {
550                let mut map = f.debug_map();
551                for (key, value) in entries.iter() {
552                    map.entry(key, value);
553                }
554                map.finish()
555            }
556        }
557    }
558
559    fn fmt_octal(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
560        match self {
561            Value::Int(value) => std::fmt::Octal::fmt(value, f),
562            _ => Err(std::fmt::Error),
563        }
564    }
565
566    fn fmt_lower_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
567        match self {
568            Value::Int(value) => std::fmt::LowerHex::fmt(value, f),
569            _ => Err(std::fmt::Error),
570        }
571    }
572
573    fn fmt_upper_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
574        match self {
575            Value::Int(value) => std::fmt::UpperHex::fmt(value, f),
576            _ => Err(std::fmt::Error),
577        }
578    }
579
580    fn fmt_binary(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
581        match self {
582            Value::Int(value) => std::fmt::Binary::fmt(value, f),
583            _ => Err(std::fmt::Error),
584        }
585    }
586
587    fn fmt_lower_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
588        match self {
589            Value::Int(value) => std::fmt::LowerExp::fmt(value, f),
590            Value::Float(value) => std::fmt::LowerExp::fmt(value, f),
591            _ => Err(std::fmt::Error),
592        }
593    }
594
595    fn fmt_upper_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
596        match self {
597            Value::Int(value) => std::fmt::UpperExp::fmt(value, f),
598            Value::Float(value) => std::fmt::UpperExp::fmt(value, f),
599            _ => Err(std::fmt::Error),
600        }
601    }
602
603    fn to_usize(&self) -> Result<usize, ()> {
604        match self {
605            Value::Int(value) => usize::try_from(*value).map_err(|_| ()),
606            _ => Err(()),
607        }
608    }
609}
610
611/// Update an array entry and return the updated array.
612#[pd_host_function(name = "set")]
613pub(super) fn builtin_set_array_impl(
614    mut items: VmArrayHandle,
615    index: i64,
616    value: VmValueOwned,
617) -> VmResult<VmArrayHandle> {
618    let items_mut = Arc::make_mut(&mut items);
619    if index < 0 {
620        return Err(VmError::HostError(
621            "array index must be non-negative".to_string(),
622        ));
623    }
624    let index = usize::try_from(index)
625        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
626    if index < items_mut.len() {
627        items_mut[index] = value;
628    } else if index == items_mut.len() {
629        items_mut.push(value);
630    } else {
631        return Err(VmError::HostError(format!(
632            "array index {index} out of bounds"
633        )));
634    }
635    Ok(items)
636}
637
638pub(crate) fn builtin_set_array_shared_impl(
639    mut items: SharedArray,
640    index: i64,
641    value: AnyValue,
642) -> VmResult<SharedArray> {
643    let items_mut = Arc::make_mut(&mut items);
644    if index < 0 {
645        return Err(VmError::HostError(
646            "array index must be non-negative".to_string(),
647        ));
648    }
649    let index = usize::try_from(index)
650        .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
651    if index < items_mut.len() {
652        items_mut[index] = value;
653    } else if index == items_mut.len() {
654        items_mut.push(value);
655    } else {
656        return Err(VmError::HostError(format!(
657            "array index {index} out of bounds"
658        )));
659    }
660    Ok(items)
661}
662
663/// Update a map entry and return the updated map.
664#[pd_host_function(name = "set")]
665pub(super) fn builtin_set_map_impl(
666    mut entries: VmMapHandle,
667    key: VmValueOwned,
668    value: VmValueOwned,
669) -> VmMapHandle {
670    let entries_mut = Arc::make_mut(&mut entries);
671    if matches!(value, Value::Null) {
672        entries_mut.remove(&key);
673    } else {
674        entries_mut.insert(key, value);
675    }
676    entries
677}
678
679pub(crate) fn builtin_set_map_shared_impl(
680    mut entries: SharedMap,
681    key: AnyValue,
682    value: AnyValue,
683) -> SharedMap {
684    let entries_mut = Arc::make_mut(&mut entries);
685    if matches!(value, Value::Null) {
686        entries_mut.remove(&key);
687    } else {
688        entries_mut.insert(key, value);
689    }
690    entries
691}
692
693pub(crate) fn builtin_set_owned(container: Value, key: Value, value: Value) -> VmResult<Value> {
694    match container {
695        Value::Array(values) => {
696            builtin_set_array_shared_impl(values, key.as_int()?, value).map(Value::Array)
697        }
698        Value::Map(entries) => Ok(Value::Map(builtin_set_map_shared_impl(entries, key, value))),
699        _ => Err(VmError::TypeMismatch("array/map")),
700    }
701}
702
703pub(super) fn builtin_set(args: &mut [Value]) -> VmResult<CallReturn> {
704    let container = take_arg(args, 0, "set container")?;
705    let key = take_arg(args, 1, "set key")?;
706    let value = take_arg(args, 2, "set value")?;
707    builtin_set_owned(container, key, value).map(return_one)
708}
709
710/// Return an array of container keys or indices.
711#[pd_host_function(name = "keys")]
712pub(super) fn builtin_keys_array_impl(items: VmArrayRef<'_>) -> VmArray {
713    (0..items.len())
714        .map(|index| Value::Int(index as i64))
715        .collect::<Vec<_>>()
716}
717
718/// Return an array of map keys.
719#[pd_host_function(name = "keys")]
720pub(super) fn builtin_keys_map_impl(entries: VmMapRef<'_>) -> VmArray {
721    entries
722        .iter()
723        .map(|(key, _)| key.clone())
724        .collect::<Vec<_>>()
725}
726
727pub(super) fn builtin_keys(args: &[Value]) -> VmResult<CallReturn> {
728    let container = arg::<&Value>(args, 0, "keys container")?;
729    match container {
730        Value::Array(values) => Ok(return_one(
731            (0..values.len())
732                .map(|index| Value::Int(index as i64))
733                .collect::<Vec<_>>(),
734        )),
735        Value::Map(entries) => Ok(return_one(
736            entries
737                .iter()
738                .map(|(key, _)| key.clone())
739                .collect::<Vec<_>>(),
740        )),
741        _ => Err(VmError::TypeMismatch("array/map")),
742    }
743}
744
745/// Return the number of entries in an array or map.
746#[pd_host_function(name = "count")]
747pub(super) fn builtin_count_array_impl(items: VmArrayRef<'_>) -> i64 {
748    items.len() as i64
749}
750
751/// Return the number of entries in a map.
752#[pd_host_function(name = "count")]
753pub(super) fn builtin_count_map_impl(entries: VmMapRef<'_>) -> i64 {
754    entries.len() as i64
755}
756
757pub(super) fn builtin_count(args: &[Value]) -> VmResult<CallReturn> {
758    let container = arg::<&Value>(args, 0, "count container")?;
759    match container {
760        Value::Array(values) => Ok(return_one(values.len())),
761        Value::Map(entries) => Ok(return_one(entries.len())),
762        _ => Err(VmError::TypeMismatch("array/map")),
763    }
764}
765
766/// Abort execution if the condition is false.
767#[pd_host_function(name = "assert")]
768pub(super) fn builtin_assert_impl(condition: bool) -> VmResult<()> {
769    if condition {
770        Ok(())
771    } else {
772        Err(VmError::HostError("assertion failed".to_string()))
773    }
774}
775
776/// Return true when `needle` is found within `text`.
777#[pd_host_function(name = "string_contains")]
778pub(crate) fn builtin_string_contains_impl(text: VmStringRef<'_>, needle: VmStringRef<'_>) -> bool {
779    text.contains(needle)
780}
781
782/// Replace non-overlapping literal `needle` matches in `text`.
783#[pd_host_function(name = "string_replace_literal")]
784pub(crate) fn builtin_string_replace_literal_impl(
785    text: VmStringRef<'_>,
786    needle: VmStringRef<'_>,
787    replacement: VmStringRef<'_>,
788) -> String {
789    if needle.is_empty() {
790        return text.to_string();
791    }
792    text.replace(needle, replacement)
793}
794
795/// Lower ASCII `A`-`Z` bytes in `text` while preserving UTF-8.
796#[pd_host_function(name = "string_lower_ascii")]
797pub(crate) fn builtin_string_lower_ascii_impl(text: VmStringRef<'_>) -> String {
798    let mut out = text.as_bytes().to_vec();
799    for byte in &mut out {
800        if byte.is_ascii_uppercase() {
801            *byte = byte.to_ascii_lowercase();
802        }
803    }
804    String::from_utf8(out).expect("ASCII-only byte changes preserve UTF-8")
805}
806
807/// Split `text` on non-overlapping literal `delimiter` matches.
808#[pd_host_function(name = "string_split_literal")]
809pub(crate) fn builtin_string_split_literal_impl(
810    text: VmStringRef<'_>,
811    delimiter: VmStringRef<'_>,
812) -> VmArray {
813    if delimiter.is_empty() {
814        return vec![Value::string(text.to_string())];
815    }
816    text.split(delimiter)
817        .map(|part| Value::string(part.to_string()))
818        .collect()
819}
820
821/// Initialize the hidden iterator used by borrowed map for-in loops.
822#[allow(dead_code)]
823#[pd_host_function(name = "__map_iter_init")]
824fn builtin_map_iter_init_metadata(map: VmMapRef<'_>, _slot: i64) -> VmMapHandle {
825    Arc::new(map.clone())
826}
827
828/// Advance a hidden borrowed-map iterator.
829#[allow(dead_code)]
830#[pd_host_function(name = "__map_iter_next")]
831fn builtin_map_iter_next_metadata(_slot: i64) -> bool {
832    false
833}
834
835/// Move the current iterator key into the loop binding.
836#[allow(dead_code)]
837#[pd_host_function(name = "__map_iter_take_key")]
838fn builtin_map_iter_take_key_metadata(_slot: i64) -> Value {
839    Value::Null
840}
841
842/// Move the current iterator value into the loop binding.
843#[allow(dead_code)]
844#[pd_host_function(name = "__map_iter_take_value")]
845fn builtin_map_iter_take_value_metadata(_slot: i64) -> Value {
846    Value::Null
847}
848
849/// Release a hidden borrowed-map iterator after loop exit.
850#[allow(dead_code)]
851#[pd_host_function(name = "__map_iter_close")]
852fn builtin_map_iter_close_metadata(map: VmMapRef<'_>, _slot: i64) -> VmMapHandle {
853    Arc::new(map.clone())
854}
855
856#[cfg(test)]
857mod tests {
858    use super::*;
859    use crate::builtins::{BUILTIN_CALL_BASE, BUILTIN_CALL_COUNT, BuiltinFunction};
860    use std::sync::Arc;
861
862    #[test]
863    fn map_iterator_builtins_have_unique_reserved_call_indices() {
864        let reserved = [
865            (BuiltinFunction::MapIterInit, BUILTIN_CALL_BASE - 9),
866            (BuiltinFunction::MapIterNext, BUILTIN_CALL_BASE - 10),
867            (BuiltinFunction::MapIterTakeKey, BUILTIN_CALL_BASE - 11),
868            (BuiltinFunction::MapIterTakeValue, BUILTIN_CALL_BASE - 12),
869            (BuiltinFunction::MapIterClose, BUILTIN_CALL_BASE - 13),
870        ];
871        for (builtin, index) in reserved {
872            assert_eq!(builtin.call_index(), index);
873            assert_eq!(BuiltinFunction::from_call_index(index), Some(builtin));
874        }
875        assert_eq!(BUILTIN_CALL_COUNT, 89);
876        for alias in BUILTIN_CALL_BASE + 89..=BUILTIN_CALL_BASE + 92 {
877            assert_eq!(BuiltinFunction::from_call_index(alias), None);
878        }
879    }
880
881    #[test]
882    fn array_push_detaches_shared_array_before_write() {
883        let shared = Value::array(vec![Value::Int(1)]);
884        let alias = shared.clone();
885
886        let mut args = [shared, Value::Int(2)];
887        let out = builtin_array_push(&mut args).expect("array push should work");
888        let [Value::Array(result)] = out.as_slice() else {
889            panic!("expected array result");
890        };
891        let Value::Array(alias_values) = &alias else {
892            panic!("expected array alias");
893        };
894
895        assert_eq!(alias_values.as_ref(), &vec![Value::Int(1)]);
896        assert_eq!(result.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
897        assert!(
898            !Arc::ptr_eq(alias_values, result),
899            "mutating a shared array should detach backing storage"
900        );
901    }
902
903    #[test]
904    fn set_detaches_shared_array_before_write() {
905        let shared = Value::array(vec![Value::Int(1), Value::Int(2)]);
906        let alias = shared.clone();
907
908        let mut args = [shared, Value::Int(0), Value::Int(9)];
909        let out = builtin_set(&mut args).expect("array set should work");
910        let [Value::Array(result)] = out.as_slice() else {
911            panic!("expected array result");
912        };
913        let Value::Array(alias_values) = &alias else {
914            panic!("expected array alias");
915        };
916
917        assert_eq!(alias_values.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
918        assert_eq!(result.as_ref(), &vec![Value::Int(9), Value::Int(2)]);
919        assert!(
920            !Arc::ptr_eq(alias_values, result),
921            "mutating a shared array should detach backing storage"
922        );
923    }
924
925    #[test]
926    fn set_detaches_shared_map_before_write() {
927        let shared = Value::map(vec![(Value::string("k"), Value::Int(1))]);
928        let alias = shared.clone();
929
930        let mut args = [shared, Value::string("k"), Value::Int(9)];
931        let out = builtin_set(&mut args).expect("map set should work");
932        let [Value::Map(result)] = out.as_slice() else {
933            panic!("expected map result");
934        };
935        let Value::Map(alias_entries) = &alias else {
936            panic!("expected map alias");
937        };
938
939        assert_eq!(alias_entries.len(), 1);
940        assert_eq!(alias_entries.get(&Value::string("k")), Some(&Value::Int(1)));
941        assert_eq!(result.len(), 1);
942        assert_eq!(result.get(&Value::string("k")), Some(&Value::Int(9)));
943        assert!(
944            !Arc::ptr_eq(alias_entries, result),
945            "mutating a shared map should detach backing storage"
946        );
947    }
948
949    #[test]
950    fn set_map_null_removes_entry() {
951        let shared = Value::map(vec![(Value::string("drop"), Value::Int(1))]);
952        let alias = shared.clone();
953
954        let mut args = [shared, Value::string("drop"), Value::Null];
955        let out = builtin_set(&mut args).expect("map null set should work");
956        let [Value::Map(result)] = out.as_slice() else {
957            panic!("expected map result");
958        };
959        let Value::Map(alias_entries) = &alias else {
960            panic!("expected map alias");
961        };
962
963        assert_eq!(alias_entries.len(), 1);
964        assert_eq!(
965            alias_entries.get(&Value::string("drop")),
966            Some(&Value::Int(1))
967        );
968        assert_eq!(result.len(), 0);
969        assert_eq!(result.get(&Value::string("drop")), None);
970        assert!(
971            !Arc::ptr_eq(alias_entries, result),
972            "mutating a shared map should detach backing storage"
973        );
974    }
975
976    #[test]
977    fn has_map_uses_identity_for_heap_keys() {
978        let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
979        let alias = key.clone();
980        let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
981        let map = VmMap::from(vec![(key, Value::Bool(true))]);
982
983        assert!(builtin_has_map_impl(&map, &alias));
984        assert!(!builtin_has_map_impl(&map, &structural_peer));
985    }
986
987    #[test]
988    fn has_dispatch_uses_identity_for_heap_keys() {
989        let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
990        let alias = key.clone();
991        let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
992        let map = Value::map(vec![(key, Value::Bool(true))]);
993
994        let alias_result = builtin_has(&[map.clone(), alias]).expect("builtin has should succeed");
995        let [Value::Bool(alias_present)] = alias_result.as_slice() else {
996            panic!("expected bool result");
997        };
998        assert!(*alias_present, "shared heap key should match by identity");
999
1000        let peer_result = builtin_has(&[map, structural_peer]).expect("builtin has should succeed");
1001        let [Value::Bool(peer_present)] = peer_result.as_slice() else {
1002            panic!("expected bool result");
1003        };
1004        assert!(
1005            !peer_present,
1006            "structural peer should not match a map key stored by heap identity"
1007        );
1008    }
1009
1010    #[test]
1011    fn len_and_count_dispatch_return_shared_container_sizes() {
1012        let array = Value::array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
1013        let map = Value::map(vec![
1014            (Value::string("a"), Value::Int(1)),
1015            (Value::string("b"), Value::Int(2)),
1016        ]);
1017
1018        let array_len =
1019            builtin_len(std::slice::from_ref(&array)).expect("array len should succeed");
1020        let [Value::Int(array_len)] = array_len.as_slice() else {
1021            panic!("expected int result");
1022        };
1023        assert_eq!(*array_len, 3);
1024
1025        let array_count = builtin_count(&[array]).expect("array count should succeed");
1026        let [Value::Int(array_count)] = array_count.as_slice() else {
1027            panic!("expected int result");
1028        };
1029        assert_eq!(*array_count, 3);
1030
1031        let map_len = builtin_len(std::slice::from_ref(&map)).expect("map len should succeed");
1032        let [Value::Int(map_len)] = map_len.as_slice() else {
1033            panic!("expected int result");
1034        };
1035        assert_eq!(*map_len, 2);
1036
1037        let map_count = builtin_count(&[map]).expect("map count should succeed");
1038        let [Value::Int(map_count)] = map_count.as_slice() else {
1039            panic!("expected int result");
1040        };
1041        assert_eq!(*map_count, 2);
1042    }
1043}