Skip to main content

shape_jit/ffi/object/
format.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 1 site
3//     jit_box(HK_STRING, ...) — jit_format result
4//   Category B (intermediate/consumed): 0 sites
5//   Category C (heap islands): 0 sites
6//!
7//! String Formatting Utilities
8//!
9//! Functions for formatting strings with template substitution.
10
11use super::super::super::context::JITContext;
12use crate::ffi::jit_kinds::*;
13use crate::ffi::value_ffi::*;
14
15// ============================================================================
16// String Formatting
17// ============================================================================
18
19/// Format a string with arguments
20#[inline(always)]
21pub extern "C" fn jit_format(ctx: *mut JITContext, _arg_count: usize) -> u64 {
22    unsafe {
23        if ctx.is_null() {
24            return TAG_NULL;
25        }
26
27        let ctx_ref = &mut *ctx;
28
29        // Pop arg_count value from stack first
30        if ctx_ref.stack_ptr == 0 {
31            return TAG_NULL;
32        }
33        ctx_ref.stack_ptr -= 1;
34        let arg_count_val = ctx_ref.stack[ctx_ref.stack_ptr];
35        let arg_count = if is_number(arg_count_val) {
36            unbox_number(arg_count_val) as usize
37        } else {
38            return TAG_NULL;
39        };
40
41        if arg_count == 0 {
42            return TAG_NULL;
43        }
44
45        // Pop all arguments from stack
46        let mut args = Vec::with_capacity(arg_count);
47        for _ in 0..arg_count {
48            if ctx_ref.stack_ptr == 0 {
49                return TAG_NULL;
50            }
51            ctx_ref.stack_ptr -= 1;
52            args.push(ctx_ref.stack[ctx_ref.stack_ptr]);
53        }
54        args.reverse(); // Restore original order
55
56        // First arg is the format string
57        let template_bits = args[0];
58        if !is_heap_kind(template_bits, HK_STRING) {
59            return TAG_NULL;
60        }
61        let template = unbox_string(template_bits).to_string();
62
63        // Substitute placeholders
64        let mut result = template;
65        let format_args = &args[1..];
66
67        // Handle {} placeholders (positional)
68        let mut arg_idx = 0;
69        while let Some(pos) = result.find("{}") {
70            if arg_idx < format_args.len() {
71                let replacement = value_to_string(format_args[arg_idx]);
72                result = format!("{}{}{}", &result[..pos], replacement, &result[pos + 2..]);
73                arg_idx += 1;
74            } else {
75                break;
76            }
77        }
78
79        // Handle {0}, {1}, etc. (indexed placeholders)
80        for i in 0..format_args.len() {
81            let placeholder = format!("{{{}}}", i);
82            if result.contains(&placeholder) {
83                let replacement = value_to_string(format_args[i]);
84                result = result.replace(&placeholder, &replacement);
85            }
86        }
87
88        box_string(result)
89    }
90}
91
92/// Helper to convert a value to string for format
93pub(crate) fn value_to_string(bits: u64) -> String {
94    if is_number(bits) {
95        let n = unbox_number(bits);
96        if n.fract() == 0.0 && n.abs() < 1e15 {
97            format!("{}", n as i64)
98        } else {
99            format!("{}", n)
100        }
101    } else if bits == TAG_NULL {
102        "null".to_string()
103    } else if bits == TAG_BOOL_TRUE {
104        "true".to_string()
105    } else if bits == TAG_BOOL_FALSE {
106        "false".to_string()
107    } else {
108        match heap_kind(bits) {
109            Some(HK_STRING) => unsafe { unbox_string(bits) }.to_string(),
110            Some(HK_ARRAY) => "[array]".to_string(),
111            Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => "[object]".to_string(),
112            _ => "[unknown]".to_string(),
113        }
114    }
115}