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
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#[pd_host_function(name = "map_new")]
236pub(super) fn builtin_map_new_impl() -> VmMap {
237 VmMap::new()
238}
239
240#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[pd_host_function(name = "count")]
738pub(super) fn builtin_count_array_impl(items: VmArrayRef<'_>) -> i64 {
739 items.len() as i64
740}
741
742#[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#[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}