floats iter 5.1: prose renders Float literals (finite + NaN/Inf)
This commit is contained in:
@@ -908,13 +908,44 @@ fn write_pattern(out: &mut String, p: &Pattern) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Floats milestone iter 5: render a Float literal's bit pattern
|
||||
/// as surface-style decimal text. Finite values use Grisu3
|
||||
/// (`f64::to_string`) with a `.0` suffix when neither `.` nor
|
||||
/// `e`/`E` appears, parallel to
|
||||
/// `crates/ailang-surface/src/print.rs::write_float_lit`. Non-finite
|
||||
/// values render as `NaN` / `+Inf` / `-Inf` — these are valid in
|
||||
/// Form-A (the canonical bytes carry the bit pattern) but cannot
|
||||
/// be expressed in surface lex; prose is one-way render, so naming
|
||||
/// them after the Mainstream-language spellings is more useful
|
||||
/// than a panic or a `(float-bits hex)` placeholder.
|
||||
fn write_float_lit(out: &mut String, bits: u64) {
|
||||
let f = f64::from_bits(bits);
|
||||
if f.is_nan() {
|
||||
out.push_str("NaN");
|
||||
return;
|
||||
}
|
||||
if f == f64::INFINITY {
|
||||
out.push_str("+Inf");
|
||||
return;
|
||||
}
|
||||
if f == f64::NEG_INFINITY {
|
||||
out.push_str("-Inf");
|
||||
return;
|
||||
}
|
||||
let s = f.to_string();
|
||||
out.push_str(&s);
|
||||
if !s.contains('.') && !s.contains('e') && !s.contains('E') {
|
||||
out.push_str(".0");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_lit(out: &mut String, lit: &Literal) {
|
||||
match lit {
|
||||
Literal::Int { value } => out.push_str(&value.to_string()),
|
||||
Literal::Bool { value } => out.push_str(if *value { "true" } else { "false" }),
|
||||
Literal::Str { value } => write_string_lit(out, value),
|
||||
Literal::Unit => out.push_str("()"),
|
||||
Literal::Float { .. } => unimplemented!("Floats milestone iter 5: prose"),
|
||||
Literal::Float { bits } => write_float_lit(out, *bits),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,6 +1220,46 @@ mod tests {
|
||||
assert_eq!(render_term(&Term::Lit { lit: Literal::Unit }), "()");
|
||||
}
|
||||
|
||||
/// Floats milestone iter 5.1: prose renders Float literals as
|
||||
/// surface-style decimal text. Finite values use shortest
|
||||
/// round-trippable decimal with `.0` suffix when neither `.` nor
|
||||
/// `e`/`E` is in the rendered string (parallel to surface print).
|
||||
/// Non-finite (NaN, ±Inf) render as the Mainstream-language
|
||||
/// spellings — `NaN`, `+Inf`, `-Inf` — because prose is a one-way
|
||||
/// render and the surface lex grammar's no-NaN/Inf restriction
|
||||
/// does not apply to it.
|
||||
#[test]
|
||||
fn renders_float_literal_finite() {
|
||||
use ailang_core::ast::*;
|
||||
let t = Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } };
|
||||
let s = render_term(&t);
|
||||
assert_eq!(s, "1.5");
|
||||
|
||||
let t = Term::Lit { lit: Literal::Float { bits: 0x4024_0000_0000_0000u64 } };
|
||||
let s = render_term(&t);
|
||||
assert_eq!(s, "10.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_float_literal_signed_zero() {
|
||||
use ailang_core::ast::*;
|
||||
let pos = Term::Lit { lit: Literal::Float { bits: 0x0u64 } };
|
||||
assert_eq!(render_term(&pos), "0.0");
|
||||
let neg = Term::Lit { lit: Literal::Float { bits: 0x8000_0000_0000_0000u64 } };
|
||||
assert_eq!(render_term(&neg), "-0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_float_literal_non_finite() {
|
||||
use ailang_core::ast::*;
|
||||
let nan = Term::Lit { lit: Literal::Float { bits: 0x7ff8_0000_0000_0000u64 } };
|
||||
assert_eq!(render_term(&nan), "NaN");
|
||||
let pos_inf = Term::Lit { lit: Literal::Float { bits: 0x7ff0_0000_0000_0000u64 } };
|
||||
assert_eq!(render_term(&pos_inf), "+Inf");
|
||||
let neg_inf = Term::Lit { lit: Literal::Float { bits: 0xfff0_0000_0000_0000u64 } };
|
||||
assert_eq!(render_term(&neg_inf), "-Inf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_renders_as_name() {
|
||||
assert_eq!(render_term(&Term::Var { name: "x".into() }), "x");
|
||||
|
||||
Reference in New Issue
Block a user