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#[pd_host_function(name = "len")]
14pub(super) fn builtin_len_string_impl(text: VmStringRef<'_>) -> i64 {
15 text.chars().count() as i64
16}
17
18#[pd_host_function(name = "len")]
20pub(super) fn builtin_len_array_impl(items: VmArrayRef<'_>) -> i64 {
21 items.len() as i64
22}
23
24#[pd_host_function(name = "len")]
26pub(super) fn builtin_len_bytes_impl(items: VmBytesRef<'_>) -> i64 {
27 items.len() as i64
28}
29
30#[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#[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#[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#[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#[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#[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#[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#[pd_host_function(name = "array_new")]
204pub(super) fn builtin_array_new_impl() -> VmArray {
205 Vec::new()
206}
207
208#[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#[pd_host_function(name = "map_new")]
242pub(super) fn builtin_map_new_impl() -> VmMap {
243 VmMap::new()
244}
245
246#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[pd_host_function(name = "count")]
747pub(super) fn builtin_count_array_impl(items: VmArrayRef<'_>) -> i64 {
748 items.len() as i64
749}
750
751#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}