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 63-bit signed range are clamped:
205/// - Values >= 2^62-1 become 2^62-1 (4611686018427387903)
206/// - Values <= -(2^62) become -(2^62) (-4611686018427387904)
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            // 63-bit signed integer range: -(2^62) to (2^62 - 1)
220            const INT63_MAX: i64 = (1i64 << 62) - 1;
221            const INT63_MIN: i64 = -(1i64 << 62);
222            let i = if f.is_nan() {
223                0
224            } else if f >= INT63_MAX as f64 {
225                INT63_MAX
226            } else if f <= INT63_MIN as f64 {
227                INT63_MIN
228            } else {
229                f as i64
230            };
231            unsafe { push(stack, Value::Int(i)) }
232        }
233        _ => panic!("float->int: expected Float on stack"),
234    }
235}
236
237/// Convert Float to String: ( Float -- String )
238///
239/// # Safety
240/// Stack must have a Float value on top
241#[unsafe(no_mangle)]
242pub unsafe extern "C" fn patch_seq_float_to_string(stack: Stack) -> Stack {
243    assert!(!stack.is_null(), "float->string: stack is empty");
244    let (stack, val) = unsafe { pop(stack) };
245
246    match val {
247        Value::Float(f) => {
248            let s = f.to_string();
249            unsafe { push(stack, Value::String(global_string(s))) }
250        }
251        _ => panic!("float->string: expected Float on stack"),
252    }
253}
254
255/// Convert String to Float: ( String -- Float Int )
256/// Returns the parsed float and 1 on success, or 0.0 and 0 on failure
257///
258/// # Safety
259/// Stack must have a String value on top
260#[unsafe(no_mangle)]
261pub unsafe extern "C" fn patch_seq_string_to_float(stack: Stack) -> Stack {
262    assert!(!stack.is_null(), "string->float: stack is empty");
263    let (stack, val) = unsafe { pop(stack) };
264
265    match val {
266        Value::String(s) => match s.as_str().parse::<f64>() {
267            Ok(f) => {
268                let stack = unsafe { push(stack, Value::Float(f)) };
269                unsafe { push(stack, Value::Bool(true)) }
270            }
271            Err(_) => {
272                let stack = unsafe { push(stack, Value::Float(0.0)) };
273                unsafe { push(stack, Value::Bool(false)) }
274            }
275        },
276        _ => panic!("string->float: expected String on stack"),
277    }
278}
279
280// =============================================================================
281// Public re-exports with short names
282// =============================================================================
283
284pub use patch_seq_f_add as f_add;
285pub use patch_seq_f_divide as f_divide;
286pub use patch_seq_f_eq as f_eq;
287pub use patch_seq_f_gt as f_gt;
288pub use patch_seq_f_gte as f_gte;
289pub use patch_seq_f_lt as f_lt;
290pub use patch_seq_f_lte as f_lte;
291pub use patch_seq_f_multiply as f_multiply;
292pub use patch_seq_f_neq as f_neq;
293pub use patch_seq_f_subtract as f_subtract;
294pub use patch_seq_float_to_int as float_to_int;
295pub use patch_seq_float_to_string as float_to_string;
296pub use patch_seq_int_to_float as int_to_float;
297pub use patch_seq_push_float as push_float;
298pub use patch_seq_string_to_float as string_to_float;
299
300// =============================================================================
301// Tests
302// =============================================================================
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_push_float() {
310        unsafe {
311            let stack = crate::stack::alloc_test_stack();
312            let stack = push_float(stack, 3.5);
313
314            let (_stack, result) = pop(stack);
315            assert_eq!(result, Value::Float(3.5));
316        }
317    }
318
319    #[test]
320    fn test_f_add() {
321        unsafe {
322            let stack = crate::stack::alloc_test_stack();
323            let stack = push(stack, Value::Float(1.5));
324            let stack = push(stack, Value::Float(2.5));
325
326            let stack = f_add(stack);
327
328            let (_stack, result) = pop(stack);
329            assert_eq!(result, Value::Float(4.0));
330        }
331    }
332
333    #[test]
334    fn test_f_subtract() {
335        unsafe {
336            let stack = crate::stack::alloc_test_stack();
337            let stack = push(stack, Value::Float(5.0));
338            let stack = push(stack, Value::Float(2.0));
339
340            let stack = f_subtract(stack);
341
342            let (_stack, result) = pop(stack);
343            assert_eq!(result, Value::Float(3.0));
344        }
345    }
346
347    #[test]
348    fn test_f_multiply() {
349        unsafe {
350            let stack = crate::stack::alloc_test_stack();
351            let stack = push(stack, Value::Float(3.0));
352            let stack = push(stack, Value::Float(4.0));
353
354            let stack = f_multiply(stack);
355
356            let (_stack, result) = pop(stack);
357            assert_eq!(result, Value::Float(12.0));
358        }
359    }
360
361    #[test]
362    fn test_f_divide() {
363        unsafe {
364            let stack = crate::stack::alloc_test_stack();
365            let stack = push(stack, Value::Float(10.0));
366            let stack = push(stack, Value::Float(4.0));
367
368            let stack = f_divide(stack);
369
370            let (_stack, result) = pop(stack);
371            assert_eq!(result, Value::Float(2.5));
372        }
373    }
374
375    #[test]
376    fn test_f_divide_by_zero() {
377        unsafe {
378            let stack = crate::stack::alloc_test_stack();
379            let stack = push(stack, Value::Float(1.0));
380            let stack = push(stack, Value::Float(0.0));
381
382            let stack = f_divide(stack);
383
384            let (_stack, result) = pop(stack);
385            match result {
386                Value::Float(f) => assert!(f.is_infinite()),
387                _ => panic!("Expected Float"),
388            }
389        }
390    }
391
392    #[test]
393    fn test_f_eq_true() {
394        unsafe {
395            let stack = crate::stack::alloc_test_stack();
396            let stack = push(stack, Value::Float(3.5));
397            let stack = push(stack, Value::Float(3.5));
398
399            let stack = f_eq(stack);
400
401            let (_stack, result) = pop(stack);
402            assert_eq!(result, Value::Int(1));
403        }
404    }
405
406    #[test]
407    fn test_f_eq_false() {
408        unsafe {
409            let stack = crate::stack::alloc_test_stack();
410            let stack = push(stack, Value::Float(3.5));
411            let stack = push(stack, Value::Float(2.5));
412
413            let stack = f_eq(stack);
414
415            let (_stack, result) = pop(stack);
416            assert_eq!(result, Value::Int(0));
417        }
418    }
419
420    #[test]
421    fn test_f_lt() {
422        unsafe {
423            let stack = crate::stack::alloc_test_stack();
424            let stack = push(stack, Value::Float(1.5));
425            let stack = push(stack, Value::Float(2.5));
426
427            let stack = f_lt(stack);
428
429            let (_stack, result) = pop(stack);
430            assert_eq!(result, Value::Int(1)); // 1.5 < 2.5
431        }
432    }
433
434    #[test]
435    fn test_f_gt() {
436        unsafe {
437            let stack = crate::stack::alloc_test_stack();
438            let stack = push(stack, Value::Float(2.5));
439            let stack = push(stack, Value::Float(1.5));
440
441            let stack = f_gt(stack);
442
443            let (_stack, result) = pop(stack);
444            assert_eq!(result, Value::Int(1)); // 2.5 > 1.5
445        }
446    }
447
448    #[test]
449    fn test_f_lte() {
450        unsafe {
451            let stack = crate::stack::alloc_test_stack();
452            let stack = push(stack, Value::Float(2.5));
453            let stack = push(stack, Value::Float(2.5));
454
455            let stack = f_lte(stack);
456
457            let (_stack, result) = pop(stack);
458            assert_eq!(result, Value::Int(1)); // 2.5 <= 2.5
459        }
460    }
461
462    #[test]
463    fn test_f_gte() {
464        unsafe {
465            let stack = crate::stack::alloc_test_stack();
466            let stack = push(stack, Value::Float(2.5));
467            let stack = push(stack, Value::Float(2.5));
468
469            let stack = f_gte(stack);
470
471            let (_stack, result) = pop(stack);
472            assert_eq!(result, Value::Int(1)); // 2.5 >= 2.5
473        }
474    }
475
476    #[test]
477    fn test_f_neq() {
478        unsafe {
479            let stack = crate::stack::alloc_test_stack();
480            let stack = push(stack, Value::Float(1.0));
481            let stack = push(stack, Value::Float(2.0));
482
483            let stack = f_neq(stack);
484
485            let (_stack, result) = pop(stack);
486            assert_eq!(result, Value::Int(1)); // 1.0 <> 2.0
487        }
488    }
489
490    #[test]
491    fn test_int_to_float() {
492        unsafe {
493            let stack = crate::stack::alloc_test_stack();
494            let stack = push(stack, Value::Int(42));
495
496            let stack = int_to_float(stack);
497
498            let (_stack, result) = pop(stack);
499            assert_eq!(result, Value::Float(42.0));
500        }
501    }
502
503    #[test]
504    fn test_float_to_int() {
505        unsafe {
506            let stack = crate::stack::alloc_test_stack();
507            let stack = push(stack, Value::Float(3.7));
508
509            let stack = float_to_int(stack);
510
511            let (_stack, result) = pop(stack);
512            assert_eq!(result, Value::Int(3)); // Truncates toward zero
513        }
514    }
515
516    #[test]
517    fn test_float_to_int_negative() {
518        unsafe {
519            let stack = crate::stack::alloc_test_stack();
520            let stack = push(stack, Value::Float(-3.7));
521
522            let stack = float_to_int(stack);
523
524            let (_stack, result) = pop(stack);
525            assert_eq!(result, Value::Int(-3)); // Truncates toward zero
526        }
527    }
528
529    #[test]
530    fn test_float_to_int_nan() {
531        unsafe {
532            let stack = crate::stack::alloc_test_stack();
533            let stack = push(stack, Value::Float(f64::NAN));
534
535            let stack = float_to_int(stack);
536
537            let (_stack, result) = pop(stack);
538            assert_eq!(result, Value::Int(0)); // NaN becomes 0
539        }
540    }
541
542    #[test]
543    fn test_float_to_int_clamps_to_63bit_max() {
544        unsafe {
545            let stack = crate::stack::alloc_test_stack();
546            let stack = push(stack, Value::Float(1e20)); // Much larger than 63-bit max
547
548            let stack = float_to_int(stack);
549
550            let (_stack, result) = pop(stack);
551            let int63_max = (1i64 << 62) - 1;
552            assert_eq!(result, Value::Int(int63_max));
553        }
554    }
555
556    #[test]
557    fn test_float_to_int_clamps_to_63bit_min() {
558        unsafe {
559            let stack = crate::stack::alloc_test_stack();
560            let stack = push(stack, Value::Float(-1e20)); // Much smaller than 63-bit min
561
562            let stack = float_to_int(stack);
563
564            let (_stack, result) = pop(stack);
565            let int63_min = -(1i64 << 62);
566            assert_eq!(result, Value::Int(int63_min));
567        }
568    }
569
570    #[test]
571    fn test_float_to_int_infinity_clamps() {
572        unsafe {
573            let stack = crate::stack::alloc_test_stack();
574            let stack = push(stack, Value::Float(f64::INFINITY));
575
576            let stack = float_to_int(stack);
577
578            let (_stack, result) = pop(stack);
579            let int63_max = (1i64 << 62) - 1;
580            assert_eq!(result, Value::Int(int63_max));
581        }
582    }
583
584    #[test]
585    fn test_float_to_string() {
586        unsafe {
587            let stack = crate::stack::alloc_test_stack();
588            let stack = push(stack, Value::Float(3.5));
589
590            let stack = float_to_string(stack);
591
592            let (_stack, result) = pop(stack);
593            match result {
594                Value::String(s) => assert_eq!(s.as_str(), "3.5"),
595                _ => panic!("Expected String"),
596            }
597        }
598    }
599
600    #[test]
601    fn test_float_to_string_whole_number() {
602        unsafe {
603            let stack = crate::stack::alloc_test_stack();
604            let stack = push(stack, Value::Float(42.0));
605
606            let stack = float_to_string(stack);
607
608            let (_stack, result) = pop(stack);
609            match result {
610                Value::String(s) => assert_eq!(s.as_str(), "42"),
611                _ => panic!("Expected String"),
612            }
613        }
614    }
615
616    #[test]
617    fn test_nan_propagation() {
618        unsafe {
619            let stack = crate::stack::alloc_test_stack();
620            let stack = push(stack, Value::Float(f64::NAN));
621            let stack = push(stack, Value::Float(1.0));
622
623            let stack = f_add(stack);
624
625            let (_stack, result) = pop(stack);
626            match result {
627                Value::Float(f) => assert!(f.is_nan()),
628                _ => panic!("Expected Float"),
629            }
630        }
631    }
632
633    #[test]
634    fn test_infinity() {
635        unsafe {
636            let stack = crate::stack::alloc_test_stack();
637            let stack = push(stack, Value::Float(f64::INFINITY));
638            let stack = push(stack, Value::Float(1.0));
639
640            let stack = f_add(stack);
641
642            let (_stack, result) = pop(stack);
643            match result {
644                Value::Float(f) => assert!(f.is_infinite()),
645                _ => panic!("Expected Float"),
646            }
647        }
648    }
649}