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: string comparison for runtime-created symbols
25                s1.as_str() == s2.as_str()
26            };
27            unsafe { push(stack, Value::Bool(equal)) }
28        }
29        _ => panic!("symbol_equal: expected two symbols on stack"),
30    }
31}
32
33/// Escape a string for JSON output
34///
35/// Stack effect: ( str -- str )
36///
37/// Escapes special characters according to JSON spec:
38/// - `"` → `\"`
39/// - `\` → `\\`
40/// - newline → `\n`
41/// - carriage return → `\r`
42/// - tab → `\t`
43/// - backspace → `\b`
44/// - form feed → `\f`
45/// - Control characters (0x00-0x1F) → `\uXXXX`
46///
47/// # Safety
48/// Stack must have a String value on top
49#[unsafe(no_mangle)]
50pub unsafe extern "C" fn patch_seq_json_escape(stack: Stack) -> Stack {
51    assert!(!stack.is_null(), "json_escape: stack is empty");
52
53    let (stack, value) = unsafe { pop(stack) };
54
55    match value {
56        Value::String(s) => {
57            let input = s.as_str();
58            let mut result = String::with_capacity(input.len() + 16);
59
60            for ch in input.chars() {
61                match ch {
62                    '"' => result.push_str("\\\""),
63                    '\\' => result.push_str("\\\\"),
64                    '\n' => result.push_str("\\n"),
65                    '\r' => result.push_str("\\r"),
66                    '\t' => result.push_str("\\t"),
67                    '\x08' => result.push_str("\\b"), // backspace
68                    '\x0C' => result.push_str("\\f"), // form feed
69                    // Control characters (0x00-0x1F except those handled above)
70                    // RFC 8259 uses uppercase hex in examples for Unicode escapes
71                    c if c.is_control() => {
72                        result.push_str(&format!("\\u{:04X}", c as u32));
73                    }
74                    c => result.push(c),
75                }
76            }
77
78            unsafe { push(stack, Value::String(global_string(result))) }
79        }
80        _ => panic!("json_escape: expected String on stack"),
81    }
82}
83
84/// Convert String to Int: ( String -- Int Bool )
85/// Returns the parsed int and true on success, or 0 and false on failure.
86/// Accepts integers in range [-9223372036854775808, 9223372036854775807] (i64).
87/// Trims leading/trailing whitespace before parsing.
88/// Leading zeros are accepted (e.g., "007" parses to 7).
89///
90/// # Error Handling
91/// - Empty stack: Sets runtime error, returns unchanged stack
92/// - Type mismatch: Sets runtime error, returns 0 and false
93///
94/// # Safety
95/// Stack must have a String value on top
96#[unsafe(no_mangle)]
97pub unsafe extern "C" fn patch_seq_string_to_int(stack: Stack) -> Stack {
98    if stack.is_null() {
99        set_runtime_error("string->int: stack is empty");
100        return stack;
101    }
102    let (stack, val) = unsafe { pop(stack) };
103
104    match val {
105        Value::String(s) => match s.as_str().trim().parse::<i64>() {
106            Ok(i) => {
107                let stack = unsafe { push(stack, Value::Int(i)) };
108                unsafe { push(stack, Value::Bool(true)) }
109            }
110            Err(_) => {
111                let stack = unsafe { push(stack, Value::Int(0)) };
112                unsafe { push(stack, Value::Bool(false)) }
113            }
114        },
115        _ => {
116            set_runtime_error("string->int: expected String on stack");
117            let stack = unsafe { push(stack, Value::Int(0)) };
118            unsafe { push(stack, Value::Bool(false)) }
119        }
120    }
121}