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#[allow(dead_code)]
14#[pd_host_function(name = "__bind_callable")]
15fn builtin_bind_callable_metadata(_prototype_id: i64, captures: VmArrayRef<'_>) -> VmArray {
16 captures.to_vec()
17}
18
19#[allow(dead_code)]
21#[pd_host_function(name = "__detach_local")]
22fn builtin_detach_local_metadata(_slot: i64) {}
23
24#[pd_host_function(name = "len")]
26pub(super) fn builtin_len_string_impl(text: VmStringRef<'_>) -> i64 {
27 text.chars().count() as i64
28}
29
30#[pd_host_function(name = "len")]
32pub(super) fn builtin_len_array_impl(items: VmArrayRef<'_>) -> i64 {
33 items.len() as i64
34}
35
36#[pd_host_function(name = "len")]
38pub(super) fn builtin_len_bytes_impl(items: VmBytesRef<'_>) -> i64 {
39 items.len() as i64
40}
41
42#[pd_host_function(name = "len")]
44pub(super) fn builtin_len_map_impl(entries: VmMapRef<'_>) -> i64 {
45 entries.len() as i64
46}
47
48pub(super) fn builtin_len(args: &[Value]) -> VmResult<CallReturn> {
49 let value = arg::<&Value>(args, 0, "len value")?;
50 match value {
51 Value::String(text) => Ok(return_one(builtin_len_string_impl(text.as_str()))),
52 Value::Bytes(values) => Ok(return_one(values.len())),
53 Value::Array(values) => Ok(return_one(values.len())),
54 Value::Map(entries) => Ok(return_one(entries.len())),
55 _ => Err(VmError::TypeMismatch("string/bytes/array/map")),
56 }
57}
58
59fn slice_bounds(start: i64, length: i64) -> VmResult<Option<(usize, usize)>> {
60 if start < 0 || length <= 0 {
61 return Ok(None);
62 }
63 let start = usize::try_from(start).map_err(|_| {
64 VmError::HostError("slice start overflow while converting to usize".to_string())
65 })?;
66 let length = usize::try_from(length).map_err(|_| {
67 VmError::HostError("slice length overflow while converting to usize".to_string())
68 })?;
69 Ok(Some((start, length)))
70}
71
72#[pd_host_function(name = "slice")]
74pub(super) fn builtin_slice_string_impl(
75 text: VmStringRef<'_>,
76 start: i64,
77 length: i64,
78) -> VmResult<String> {
79 let Some((start, length)) = slice_bounds(start, length)? else {
80 return Ok(String::new());
81 };
82 Ok(text.chars().skip(start).take(length).collect::<String>())
83}
84
85#[pd_host_function(name = "slice")]
87pub(super) fn builtin_slice_array_impl(
88 items: VmArrayRef<'_>,
89 start: i64,
90 length: i64,
91) -> VmResult<VmArray> {
92 let Some((start, length)) = slice_bounds(start, length)? else {
93 return Ok(Vec::new());
94 };
95 Ok(items
96 .iter()
97 .skip(start)
98 .take(length)
99 .cloned()
100 .collect::<Vec<_>>())
101}
102
103#[pd_host_function(name = "slice")]
105pub(super) fn builtin_slice_bytes_impl(
106 items: VmBytesRef<'_>,
107 start: i64,
108 length: i64,
109) -> VmResult<VmBytes> {
110 let Some((start, length)) = slice_bounds(start, length)? else {
111 return Ok(Vec::new());
112 };
113 Ok(items
114 .iter()
115 .skip(start)
116 .take(length)
117 .copied()
118 .collect::<Vec<_>>())
119}
120
121pub(super) fn builtin_slice(args: &[Value]) -> VmResult<CallReturn> {
122 let source = arg::<&Value>(args, 0, "slice source")?;
123 let start = arg::<i64>(args, 1, "slice start")?;
124 let length = arg::<i64>(args, 2, "slice length")?;
125 match source {
126 Value::String(text) => {
127 builtin_slice_string_impl(text.as_str(), start, length).map(return_one)
128 }
129 Value::Array(values) => {
130 let Some((start, length)) = slice_bounds(start, length)? else {
131 return Ok(return_one(Vec::<Value>::new()));
132 };
133 Ok(return_one(
134 values
135 .iter()
136 .skip(start)
137 .take(length)
138 .cloned()
139 .collect::<Vec<_>>(),
140 ))
141 }
142 Value::Bytes(values) => {
143 let Some((start, length)) = slice_bounds(start, length)? else {
144 return Ok(return_one(Vec::<u8>::new()));
145 };
146 Ok(return_one(
147 values
148 .iter()
149 .skip(start)
150 .take(length)
151 .copied()
152 .collect::<Vec<_>>(),
153 ))
154 }
155 _ => Err(VmError::TypeMismatch("string/bytes/array")),
156 }
157}
158
159#[pd_host_function(name = "concat")]
161pub(super) fn builtin_concat_string_impl(left: VmStringRef<'_>, right: VmStringRef<'_>) -> String {
162 let mut out = String::with_capacity(left.len() + right.len());
163 out.push_str(left);
164 out.push_str(right);
165 out
166}
167
168#[pd_host_function(name = "concat")]
170pub(super) fn builtin_concat_array_impl(
171 mut left: VmArrayHandle,
172 right: VmArrayHandle,
173) -> VmArrayHandle {
174 Arc::make_mut(&mut left).extend(right.iter().cloned());
175 left
176}
177
178#[pd_host_function(name = "concat")]
180pub(super) fn builtin_concat_bytes_impl(
181 mut left: VmBytesHandle,
182 right: VmBytesHandle,
183) -> VmBytesHandle {
184 Arc::make_mut(&mut left).extend(right.iter().copied());
185 left
186}
187
188pub(super) fn builtin_concat(args: &[Value]) -> VmResult<CallReturn> {
189 let left = arg::<&Value>(args, 0, "concat left")?;
190 let right = arg::<&Value>(args, 1, "concat right")?;
191 match (left, right) {
192 (Value::String(left), Value::String(right)) => Ok(return_one(builtin_concat_string_impl(
193 left.as_str(),
194 right.as_str(),
195 ))),
196 (Value::Array(left), Value::Array(right)) => {
197 let mut values = Vec::with_capacity(left.len() + right.len());
198 values.extend(left.iter().cloned());
199 values.extend(right.iter().cloned());
200 Ok(return_one(values))
201 }
202 (Value::Bytes(left), Value::Bytes(right)) => {
203 let mut values = Vec::with_capacity(left.len() + right.len());
204 values.extend(left.iter().copied());
205 values.extend(right.iter().copied());
206 Ok(return_one(values))
207 }
208 _ => Err(VmError::TypeMismatch(
209 "string/string or bytes/bytes or array/array",
210 )),
211 }
212}
213
214#[pd_host_function(name = "array_new")]
216pub(super) fn builtin_array_new_impl() -> VmArray {
217 Vec::new()
218}
219
220#[pd_host_function(name = "array_push")]
222pub(super) fn builtin_array_push_typed_impl(
223 mut items: VmArrayHandle,
224 value: VmValueOwned,
225) -> VmArrayHandle {
226 Arc::make_mut(&mut items).push(value);
227 items
228}
229
230pub(crate) fn builtin_array_push_shared_impl(
231 mut items: SharedArray,
232 value: AnyValue,
233) -> SharedArray {
234 Arc::make_mut(&mut items).push(value);
235 items
236}
237
238pub(crate) fn builtin_array_push_owned(items: Value, value: Value) -> VmResult<Value> {
239 let items = match items {
240 Value::Array(values) => values,
241 _ => return Err(VmError::TypeMismatch("array")),
242 };
243 Ok(Value::Array(builtin_array_push_shared_impl(items, value)))
244}
245
246pub(super) fn builtin_array_push(args: &mut [Value]) -> VmResult<CallReturn> {
247 let items = take_arg(args, 0, "array_push array")?;
248 let value = take_arg(args, 1, "array_push value")?;
249 builtin_array_push_owned(items, value).map(return_one)
250}
251
252#[pd_host_function(name = "map_new")]
254pub(super) fn builtin_map_new_impl() -> VmMap {
255 VmMap::new()
256}
257
258#[pd_host_function(name = "get")]
260pub(super) fn builtin_get_string_impl(text: VmStringRef<'_>, index: i64) -> VmResult<String> {
261 if index < 0 {
262 return Err(VmError::HostError(
263 "string index must be non-negative".to_string(),
264 ));
265 }
266 let index = usize::try_from(index)
267 .map_err(|_| VmError::HostError("string index overflow".to_string()))?;
268 text.chars()
269 .nth(index)
270 .map(|ch| ch.to_string())
271 .ok_or_else(|| VmError::HostError(format!("string index {index} out of bounds")))
272}
273
274#[pd_host_function(name = "get")]
276pub(super) fn builtin_get_array_impl(items: VmArrayRef<'_>, index: i64) -> VmResult<UnknownValue> {
277 if index < 0 {
278 return Err(VmError::HostError(
279 "array index must be non-negative".to_string(),
280 ));
281 }
282 let index = usize::try_from(index)
283 .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
284 items
285 .get(index)
286 .cloned()
287 .ok_or_else(|| VmError::HostError(format!("array index {index} out of bounds")))
288}
289
290#[pd_host_function(name = "get")]
292pub(super) fn builtin_get_bytes_impl(items: VmBytesRef<'_>, index: i64) -> VmResult<i64> {
293 if index < 0 {
294 return Err(VmError::HostError(
295 "bytes index must be non-negative".to_string(),
296 ));
297 }
298 let index = usize::try_from(index)
299 .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
300 items
301 .get(index)
302 .copied()
303 .map(i64::from)
304 .ok_or_else(|| VmError::HostError(format!("bytes index {index} out of bounds")))
305}
306
307#[pd_host_function(name = "get")]
309pub(super) fn builtin_get_map_impl(
310 entries: VmMapRef<'_>,
311 key: VmValueRef<'_>,
312) -> VmResult<UnknownValue> {
313 entries
314 .get(key)
315 .cloned()
316 .ok_or_else(|| VmError::HostError("map key not found".to_string()))
317}
318
319#[pd_host_function(name = "has")]
321pub(super) fn builtin_has_array_impl(items: VmArrayRef<'_>, index: i64) -> bool {
322 if index < 0 {
323 return false;
324 }
325 usize::try_from(index)
326 .ok()
327 .is_some_and(|index| index < items.len())
328}
329
330#[pd_host_function(name = "has")]
332pub(super) fn builtin_has_bytes_impl(items: VmBytesRef<'_>, index: i64) -> bool {
333 if index < 0 {
334 return false;
335 }
336 usize::try_from(index)
337 .ok()
338 .is_some_and(|index| index < items.len())
339}
340
341#[pd_host_function(name = "has")]
343pub(super) fn builtin_has_map_impl(entries: VmMapRef<'_>, key: VmValueRef<'_>) -> bool {
344 entries.get(key).is_some()
345}
346
347pub(super) fn builtin_has(args: &[Value]) -> VmResult<CallReturn> {
348 let container = arg::<&Value>(args, 0, "has container")?;
349 let key = arg::<&Value>(args, 1, "has key")?;
350 match container {
351 Value::Array(values) => {
352 let index = key.as_int()?;
353 let present = if index < 0 {
354 false
355 } else {
356 usize::try_from(index)
357 .ok()
358 .is_some_and(|index| index < values.len())
359 };
360 Ok(return_one(present))
361 }
362 Value::Bytes(values) => {
363 let index = key.as_int()?;
364 let present = if index < 0 {
365 false
366 } else {
367 usize::try_from(index)
368 .ok()
369 .is_some_and(|index| index < values.len())
370 };
371 Ok(return_one(present))
372 }
373 Value::Map(entries) => {
374 ensure_supported_map_key(key)?;
375 Ok(return_one(entries.get(key).is_some()))
376 }
377 _ => Err(VmError::TypeMismatch("bytes/array/map")),
378 }
379}
380
381pub(super) fn builtin_get(args: &[Value]) -> VmResult<CallReturn> {
382 let container = arg::<&Value>(args, 0, "get container")?;
383 let key = arg::<&Value>(args, 1, "get key")?;
384 match container {
385 Value::Array(values) => {
386 let index = key.as_int()?;
387 if index < 0 {
388 return Err(VmError::HostError(
389 "array index must be non-negative".to_string(),
390 ));
391 }
392 let index = usize::try_from(index)
393 .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
394 Ok(return_one(values.get(index).cloned().ok_or_else(|| {
395 VmError::HostError(format!("array index {index} out of bounds"))
396 })?))
397 }
398 Value::Map(entries) => {
399 ensure_supported_map_key(key)?;
400 Ok(return_one(entries.get(key).cloned().ok_or_else(|| {
401 VmError::HostError("map key not found".to_string())
402 })?))
403 }
404 Value::Bytes(values) => {
405 let index = key.as_int()?;
406 if index < 0 {
407 return Err(VmError::HostError(
408 "bytes index must be non-negative".to_string(),
409 ));
410 }
411 let index = usize::try_from(index)
412 .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
413 Ok(return_one(i64::from(
414 values.get(index).copied().ok_or_else(|| {
415 VmError::HostError(format!("bytes index {index} out of bounds"))
416 })?,
417 )))
418 }
419 Value::String(text) => {
420 builtin_get_string_impl(text.as_str(), key.as_int()?).map(return_one)
421 }
422 _ => Err(VmError::TypeMismatch("bytes/array/map/string")),
423 }
424}
425
426#[pd_host_function(name = "type")]
428pub(super) fn builtin_type_of_impl(value: VmValueRef<'_>) -> String {
429 match value {
430 Value::Null => "null",
431 Value::Int(_) => "int",
432 Value::Float(_) => "float",
433 Value::Bool(_) => "bool",
434 Value::String(_) => "string",
435 Value::Bytes(_) => "bytes",
436 Value::Array(_) => "array",
437 Value::Map(_) => "map",
438 Value::Callable(_) => "callable",
439 }
440 .to_string()
441}
442
443#[pd_host_function(name = "__to_string")]
445pub(crate) fn builtin_to_string_impl(value: VmValueRef<'_>) -> String {
446 render_value_for_display(value)
447}
448
449#[pd_host_function(name = "__format_template")]
451pub(super) fn builtin_format_template_impl(
452 template: &str,
453 values: VmArrayRef<'_>,
454) -> VmResult<String> {
455 ParsedFormat::parse(template, values, &NoNamedArguments)
456 .map(|parsed| parsed.to_string())
457 .map_err(|offset| {
458 VmError::HostError(format!(
459 "format string and arguments are incompatible at byte {offset}: {template}"
460 ))
461 })
462}
463
464fn render_value_for_display(value: &Value) -> String {
465 match value {
466 Value::Null => "null".to_string(),
467 Value::Int(v) => v.to_string(),
468 Value::Float(v) => v.to_string(),
469 Value::Bool(v) => v.to_string(),
470 Value::String(v) => v.as_str().to_string(),
471 Value::Bytes(v) => render_bytes_for_display(v.as_ref()),
472 Value::Array(values) => {
473 let parts = values
474 .iter()
475 .map(render_value_for_display)
476 .collect::<Vec<_>>()
477 .join(", ");
478 format!("[{parts}]")
479 }
480 Value::Map(entries) => {
481 let parts = entries
482 .iter()
483 .map(|(key, value)| {
484 format!(
485 "{}: {}",
486 render_value_for_display(key),
487 render_value_for_display(value)
488 )
489 })
490 .collect::<Vec<_>>()
491 .join(", ");
492 format!("{{{parts}}}")
493 }
494 Value::Callable(callable) => match callable.kind {
495 crate::CallableKind::FunctionItem => format!("<fn#{}>", callable.prototype_id),
496 crate::CallableKind::Closure => format!("<closure#{}>", callable.prototype_id),
497 crate::CallableKind::HostFunction => format!("<host-fn#{}>", callable.prototype_id),
498 },
499 }
500}
501
502fn render_bytes_for_display(bytes: &[u8]) -> String {
503 let preview_len = bytes.len().min(16);
504 let mut preview = String::with_capacity(preview_len * 2);
505 for byte in &bytes[..preview_len] {
506 preview.push(hex_nibble(byte >> 4));
507 preview.push(hex_nibble(byte & 0x0F));
508 }
509 if bytes.len() > preview_len {
510 format!("bytes[len={} hex={}..]", bytes.len(), preview)
511 } else {
512 format!("bytes[len={} hex={}]", bytes.len(), preview)
513 }
514}
515
516fn hex_nibble(value: u8) -> char {
517 match value {
518 0..=9 => char::from(b'0' + value),
519 10..=15 => char::from(b'a' + (value - 10)),
520 _ => unreachable!("hex nibble out of range"),
521 }
522}
523
524impl FormatArgument for Value {
525 fn supports_format(&self, specifier: &Specifier) -> bool {
526 match self {
527 Value::Null => matches!(specifier.format, Format::Display | Format::Debug),
528 Value::Int(_) => true,
529 Value::Float(_) => matches!(
530 specifier.format,
531 Format::Display | Format::Debug | Format::LowerExp | Format::UpperExp
532 ),
533 Value::Bool(_) => matches!(specifier.format, Format::Display | Format::Debug),
534 Value::String(_) | Value::Bytes(_) => {
535 matches!(specifier.format, Format::Display | Format::Debug)
536 }
537 Value::Array(_) | Value::Map(_) | Value::Callable(_) => {
538 matches!(specifier.format, Format::Display | Format::Debug)
539 }
540 }
541 }
542
543 fn fmt_display(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
544 match self {
545 Value::Null => f.write_str("null"),
546 Value::Int(value) => std::fmt::Display::fmt(value, f),
547 Value::Float(value) => std::fmt::Display::fmt(value, f),
548 Value::Bool(value) => std::fmt::Display::fmt(value, f),
549 Value::String(value) => std::fmt::Display::fmt(value.as_str(), f),
550 Value::Bytes(_) | Value::Array(_) | Value::Map(_) | Value::Callable(_) => {
551 f.write_str(render_value_for_display(self).as_str())
552 }
553 }
554 }
555
556 fn fmt_debug(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
557 match self {
558 Value::Null => f.write_str("null"),
559 Value::Int(value) => std::fmt::Debug::fmt(value, f),
560 Value::Float(value) => std::fmt::Debug::fmt(value, f),
561 Value::Bool(value) => std::fmt::Debug::fmt(value, f),
562 Value::String(value) => std::fmt::Debug::fmt(value.as_str(), f),
563 Value::Bytes(_) => f.write_str(render_value_for_display(self).as_str()),
564 Value::Array(values) => {
565 let mut list = f.debug_list();
566 for value in values.iter() {
567 list.entry(value);
568 }
569 list.finish()
570 }
571 Value::Map(entries) => {
572 let mut map = f.debug_map();
573 for (key, value) in entries.iter() {
574 map.entry(key, value);
575 }
576 map.finish()
577 }
578 Value::Callable(_) => f.write_str(render_value_for_display(self).as_str()),
579 }
580 }
581
582 fn fmt_octal(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
583 match self {
584 Value::Int(value) => std::fmt::Octal::fmt(value, f),
585 _ => Err(std::fmt::Error),
586 }
587 }
588
589 fn fmt_lower_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
590 match self {
591 Value::Int(value) => std::fmt::LowerHex::fmt(value, f),
592 _ => Err(std::fmt::Error),
593 }
594 }
595
596 fn fmt_upper_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
597 match self {
598 Value::Int(value) => std::fmt::UpperHex::fmt(value, f),
599 _ => Err(std::fmt::Error),
600 }
601 }
602
603 fn fmt_binary(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
604 match self {
605 Value::Int(value) => std::fmt::Binary::fmt(value, f),
606 _ => Err(std::fmt::Error),
607 }
608 }
609
610 fn fmt_lower_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
611 match self {
612 Value::Int(value) => std::fmt::LowerExp::fmt(value, f),
613 Value::Float(value) => std::fmt::LowerExp::fmt(value, f),
614 _ => Err(std::fmt::Error),
615 }
616 }
617
618 fn fmt_upper_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
619 match self {
620 Value::Int(value) => std::fmt::UpperExp::fmt(value, f),
621 Value::Float(value) => std::fmt::UpperExp::fmt(value, f),
622 _ => Err(std::fmt::Error),
623 }
624 }
625
626 fn to_usize(&self) -> Result<usize, ()> {
627 match self {
628 Value::Int(value) => usize::try_from(*value).map_err(|_| ()),
629 _ => Err(()),
630 }
631 }
632}
633
634#[pd_host_function(name = "set")]
636pub(super) fn builtin_set_array_impl(
637 mut items: VmArrayHandle,
638 index: i64,
639 value: VmValueOwned,
640) -> VmResult<VmArrayHandle> {
641 let items_mut = Arc::make_mut(&mut items);
642 if index < 0 {
643 return Err(VmError::HostError(
644 "array index must be non-negative".to_string(),
645 ));
646 }
647 let index = usize::try_from(index)
648 .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
649 if index < items_mut.len() {
650 items_mut[index] = value;
651 } else if index == items_mut.len() {
652 items_mut.push(value);
653 } else {
654 return Err(VmError::HostError(format!(
655 "array index {index} out of bounds"
656 )));
657 }
658 Ok(items)
659}
660
661pub(crate) fn builtin_set_array_shared_impl(
662 mut items: SharedArray,
663 index: i64,
664 value: AnyValue,
665) -> VmResult<SharedArray> {
666 let items_mut = Arc::make_mut(&mut items);
667 if index < 0 {
668 return Err(VmError::HostError(
669 "array index must be non-negative".to_string(),
670 ));
671 }
672 let index = usize::try_from(index)
673 .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
674 if index < items_mut.len() {
675 items_mut[index] = value;
676 } else if index == items_mut.len() {
677 items_mut.push(value);
678 } else {
679 return Err(VmError::HostError(format!(
680 "array index {index} out of bounds"
681 )));
682 }
683 Ok(items)
684}
685
686#[pd_host_function(name = "set")]
688pub(super) fn builtin_set_map_impl(
689 mut entries: VmMapHandle,
690 key: VmValueOwned,
691 value: VmValueOwned,
692) -> VmMapHandle {
693 let entries_mut = Arc::make_mut(&mut entries);
694 if matches!(value, Value::Null) {
695 entries_mut.remove(&key);
696 } else {
697 entries_mut.insert(key, value);
698 }
699 entries
700}
701
702pub(crate) fn builtin_set_map_shared_impl(
703 mut entries: SharedMap,
704 key: AnyValue,
705 value: AnyValue,
706) -> SharedMap {
707 let entries_mut = Arc::make_mut(&mut entries);
708 if matches!(value, Value::Null) {
709 entries_mut.remove(&key);
710 } else {
711 entries_mut.insert(key, value);
712 }
713 entries
714}
715
716pub(crate) fn ensure_supported_map_key(key: &Value) -> VmResult<()> {
717 if matches!(key, Value::Callable(_)) {
718 return Err(VmError::HostError(
719 "callable values are not supported as map keys".to_string(),
720 ));
721 }
722 Ok(())
723}
724
725pub(crate) fn builtin_set_owned(container: Value, key: Value, value: Value) -> VmResult<Value> {
726 match container {
727 Value::Array(values) => {
728 builtin_set_array_shared_impl(values, key.as_int()?, value).map(Value::Array)
729 }
730 Value::Map(entries) => {
731 ensure_supported_map_key(&key)?;
732 Ok(Value::Map(builtin_set_map_shared_impl(entries, key, value)))
733 }
734 _ => Err(VmError::TypeMismatch("array/map")),
735 }
736}
737
738pub(super) fn builtin_set(args: &mut [Value]) -> VmResult<CallReturn> {
739 let container = take_arg(args, 0, "set container")?;
740 let key = take_arg(args, 1, "set key")?;
741 let value = take_arg(args, 2, "set value")?;
742 builtin_set_owned(container, key, value).map(return_one)
743}
744
745#[pd_host_function(name = "keys")]
747pub(super) fn builtin_keys_array_impl(items: VmArrayRef<'_>) -> VmArray {
748 (0..items.len())
749 .map(|index| Value::Int(index as i64))
750 .collect::<Vec<_>>()
751}
752
753#[pd_host_function(name = "keys")]
755pub(super) fn builtin_keys_map_impl(entries: VmMapRef<'_>) -> VmArray {
756 entries
757 .iter()
758 .map(|(key, _)| key.clone())
759 .collect::<Vec<_>>()
760}
761
762pub(super) fn builtin_keys(args: &[Value]) -> VmResult<CallReturn> {
763 let container = arg::<&Value>(args, 0, "keys container")?;
764 match container {
765 Value::Array(values) => Ok(return_one(
766 (0..values.len())
767 .map(|index| Value::Int(index as i64))
768 .collect::<Vec<_>>(),
769 )),
770 Value::Map(entries) => Ok(return_one(
771 entries
772 .iter()
773 .map(|(key, _)| key.clone())
774 .collect::<Vec<_>>(),
775 )),
776 _ => Err(VmError::TypeMismatch("array/map")),
777 }
778}
779
780#[pd_host_function(name = "count")]
782pub(super) fn builtin_count_array_impl(items: VmArrayRef<'_>) -> i64 {
783 items.len() as i64
784}
785
786#[pd_host_function(name = "count")]
788pub(super) fn builtin_count_map_impl(entries: VmMapRef<'_>) -> i64 {
789 entries.len() as i64
790}
791
792pub(super) fn builtin_count(args: &[Value]) -> VmResult<CallReturn> {
793 let container = arg::<&Value>(args, 0, "count container")?;
794 match container {
795 Value::Array(values) => Ok(return_one(values.len())),
796 Value::Map(entries) => Ok(return_one(entries.len())),
797 _ => Err(VmError::TypeMismatch("array/map")),
798 }
799}
800
801#[pd_host_function(name = "assert")]
803pub(super) fn builtin_assert_impl(condition: bool) -> VmResult<()> {
804 if condition {
805 Ok(())
806 } else {
807 Err(VmError::HostError("assertion failed".to_string()))
808 }
809}
810
811#[pd_host_function(name = "string_contains")]
813pub(crate) fn builtin_string_contains_impl(text: VmStringRef<'_>, needle: VmStringRef<'_>) -> bool {
814 text.contains(needle)
815}
816
817#[pd_host_function(name = "string_replace_literal")]
819pub(crate) fn builtin_string_replace_literal_impl(
820 text: VmStringRef<'_>,
821 needle: VmStringRef<'_>,
822 replacement: VmStringRef<'_>,
823) -> String {
824 if needle.is_empty() {
825 return text.to_string();
826 }
827 text.replace(needle, replacement)
828}
829
830#[pd_host_function(name = "string_lower_ascii")]
832pub(crate) fn builtin_string_lower_ascii_impl(text: VmStringRef<'_>) -> String {
833 let mut out = text.as_bytes().to_vec();
834 for byte in &mut out {
835 if byte.is_ascii_uppercase() {
836 *byte = byte.to_ascii_lowercase();
837 }
838 }
839 String::from_utf8(out).expect("ASCII-only byte changes preserve UTF-8")
840}
841
842#[pd_host_function(name = "string_split_literal")]
844pub(crate) fn builtin_string_split_literal_impl(
845 text: VmStringRef<'_>,
846 delimiter: VmStringRef<'_>,
847) -> VmArray {
848 if delimiter.is_empty() {
849 return vec![Value::string(text.to_string())];
850 }
851 text.split(delimiter)
852 .map(|part| Value::string(part.to_string()))
853 .collect()
854}
855
856#[allow(dead_code)]
858#[pd_host_function(name = "__map_iter_init")]
859fn builtin_map_iter_init_metadata(map: VmMapRef<'_>, _slot: i64) -> VmMapHandle {
860 Arc::new(map.clone())
861}
862
863#[allow(dead_code)]
865#[pd_host_function(name = "__map_iter_next")]
866fn builtin_map_iter_next_metadata(_slot: i64) -> bool {
867 false
868}
869
870#[allow(dead_code)]
872#[pd_host_function(name = "__map_iter_take_key")]
873fn builtin_map_iter_take_key_metadata(_slot: i64) -> Value {
874 Value::Null
875}
876
877#[allow(dead_code)]
879#[pd_host_function(name = "__map_iter_take_value")]
880fn builtin_map_iter_take_value_metadata(_slot: i64) -> Value {
881 Value::Null
882}
883
884#[allow(dead_code)]
886#[pd_host_function(name = "__map_iter_close")]
887fn builtin_map_iter_close_metadata(map: VmMapRef<'_>, _slot: i64) -> VmMapHandle {
888 Arc::new(map.clone())
889}
890
891#[cfg(test)]
892mod tests {
893 use super::*;
894 use crate::builtins::{BUILTIN_CALL_BASE, BUILTIN_CALL_COUNT, BuiltinFunction};
895 use std::sync::Arc;
896
897 #[test]
898 fn internal_builtins_have_unique_reserved_call_indices() {
899 let reserved = [
900 (BuiltinFunction::MapIterInit, BUILTIN_CALL_BASE - 9),
901 (BuiltinFunction::MapIterNext, BUILTIN_CALL_BASE - 10),
902 (BuiltinFunction::MapIterTakeKey, BUILTIN_CALL_BASE - 11),
903 (BuiltinFunction::MapIterTakeValue, BUILTIN_CALL_BASE - 12),
904 (BuiltinFunction::MapIterClose, BUILTIN_CALL_BASE - 13),
905 (BuiltinFunction::BindCallable, BUILTIN_CALL_BASE - 14),
906 (BuiltinFunction::DetachLocal, BUILTIN_CALL_BASE - 15),
907 ];
908 for (builtin, index) in reserved {
909 assert_eq!(builtin.call_index(), index);
910 assert_eq!(BuiltinFunction::from_call_index(index), Some(builtin));
911 }
912 assert_eq!(BUILTIN_CALL_COUNT, 89);
913 for name in ["__bind_callable", "__detach_local"] {
914 assert!(
915 !crate::builtins::language_builtin_specs()
916 .iter()
917 .any(|spec| spec.name == name),
918 "internal callable metadata operation must not be language-visible: {name}"
919 );
920 }
921 for alias in BUILTIN_CALL_BASE + 89..=BUILTIN_CALL_BASE + 92 {
922 assert_eq!(BuiltinFunction::from_call_index(alias), None);
923 }
924 }
925
926 #[test]
927 fn callable_map_keys_are_rejected() {
928 let callable = Value::Callable(Arc::new(crate::CallableValue {
929 prototype_id: 0,
930 kind: crate::CallableKind::FunctionItem,
931 env: None,
932 }));
933 let err = builtin_set_owned(Value::map(Vec::new()), callable, Value::Int(1))
934 .expect_err("callable map key should fail");
935 assert!(err.to_string().contains("not supported as map keys"));
936 }
937
938 #[test]
939 fn array_push_detaches_shared_array_before_write() {
940 let shared = Value::array(vec![Value::Int(1)]);
941 let alias = shared.clone();
942
943 let mut args = [shared, Value::Int(2)];
944 let out = builtin_array_push(&mut args).expect("array push should work");
945 let [Value::Array(result)] = out.as_slice() else {
946 panic!("expected array result");
947 };
948 let Value::Array(alias_values) = &alias else {
949 panic!("expected array alias");
950 };
951
952 assert_eq!(alias_values.as_ref(), &vec![Value::Int(1)]);
953 assert_eq!(result.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
954 assert!(
955 !Arc::ptr_eq(alias_values, result),
956 "mutating a shared array should detach backing storage"
957 );
958 }
959
960 #[test]
961 fn set_detaches_shared_array_before_write() {
962 let shared = Value::array(vec![Value::Int(1), Value::Int(2)]);
963 let alias = shared.clone();
964
965 let mut args = [shared, Value::Int(0), Value::Int(9)];
966 let out = builtin_set(&mut args).expect("array set should work");
967 let [Value::Array(result)] = out.as_slice() else {
968 panic!("expected array result");
969 };
970 let Value::Array(alias_values) = &alias else {
971 panic!("expected array alias");
972 };
973
974 assert_eq!(alias_values.as_ref(), &vec![Value::Int(1), Value::Int(2)]);
975 assert_eq!(result.as_ref(), &vec![Value::Int(9), Value::Int(2)]);
976 assert!(
977 !Arc::ptr_eq(alias_values, result),
978 "mutating a shared array should detach backing storage"
979 );
980 }
981
982 #[test]
983 fn set_detaches_shared_map_before_write() {
984 let shared = Value::map(vec![(Value::string("k"), Value::Int(1))]);
985 let alias = shared.clone();
986
987 let mut args = [shared, Value::string("k"), Value::Int(9)];
988 let out = builtin_set(&mut args).expect("map set should work");
989 let [Value::Map(result)] = out.as_slice() else {
990 panic!("expected map result");
991 };
992 let Value::Map(alias_entries) = &alias else {
993 panic!("expected map alias");
994 };
995
996 assert_eq!(alias_entries.len(), 1);
997 assert_eq!(alias_entries.get(&Value::string("k")), Some(&Value::Int(1)));
998 assert_eq!(result.len(), 1);
999 assert_eq!(result.get(&Value::string("k")), Some(&Value::Int(9)));
1000 assert!(
1001 !Arc::ptr_eq(alias_entries, result),
1002 "mutating a shared map should detach backing storage"
1003 );
1004 }
1005
1006 #[test]
1007 fn set_map_null_removes_entry() {
1008 let shared = Value::map(vec![(Value::string("drop"), Value::Int(1))]);
1009 let alias = shared.clone();
1010
1011 let mut args = [shared, Value::string("drop"), Value::Null];
1012 let out = builtin_set(&mut args).expect("map null set should work");
1013 let [Value::Map(result)] = out.as_slice() else {
1014 panic!("expected map result");
1015 };
1016 let Value::Map(alias_entries) = &alias else {
1017 panic!("expected map alias");
1018 };
1019
1020 assert_eq!(alias_entries.len(), 1);
1021 assert_eq!(
1022 alias_entries.get(&Value::string("drop")),
1023 Some(&Value::Int(1))
1024 );
1025 assert_eq!(result.len(), 0);
1026 assert_eq!(result.get(&Value::string("drop")), None);
1027 assert!(
1028 !Arc::ptr_eq(alias_entries, result),
1029 "mutating a shared map should detach backing storage"
1030 );
1031 }
1032
1033 #[test]
1034 fn has_map_uses_identity_for_heap_keys() {
1035 let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
1036 let alias = key.clone();
1037 let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
1038 let map = VmMap::from(vec![(key, Value::Bool(true))]);
1039
1040 assert!(builtin_has_map_impl(&map, &alias));
1041 assert!(!builtin_has_map_impl(&map, &structural_peer));
1042 }
1043
1044 #[test]
1045 fn has_dispatch_uses_identity_for_heap_keys() {
1046 let key = Value::array(vec![Value::Int(1), Value::Int(2)]);
1047 let alias = key.clone();
1048 let structural_peer = Value::array(vec![Value::Int(1), Value::Int(2)]);
1049 let map = Value::map(vec![(key, Value::Bool(true))]);
1050
1051 let alias_result = builtin_has(&[map.clone(), alias]).expect("builtin has should succeed");
1052 let [Value::Bool(alias_present)] = alias_result.as_slice() else {
1053 panic!("expected bool result");
1054 };
1055 assert!(*alias_present, "shared heap key should match by identity");
1056
1057 let peer_result = builtin_has(&[map, structural_peer]).expect("builtin has should succeed");
1058 let [Value::Bool(peer_present)] = peer_result.as_slice() else {
1059 panic!("expected bool result");
1060 };
1061 assert!(
1062 !peer_present,
1063 "structural peer should not match a map key stored by heap identity"
1064 );
1065 }
1066
1067 #[test]
1068 fn len_and_count_dispatch_return_shared_container_sizes() {
1069 let array = Value::array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
1070 let map = Value::map(vec![
1071 (Value::string("a"), Value::Int(1)),
1072 (Value::string("b"), Value::Int(2)),
1073 ]);
1074
1075 let array_len =
1076 builtin_len(std::slice::from_ref(&array)).expect("array len should succeed");
1077 let [Value::Int(array_len)] = array_len.as_slice() else {
1078 panic!("expected int result");
1079 };
1080 assert_eq!(*array_len, 3);
1081
1082 let array_count = builtin_count(&[array]).expect("array count should succeed");
1083 let [Value::Int(array_count)] = array_count.as_slice() else {
1084 panic!("expected int result");
1085 };
1086 assert_eq!(*array_count, 3);
1087
1088 let map_len = builtin_len(std::slice::from_ref(&map)).expect("map len should succeed");
1089 let [Value::Int(map_len)] = map_len.as_slice() else {
1090 panic!("expected int result");
1091 };
1092 assert_eq!(*map_len, 2);
1093
1094 let map_count = builtin_count(&[map]).expect("map count should succeed");
1095 let [Value::Int(map_count)] = map_count.as_slice() else {
1096 panic!("expected int result");
1097 };
1098 assert_eq!(*map_count, 2);
1099 }
1100}