Skip to main content

seq_runtime/
float_ops.rs

1//! Float operations for Seq
2//!
3//! These functions are exported with C ABI for LLVM codegen to call.
4//! All float operations use the `f.` prefix to distinguish from integer operations.
5
6use crate::seqstring::global_string;
7use crate::stack::{Stack, pop, pop_two, push};
8use crate::value::Value;
9
10// =============================================================================
11// Push Float
12// =============================================================================
13
14/// Push a float value onto the stack
15///
16/// # Safety
17/// Stack pointer must be valid or null
18#[unsafe(no_mangle)]
19pub unsafe extern "C" fn patch_seq_push_float(stack: Stack, value: f64) -> Stack {
20    unsafe { push(stack, Value::Float(value)) }
21}
22
23// =============================================================================
24// Arithmetic Operations
25// =============================================================================
26
27/// Float addition: ( Float Float -- Float )
28///
29/// # Safety
30/// Stack must have two Float values on top
31#[unsafe(no_mangle)]
32pub unsafe extern "C" fn patch_seq_f_add(stack: Stack) -> Stack {
33    let (rest, a, b) = unsafe { pop_two(stack, "f.add") };
34    match (a, b) {
35        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x + y)) },
36        _ => panic!("f.add: expected two Floats on stack"),
37    }
38}
39
40/// Float subtraction: ( Float Float -- Float )
41///
42/// # Safety
43/// Stack must have two Float values on top
44#[unsafe(no_mangle)]
45pub unsafe extern "C" fn patch_seq_f_subtract(stack: Stack) -> Stack {
46    let (rest, a, b) = unsafe { pop_two(stack, "f.subtract") };
47    match (a, b) {
48        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x - y)) },
49        _ => panic!("f.subtract: expected two Floats on stack"),
50    }
51}
52
53/// Float multiplication: ( Float Float -- Float )
54///
55/// # Safety
56/// Stack must have two Float values on top
57#[unsafe(no_mangle)]
58pub unsafe extern "C" fn patch_seq_f_multiply(stack: Stack) -> Stack {
59    let (rest, a, b) = unsafe { pop_two(stack, "f.multiply") };
60    match (a, b) {
61        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x * y)) },
62        _ => panic!("f.multiply: expected two Floats on stack"),
63    }
64}
65
66/// Float division: ( Float Float -- Float )
67///
68/// Division by zero returns infinity (IEEE 754 behavior)
69///
70/// # Safety
71/// Stack must have two Float values on top
72#[unsafe(no_mangle)]
73pub unsafe extern "C" fn patch_seq_f_divide(stack: Stack) -> Stack {
74    let (rest, a, b) = unsafe { pop_two(stack, "f.divide") };
75    match (a, b) {
76        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Float(x / y)) },
77        _ => panic!("f.divide: expected two Floats on stack"),
78    }
79}
80
81// =============================================================================
82// Comparison Operations (return Bool)
83// =============================================================================
84
85/// Float equality: ( Float Float -- Bool )
86///
87/// **Warning:** Direct float equality can be surprising due to IEEE 754
88/// rounding. For example, `0.1 0.2 f.add 0.3 f.=` may return false.
89/// Consider using epsilon-based comparison for tolerances.
90///
91/// # Safety
92/// Stack must have two Float values on top
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn patch_seq_f_eq(stack: Stack) -> Stack {
95    let (rest, a, b) = unsafe { pop_two(stack, "f.=") };
96    match (a, b) {
97        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x == y)) },
98        _ => panic!("f.=: expected two Floats on stack"),
99    }
100}
101
102/// Float less than: ( Float Float -- Bool )
103///
104/// # Safety
105/// Stack must have two Float values on top
106#[unsafe(no_mangle)]
107pub unsafe extern "C" fn patch_seq_f_lt(stack: Stack) -> Stack {
108    let (rest, a, b) = unsafe { pop_two(stack, "f.<") };
109    match (a, b) {
110        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x < y)) },
111        _ => panic!("f.<: expected two Floats on stack"),
112    }
113}
114
115/// Float greater than: ( Float Float -- Bool )
116///
117/// # Safety
118/// Stack must have two Float values on top
119#[unsafe(no_mangle)]
120pub unsafe extern "C" fn patch_seq_f_gt(stack: Stack) -> Stack {
121    let (rest, a, b) = unsafe { pop_two(stack, "f.>") };
122    match (a, b) {
123        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x > y)) },
124        _ => panic!("f.>: expected two Floats on stack"),
125    }
126}
127
128/// Float less than or equal: ( Float Float -- Bool )
129///
130/// # Safety
131/// Stack must have two Float values on top
132#[unsafe(no_mangle)]
133pub unsafe extern "C" fn patch_seq_f_lte(stack: Stack) -> Stack {
134    let (rest, a, b) = unsafe { pop_two(stack, "f.<=") };
135    match (a, b) {
136        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x <= y)) },
137        _ => panic!("f.<=: expected two Floats on stack"),
138    }
139}
140
141/// Float greater than or equal: ( Float Float -- Bool )
142///
143/// # Safety
144/// Stack must have two Float values on top
145#[unsafe(no_mangle)]
146pub unsafe extern "C" fn patch_seq_f_gte(stack: Stack) -> Stack {
147    let (rest, a, b) = unsafe { pop_two(stack, "f.>=") };
148    match (a, b) {
149        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x >= y)) },
150        _ => panic!("f.>=: expected two Floats on stack"),
151    }
152}
153
154/// Float not equal: ( Float Float -- Bool )
155///
156/// # Safety
157/// Stack must have two Float values on top
158#[unsafe(no_mangle)]
159pub unsafe extern "C" fn patch_seq_f_neq(stack: Stack) -> Stack {
160    let (rest, a, b) = unsafe { pop_two(stack, "f.<>") };
161    match (a, b) {
162        (Value::Float(x), Value::Float(y)) => unsafe { push(rest, Value::Bool(x != y)) },
163        _ => panic!("f.<>: expected two Floats on stack"),
164    }
165}
166
167// =============================================================================
168// Type Conversions
169// =============================================================================
170
171/// Convert Int to Float: ( Int -- Float )
172///
173/// # Safety
174/// Stack must have an Int value on top
175#[unsafe(no_mangle)]
176pub unsafe extern "C" fn patch_seq_int_to_float(stack: Stack) -> Stack {
177    assert!(!stack.is_null(), "int->float: stack is empty");
178    let (stack, val) = unsafe { pop(stack) };
179
180    match val {
181        Value::Int(i) => unsafe { push(stack, Value::Float(i as f64)) },
182        _ => panic!("int->float: expected Int on stack"),
183    }
184}
185
186/// Convert Float to Int: ( Float -- Int )
187///
188/// Truncates toward zero. Values outside 63-bit signed range are clamped:
189/// - Values >= 2^62-1 become 2^62-1 (4611686018427387903)
190/// - Values <= -(2^62) become -(2^62) (-4611686018427387904)
191/// - NaN becomes 0
192///
193/// # Safety
194/// Stack must have a Float value on top
195#[unsafe(no_mangle)]
196pub unsafe extern "C" fn patch_seq_float_to_int(stack: Stack) -> Stack {
197    assert!(!stack.is_null(), "float->int: stack is empty");
198    let (stack, val) = unsafe { pop(stack) };
199
200    match val {
201        Value::Float(f) => {
202            // Clamp to i64 range to avoid undefined behavior
203            // 63-bit signed integer range: -(2^62) to (2^62 - 1)
204            const INT63_MAX: i64 = (1i64 << 62) - 1;
205            const INT63_MIN: i64 = -(1i64 << 62);
206            let i = if f.is_nan() {
207                0
208            } else if f >= INT63_MAX as f64 {
209                INT63_MAX
210            } else if f <= INT63_MIN as f64 {
211                INT63_MIN
212            } else {
213                f as i64
214            };
215            unsafe { push(stack, Value::Int(i)) }
216        }
217        _ => panic!("float->int: expected Float on stack"),
218    }
219}
220
221/// Convert Float to String: ( Float -- String )
222///
223/// # Safety
224/// Stack must have a Float value on top
225#[unsafe(no_mangle)]
226pub unsafe extern "C" fn patch_seq_float_to_string(stack: Stack) -> Stack {
227    assert!(!stack.is_null(), "float->string: stack is empty");
228    let (stack, val) = unsafe { pop(stack) };
229
230    match val {
231        Value::Float(f) => {
232            let s = f.to_string();
233            unsafe { push(stack, Value::String(global_string(s))) }
234        }
235        _ => panic!("float->string: expected Float on stack"),
236    }
237}
238
239/// Convert String to Float: ( String -- Float Int )
240/// Returns the parsed float and 1 on success, or 0.0 and 0 on failure
241///
242/// # Safety
243/// Stack must have a String value on top
244#[unsafe(no_mangle)]
245pub unsafe extern "C" fn patch_seq_string_to_float(stack: Stack) -> Stack {
246    assert!(!stack.is_null(), "string->float: stack is empty");
247    let (stack, val) = unsafe { pop(stack) };
248
249    match val {
250        Value::String(s) => match s.as_str().parse::<f64>() {
251            Ok(f) => {
252                let stack = unsafe { push(stack, Value::Float(f)) };
253                unsafe { push(stack, Value::Bool(true)) }
254            }
255            Err(_) => {
256                let stack = unsafe { push(stack, Value::Float(0.0)) };
257                unsafe { push(stack, Value::Bool(false)) }
258            }
259        },
260        _ => panic!("string->float: expected String on stack"),
261    }
262}
263
264// =============================================================================
265// Public re-exports with short names
266// =============================================================================
267
268pub use patch_seq_f_add as f_add;
269pub use patch_seq_f_divide as f_divide;
270pub use patch_seq_f_eq as f_eq;
271pub use patch_seq_f_gt as f_gt;
272pub use patch_seq_f_gte as f_gte;
273pub use patch_seq_f_lt as f_lt;
274pub use patch_seq_f_lte as f_lte;
275pub use patch_seq_f_multiply as f_multiply;
276pub use patch_seq_f_neq as f_neq;
277pub use patch_seq_f_subtract as f_subtract;
278pub use patch_seq_float_to_int as float_to_int;
279pub use patch_seq_float_to_string as float_to_string;
280pub use patch_seq_int_to_float as int_to_float;
281pub use patch_seq_push_float as push_float;
282pub use patch_seq_string_to_float as string_to_float;
283
284// =============================================================================
285// Tests
286// =============================================================================
287
288#[cfg(test)]
289mod tests;