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 Int 0 or 1)
83// =============================================================================
84//
85// Note: Float comparisons return Int (0/1) for Forth-style boolean semantics,
86// while integer comparisons in arithmetic.rs return Bool. Both work with
87// conditionals and test.assert since they accept both Int and Bool.
88
89/// Float equality: ( Float Float -- Int )
90///
91/// **Warning:** Direct float equality can be surprising due to IEEE 754
92/// rounding. For example, `0.1 0.2 f.add 0.3 f.=` may return 0.
93/// Consider using epsilon-based comparison for tolerances.
94///
95/// # Safety
96/// Stack must have two Float values on top
97#[unsafe(no_mangle)]
98pub unsafe extern "C" fn patch_seq_f_eq(stack: Stack) -> Stack {
99    let (rest, a, b) = unsafe { pop_two(stack, "f.=") };
100    match (a, b) {
101        (Value::Float(x), Value::Float(y)) => unsafe {
102            push(rest, Value::Int(if x == y { 1 } else { 0 }))
103        },
104        _ => panic!("f.=: expected two Floats on stack"),
105    }
106}
107
108/// Float less than: ( Float Float -- Int )
109///
110/// # Safety
111/// Stack must have two Float values on top
112#[unsafe(no_mangle)]
113pub unsafe extern "C" fn patch_seq_f_lt(stack: Stack) -> Stack {
114    let (rest, a, b) = unsafe { pop_two(stack, "f.<") };
115    match (a, b) {
116        (Value::Float(x), Value::Float(y)) => unsafe {
117            push(rest, Value::Int(if x < y { 1 } else { 0 }))
118        },
119        _ => panic!("f.<: expected two Floats on stack"),
120    }
121}
122
123/// Float greater than: ( Float Float -- Int )
124///
125/// # Safety
126/// Stack must have two Float values on top
127#[unsafe(no_mangle)]
128pub unsafe extern "C" fn patch_seq_f_gt(stack: Stack) -> Stack {
129    let (rest, a, b) = unsafe { pop_two(stack, "f.>") };
130    match (a, b) {
131        (Value::Float(x), Value::Float(y)) => unsafe {
132            push(rest, Value::Int(if x > y { 1 } else { 0 }))
133        },
134        _ => panic!("f.>: expected two Floats on stack"),
135    }
136}
137
138/// Float less than or equal: ( Float Float -- Int )
139///
140/// # Safety
141/// Stack must have two Float values on top
142#[unsafe(no_mangle)]
143pub unsafe extern "C" fn patch_seq_f_lte(stack: Stack) -> Stack {
144    let (rest, a, b) = unsafe { pop_two(stack, "f.<=") };
145    match (a, b) {
146        (Value::Float(x), Value::Float(y)) => unsafe {
147            push(rest, Value::Int(if x <= y { 1 } else { 0 }))
148        },
149        _ => panic!("f.<=: expected two Floats on stack"),
150    }
151}
152
153/// Float greater than or equal: ( Float Float -- Int )
154///
155/// # Safety
156/// Stack must have two Float values on top
157#[unsafe(no_mangle)]
158pub unsafe extern "C" fn patch_seq_f_gte(stack: Stack) -> Stack {
159    let (rest, a, b) = unsafe { pop_two(stack, "f.>=") };
160    match (a, b) {
161        (Value::Float(x), Value::Float(y)) => unsafe {
162            push(rest, Value::Int(if x >= y { 1 } else { 0 }))
163        },
164        _ => panic!("f.>=: expected two Floats on stack"),
165    }
166}
167
168/// Float not equal: ( Float Float -- Int )
169///
170/// # Safety
171/// Stack must have two Float values on top
172#[unsafe(no_mangle)]
173pub unsafe extern "C" fn patch_seq_f_neq(stack: Stack) -> Stack {
174    let (rest, a, b) = unsafe { pop_two(stack, "f.<>") };
175    match (a, b) {
176        (Value::Float(x), Value::Float(y)) => unsafe {
177            push(rest, Value::Int(if x != y { 1 } else { 0 }))
178        },
179        _ => panic!("f.<>: expected two Floats on stack"),
180    }
181}
182
183// =============================================================================
184// Type Conversions
185// =============================================================================
186
187/// Convert Int to Float: ( Int -- Float )
188///
189/// # Safety
190/// Stack must have an Int value on top
191#[unsafe(no_mangle)]
192pub unsafe extern "C" fn patch_seq_int_to_float(stack: Stack) -> Stack {
193    assert!(!stack.is_null(), "int->float: stack is empty");
194    let (stack, val) = unsafe { pop(stack) };
195
196    match val {
197        Value::Int(i) => unsafe { push(stack, Value::Float(i as f64)) },
198        _ => panic!("int->float: expected Int on stack"),
199    }
200}
201
202/// Convert Float to Int: ( Float -- Int )
203///
204/// Truncates toward zero. Values outside i64 range are clamped:
205/// - Values >= i64::MAX become i64::MAX
206/// - Values <= i64::MIN become i64::MIN
207/// - NaN becomes 0
208///
209/// # Safety
210/// Stack must have a Float value on top
211#[unsafe(no_mangle)]
212pub unsafe extern "C" fn patch_seq_float_to_int(stack: Stack) -> Stack {
213    assert!(!stack.is_null(), "float->int: stack is empty");
214    let (stack, val) = unsafe { pop(stack) };
215
216    match val {
217        Value::Float(f) => {
218            // Clamp to i64 range to avoid undefined behavior
219            let i = if f.is_nan() {
220                0
221            } else if f >= i64::MAX as f64 {
222                i64::MAX
223            } else if f <= i64::MIN as f64 {
224                i64::MIN
225            } else {
226                f as i64
227            };
228            unsafe { push(stack, Value::Int(i)) }
229        }
230        _ => panic!("float->int: expected Float on stack"),
231    }
232}
233
234/// Convert Float to String: ( Float -- String )
235///
236/// # Safety
237/// Stack must have a Float value on top
238#[unsafe(no_mangle)]
239pub unsafe extern "C" fn patch_seq_float_to_string(stack: Stack) -> Stack {
240    assert!(!stack.is_null(), "float->string: stack is empty");
241    let (stack, val) = unsafe { pop(stack) };
242
243    match val {
244        Value::Float(f) => {
245            let s = f.to_string();
246            unsafe { push(stack, Value::String(global_string(s))) }
247        }
248        _ => panic!("float->string: expected Float on stack"),
249    }
250}
251
252/// Convert String to Float: ( String -- Float Int )
253/// Returns the parsed float and 1 on success, or 0.0 and 0 on failure
254///
255/// # Safety
256/// Stack must have a String value on top
257#[unsafe(no_mangle)]
258pub unsafe extern "C" fn patch_seq_string_to_float(stack: Stack) -> Stack {
259    assert!(!stack.is_null(), "string->float: stack is empty");
260    let (stack, val) = unsafe { pop(stack) };
261
262    match val {
263        Value::String(s) => match s.as_str().parse::<f64>() {
264            Ok(f) => {
265                let stack = unsafe { push(stack, Value::Float(f)) };
266                unsafe { push(stack, Value::Bool(true)) }
267            }
268            Err(_) => {
269                let stack = unsafe { push(stack, Value::Float(0.0)) };
270                unsafe { push(stack, Value::Bool(false)) }
271            }
272        },
273        _ => panic!("string->float: expected String on stack"),
274    }
275}
276
277// =============================================================================
278// Public re-exports with short names
279// =============================================================================
280
281pub use patch_seq_f_add as f_add;
282pub use patch_seq_f_divide as f_divide;
283pub use patch_seq_f_eq as f_eq;
284pub use patch_seq_f_gt as f_gt;
285pub use patch_seq_f_gte as f_gte;
286pub use patch_seq_f_lt as f_lt;
287pub use patch_seq_f_lte as f_lte;
288pub use patch_seq_f_multiply as f_multiply;
289pub use patch_seq_f_neq as f_neq;
290pub use patch_seq_f_subtract as f_subtract;
291pub use patch_seq_float_to_int as float_to_int;
292pub use patch_seq_float_to_string as float_to_string;
293pub use patch_seq_int_to_float as int_to_float;
294pub use patch_seq_push_float as push_float;
295pub use patch_seq_string_to_float as string_to_float;
296
297// =============================================================================
298// Tests
299// =============================================================================
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn test_push_float() {
307        unsafe {
308            let stack = crate::stack::alloc_test_stack();
309            let stack = push_float(stack, 3.5);
310
311            let (_stack, result) = pop(stack);
312            assert_eq!(result, Value::Float(3.5));
313        }
314    }
315
316    #[test]
317    fn test_f_add() {
318        unsafe {
319            let stack = crate::stack::alloc_test_stack();
320            let stack = push(stack, Value::Float(1.5));
321            let stack = push(stack, Value::Float(2.5));
322
323            let stack = f_add(stack);
324
325            let (_stack, result) = pop(stack);
326            assert_eq!(result, Value::Float(4.0));
327        }
328    }
329
330    #[test]
331    fn test_f_subtract() {
332        unsafe {
333            let stack = crate::stack::alloc_test_stack();
334            let stack = push(stack, Value::Float(5.0));
335            let stack = push(stack, Value::Float(2.0));
336
337            let stack = f_subtract(stack);
338
339            let (_stack, result) = pop(stack);
340            assert_eq!(result, Value::Float(3.0));
341        }
342    }
343
344    #[test]
345    fn test_f_multiply() {
346        unsafe {
347            let stack = crate::stack::alloc_test_stack();
348            let stack = push(stack, Value::Float(3.0));
349            let stack = push(stack, Value::Float(4.0));
350
351            let stack = f_multiply(stack);
352
353            let (_stack, result) = pop(stack);
354            assert_eq!(result, Value::Float(12.0));
355        }
356    }
357
358    #[test]
359    fn test_f_divide() {
360        unsafe {
361            let stack = crate::stack::alloc_test_stack();
362            let stack = push(stack, Value::Float(10.0));
363            let stack = push(stack, Value::Float(4.0));
364
365            let stack = f_divide(stack);
366
367            let (_stack, result) = pop(stack);
368            assert_eq!(result, Value::Float(2.5));
369        }
370    }
371
372    #[test]
373    fn test_f_divide_by_zero() {
374        unsafe {
375            let stack = crate::stack::alloc_test_stack();
376            let stack = push(stack, Value::Float(1.0));
377            let stack = push(stack, Value::Float(0.0));
378
379            let stack = f_divide(stack);
380
381            let (_stack, result) = pop(stack);
382            match result {
383                Value::Float(f) => assert!(f.is_infinite()),
384                _ => panic!("Expected Float"),
385            }
386        }
387    }
388
389    #[test]
390    fn test_f_eq_true() {
391        unsafe {
392            let stack = crate::stack::alloc_test_stack();
393            let stack = push(stack, Value::Float(3.5));
394            let stack = push(stack, Value::Float(3.5));
395
396            let stack = f_eq(stack);
397
398            let (_stack, result) = pop(stack);
399            assert_eq!(result, Value::Int(1));
400        }
401    }
402
403    #[test]
404    fn test_f_eq_false() {
405        unsafe {
406            let stack = crate::stack::alloc_test_stack();
407            let stack = push(stack, Value::Float(3.5));
408            let stack = push(stack, Value::Float(2.5));
409
410            let stack = f_eq(stack);
411
412            let (_stack, result) = pop(stack);
413            assert_eq!(result, Value::Int(0));
414        }
415    }
416
417    #[test]
418    fn test_f_lt() {
419        unsafe {
420            let stack = crate::stack::alloc_test_stack();
421            let stack = push(stack, Value::Float(1.5));
422            let stack = push(stack, Value::Float(2.5));
423
424            let stack = f_lt(stack);
425
426            let (_stack, result) = pop(stack);
427            assert_eq!(result, Value::Int(1)); // 1.5 < 2.5
428        }
429    }
430
431    #[test]
432    fn test_f_gt() {
433        unsafe {
434            let stack = crate::stack::alloc_test_stack();
435            let stack = push(stack, Value::Float(2.5));
436            let stack = push(stack, Value::Float(1.5));
437
438            let stack = f_gt(stack);
439
440            let (_stack, result) = pop(stack);
441            assert_eq!(result, Value::Int(1)); // 2.5 > 1.5
442        }
443    }
444
445    #[test]
446    fn test_f_lte() {
447        unsafe {
448            let stack = crate::stack::alloc_test_stack();
449            let stack = push(stack, Value::Float(2.5));
450            let stack = push(stack, Value::Float(2.5));
451
452            let stack = f_lte(stack);
453
454            let (_stack, result) = pop(stack);
455            assert_eq!(result, Value::Int(1)); // 2.5 <= 2.5
456        }
457    }
458
459    #[test]
460    fn test_f_gte() {
461        unsafe {
462            let stack = crate::stack::alloc_test_stack();
463            let stack = push(stack, Value::Float(2.5));
464            let stack = push(stack, Value::Float(2.5));
465
466            let stack = f_gte(stack);
467
468            let (_stack, result) = pop(stack);
469            assert_eq!(result, Value::Int(1)); // 2.5 >= 2.5
470        }
471    }
472
473    #[test]
474    fn test_f_neq() {
475        unsafe {
476            let stack = crate::stack::alloc_test_stack();
477            let stack = push(stack, Value::Float(1.0));
478            let stack = push(stack, Value::Float(2.0));
479
480            let stack = f_neq(stack);
481
482            let (_stack, result) = pop(stack);
483            assert_eq!(result, Value::Int(1)); // 1.0 <> 2.0
484        }
485    }
486
487    #[test]
488    fn test_int_to_float() {
489        unsafe {
490            let stack = crate::stack::alloc_test_stack();
491            let stack = push(stack, Value::Int(42));
492
493            let stack = int_to_float(stack);
494
495            let (_stack, result) = pop(stack);
496            assert_eq!(result, Value::Float(42.0));
497        }
498    }
499
500    #[test]
501    fn test_float_to_int() {
502        unsafe {
503            let stack = crate::stack::alloc_test_stack();
504            let stack = push(stack, Value::Float(3.7));
505
506            let stack = float_to_int(stack);
507
508            let (_stack, result) = pop(stack);
509            assert_eq!(result, Value::Int(3)); // Truncates toward zero
510        }
511    }
512
513    #[test]
514    fn test_float_to_int_negative() {
515        unsafe {
516            let stack = crate::stack::alloc_test_stack();
517            let stack = push(stack, Value::Float(-3.7));
518
519            let stack = float_to_int(stack);
520
521            let (_stack, result) = pop(stack);
522            assert_eq!(result, Value::Int(-3)); // Truncates toward zero
523        }
524    }
525
526    // This test uses i64::MAX which is outside the 44-bit NaN-boxing range
527    #[test]
528    fn test_float_to_int_overflow_positive() {
529        unsafe {
530            let stack = crate::stack::alloc_test_stack();
531            let stack = push(stack, Value::Float(1e20)); // Much larger than i64::MAX
532
533            let stack = float_to_int(stack);
534
535            let (_stack, result) = pop(stack);
536            assert_eq!(result, Value::Int(i64::MAX)); // Clamped to max
537        }
538    }
539
540    // This test uses i64::MIN which is outside the 44-bit NaN-boxing range
541    #[test]
542    fn test_float_to_int_overflow_negative() {
543        unsafe {
544            let stack = crate::stack::alloc_test_stack();
545            let stack = push(stack, Value::Float(-1e20)); // Much smaller than i64::MIN
546
547            let stack = float_to_int(stack);
548
549            let (_stack, result) = pop(stack);
550            assert_eq!(result, Value::Int(i64::MIN)); // Clamped to min
551        }
552    }
553
554    #[test]
555    fn test_float_to_int_nan() {
556        unsafe {
557            let stack = crate::stack::alloc_test_stack();
558            let stack = push(stack, Value::Float(f64::NAN));
559
560            let stack = float_to_int(stack);
561
562            let (_stack, result) = pop(stack);
563            assert_eq!(result, Value::Int(0)); // NaN becomes 0
564        }
565    }
566
567    // This test uses i64::MAX which is outside the 44-bit NaN-boxing range
568    #[test]
569    fn test_float_to_int_infinity() {
570        unsafe {
571            let stack = crate::stack::alloc_test_stack();
572            let stack = push(stack, Value::Float(f64::INFINITY));
573
574            let stack = float_to_int(stack);
575
576            let (_stack, result) = pop(stack);
577            assert_eq!(result, Value::Int(i64::MAX)); // +Inf becomes MAX
578        }
579    }
580
581    #[test]
582    fn test_float_to_string() {
583        unsafe {
584            let stack = crate::stack::alloc_test_stack();
585            let stack = push(stack, Value::Float(3.5));
586
587            let stack = float_to_string(stack);
588
589            let (_stack, result) = pop(stack);
590            match result {
591                Value::String(s) => assert_eq!(s.as_str(), "3.5"),
592                _ => panic!("Expected String"),
593            }
594        }
595    }
596
597    #[test]
598    fn test_float_to_string_whole_number() {
599        unsafe {
600            let stack = crate::stack::alloc_test_stack();
601            let stack = push(stack, Value::Float(42.0));
602
603            let stack = float_to_string(stack);
604
605            let (_stack, result) = pop(stack);
606            match result {
607                Value::String(s) => assert_eq!(s.as_str(), "42"),
608                _ => panic!("Expected String"),
609            }
610        }
611    }
612
613    #[test]
614    fn test_nan_propagation() {
615        unsafe {
616            let stack = crate::stack::alloc_test_stack();
617            let stack = push(stack, Value::Float(f64::NAN));
618            let stack = push(stack, Value::Float(1.0));
619
620            let stack = f_add(stack);
621
622            let (_stack, result) = pop(stack);
623            match result {
624                Value::Float(f) => assert!(f.is_nan()),
625                _ => panic!("Expected Float"),
626            }
627        }
628    }
629
630    #[test]
631    fn test_infinity() {
632        unsafe {
633            let stack = crate::stack::alloc_test_stack();
634            let stack = push(stack, Value::Float(f64::INFINITY));
635            let stack = push(stack, Value::Float(1.0));
636
637            let stack = f_add(stack);
638
639            let (_stack, result) = pop(stack);
640            match result {
641                Value::Float(f) => assert!(f.is_infinite()),
642                _ => panic!("Expected Float"),
643            }
644        }
645    }
646}