Skip to main content

seq_runtime/string_ops/
conversion.rs

1//! Conversions and escape/compare helpers that aren't plain string-to-string.
2
3use crate::error::set_runtime_error;
4use crate::seqstring::global_string;
5use crate::stack::{Stack, pop, push};
6use crate::value::Value;
7
8/// # Safety
9/// Stack must have the expected values on top for this operation.
10#[unsafe(no_mangle)]
11pub unsafe extern "C" fn patch_seq_symbol_equal(stack: Stack) -> Stack {
12    assert!(!stack.is_null(), "symbol_equal: stack is empty");
13
14    let (stack, sym2_val) = unsafe { pop(stack) };
15    assert!(!stack.is_null(), "symbol_equal: need two symbols");
16    let (stack, sym1_val) = unsafe { pop(stack) };
17
18    match (sym1_val, sym2_val) {
19        (Value::Symbol(s1), Value::Symbol(s2)) => {
20            // Fast path: both interned symbols -> O(1) pointer comparison
21            let equal = if s1.is_interned() && s2.is_interned() {
22                s1.as_ptr() == s2.as_ptr()
23            } else {
24                // Fallback: byte-level comparison for runtime-created
25                // symbols. Must be `as_bytes()`, not `as_str_or_empty()` —
26                // otherwise two distinct non-UTF-8 symbols both collapse
27                // to "" and are reported equal. (Symbols are normally
28                // ASCII identifiers so this rarely matters in practice,
29                // but the contract should be byte-precise.)
30                s1.as_bytes() == s2.as_bytes()
31            };
32            unsafe { push(stack, Value::Bool(equal)) }
33        }
34        _ => panic!("symbol_equal: expected two symbols on stack"),
35    }
36}
37
38/// Escape a string for JSON output
39///
40/// Stack effect: ( str -- str )
41///
42/// Escapes special characters according to JSON spec:
43/// - `"` → `\"`
44/// - `\` → `\\`
45/// - newline → `\n`
46/// - carriage return → `\r`
47/// - tab → `\t`
48/// - backspace → `\b`
49/// - form feed → `\f`
50/// - Control characters (0x00-0x1F) → `\uXXXX`
51///
52/// # Safety
53/// Stack must have a String value on top
54#[unsafe(no_mangle)]
55pub unsafe extern "C" fn patch_seq_json_escape(stack: Stack) -> Stack {
56    assert!(!stack.is_null(), "json_escape: stack is empty");
57
58    let (stack, value) = unsafe { pop(stack) };
59
60    match value {
61        Value::String(s) => {
62            let input = s.as_str_or_empty();
63            let mut result = String::with_capacity(input.len() + 16);
64
65            for ch in input.chars() {
66                match ch {
67                    '"' => result.push_str("\\\""),
68                    '\\' => result.push_str("\\\\"),
69                    '\n' => result.push_str("\\n"),
70                    '\r' => result.push_str("\\r"),
71                    '\t' => result.push_str("\\t"),
72                    '\x08' => result.push_str("\\b"), // backspace
73                    '\x0C' => result.push_str("\\f"), // form feed
74                    // Control characters (0x00-0x1F except those handled above)
75                    // RFC 8259 uses uppercase hex in examples for Unicode escapes
76                    c if c.is_control() => {
77                        result.push_str(&format!("\\u{:04X}", c as u32));
78                    }
79                    c => result.push(c),
80                }
81            }
82
83            unsafe { push(stack, Value::String(global_string(result))) }
84        }
85        _ => panic!("json_escape: expected String on stack"),
86    }
87}
88
89/// Convert String to Int: ( String -- Int Bool )
90/// Returns the parsed int and true on success, or 0 and false on failure.
91/// Accepts integers in range [-9223372036854775808, 9223372036854775807] (i64).
92/// Trims leading/trailing whitespace before parsing.
93/// Leading zeros are accepted (e.g., "007" parses to 7).
94///
95/// # Error Handling
96/// - Empty stack: Sets runtime error, returns unchanged stack
97/// - Type mismatch: Sets runtime error, returns 0 and false
98///
99/// # Safety
100/// Stack must have a String value on top
101#[unsafe(no_mangle)]
102pub unsafe extern "C" fn patch_seq_string_to_int(stack: Stack) -> Stack {
103    if stack.is_null() {
104        set_runtime_error("string->int: stack is empty");
105        return stack;
106    }
107    let (stack, val) = unsafe { pop(stack) };
108
109    match val {
110        Value::String(s) => match s.as_str_or_empty().trim().parse::<i64>() {
111            Ok(i) => {
112                let stack = unsafe { push(stack, Value::Int(i)) };
113                unsafe { push(stack, Value::Bool(true)) }
114            }
115            Err(_) => {
116                let stack = unsafe { push(stack, Value::Int(0)) };
117                unsafe { push(stack, Value::Bool(false)) }
118            }
119        },
120        _ => {
121            set_runtime_error("string->int: expected String on stack");
122            let stack = unsafe { push(stack, Value::Int(0)) };
123            unsafe { push(stack, Value::Bool(false)) }
124        }
125    }
126}