floats iter 2.3: surface print emits shortest round-trippable decimal with .0 fallback
This commit is contained in:
@@ -559,13 +559,44 @@ fn write_arm(out: &mut String, arm: &Arm, level: usize) {
|
||||
out.push(')');
|
||||
}
|
||||
|
||||
/// Render a Float literal's bit pattern as surface text. The output
|
||||
/// is the shortest round-trippable decimal produced by
|
||||
/// `f64::to_string` (Grisu3). If the rendered form contains
|
||||
/// neither `.` nor `e`/`E` (`f64::to_string` returns "1" for
|
||||
/// `1.0_f64`, "10000000000" for `1e10_f64`), append `.0` so the
|
||||
/// lexer's `is_int`/`has_dot|has_exp` dispatch routes the token to
|
||||
/// the Float path on re-lex. This preserves the
|
||||
/// `lex(print(L)) == L` round-trip property pinned by
|
||||
/// [`print_then_parse_round_trip_float_literal`].
|
||||
///
|
||||
/// Non-finite bit patterns (NaN, ±Inf) cannot be expressed in
|
||||
/// surface form (per spec section A2 — the lexer rejects all such
|
||||
/// inputs). A Float literal in the AST whose bits are non-finite
|
||||
/// must therefore have arrived via Form-A directly. The printer's
|
||||
/// job is surface output, not a Form-A escape hatch, so we panic
|
||||
/// rather than emit a non-round-trippable token.
|
||||
fn write_float_lit(out: &mut String, bits: u64) {
|
||||
let f = f64::from_bits(bits);
|
||||
if !f.is_finite() {
|
||||
panic!(
|
||||
"write_float_lit: non-finite Float literal {bits:#018x} \
|
||||
cannot be expressed in surface form"
|
||||
);
|
||||
}
|
||||
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("(lit-unit)"),
|
||||
Literal::Float { .. } => unimplemented!("Floats milestone iter 2: surface print"),
|
||||
Literal::Float { bits } => write_float_lit(out, *bits),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +618,7 @@ fn write_pattern(out: &mut String, p: &Pattern) {
|
||||
// came through the typechecker.
|
||||
out.push_str("(lit-unit)");
|
||||
}
|
||||
Literal::Float { .. } => unimplemented!("Floats milestone iter 2: surface print"),
|
||||
Literal::Float { bits } => write_float_lit(out, *bits),
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
@@ -733,4 +764,40 @@ mod tests {
|
||||
};
|
||||
round_trip(m, "minimal_instance");
|
||||
}
|
||||
|
||||
/// Iter 22-floats.2 RED: `lex(print(L)) == L` round-trip property
|
||||
/// for Float literals. Six representative bit patterns:
|
||||
/// `1.5`, `0.0`, `-0.0`, `10.0`, `-0.375`, `1e10`. The round-trip
|
||||
/// uses the existing `round_trip(Module, &str)` helper, which
|
||||
/// prints the module to surface form, re-parses it, and asserts
|
||||
/// canonical bytes match. The Float arm in surface print MUST emit
|
||||
/// at least one of `.` or `e`/`E` so the lexer recognises the
|
||||
/// output as a Float (not an Int) — `f64::to_string` returns "1"
|
||||
/// for `1.0_f64`, "10000000000" for `1e10_f64`, and the printer's
|
||||
/// post-pass adds `.0` in those cases.
|
||||
#[test]
|
||||
fn print_then_parse_round_trip_float_literal() {
|
||||
use ailang_core::ast::*;
|
||||
for &bits in &[
|
||||
0x3ff8_0000_0000_0000u64, // 1.5
|
||||
0x0000_0000_0000_0000u64, // 0.0
|
||||
0x8000_0000_0000_0000u64, // -0.0
|
||||
0x4024_0000_0000_0000u64, // 10.0
|
||||
0xbfd8_0000_0000_0000u64, // -0.375
|
||||
0x4202_a05f_2000_0000u64, // 1e10
|
||||
] {
|
||||
let m = Module {
|
||||
schema: ailang_core::SCHEMA.to_string(),
|
||||
name: "M".into(),
|
||||
imports: vec![],
|
||||
defs: vec![Def::Const(ConstDef {
|
||||
name: "k".into(),
|
||||
ty: Type::float(),
|
||||
value: Term::Lit { lit: Literal::Float { bits } },
|
||||
doc: None,
|
||||
})],
|
||||
};
|
||||
round_trip(m, &format!("float_literal_{bits:#018x}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user