floats iter 1.1: Literal::Float variant + canonical hex serde + drift-test anchors
This commit is contained in:
@@ -1698,6 +1698,7 @@ pub(crate) fn synth(
|
|||||||
Literal::Bool { .. } => Type::bool_(),
|
Literal::Bool { .. } => Type::bool_(),
|
||||||
Literal::Str { .. } => Type::str_(),
|
Literal::Str { .. } => Type::str_(),
|
||||||
Literal::Unit => Type::unit(),
|
Literal::Unit => Type::unit(),
|
||||||
|
Literal::Float { .. } => Type::float(),
|
||||||
}),
|
}),
|
||||||
Term::Var { name } => {
|
Term::Var { name } => {
|
||||||
// Lookup precedence: locals → local globals → class-method
|
// Lookup precedence: locals → local globals → class-method
|
||||||
@@ -2308,6 +2309,7 @@ fn type_check_pattern(
|
|||||||
Literal::Bool { .. } => Type::bool_(),
|
Literal::Bool { .. } => Type::bool_(),
|
||||||
Literal::Str { .. } => Type::str_(),
|
Literal::Str { .. } => Type::str_(),
|
||||||
Literal::Unit => Type::unit(),
|
Literal::Unit => Type::unit(),
|
||||||
|
Literal::Float { .. } => Type::float(),
|
||||||
};
|
};
|
||||||
expect_eq(expected, <)?;
|
expect_eq(expected, <)?;
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
|
|||||||
@@ -925,6 +925,7 @@ impl<'a> Emitter<'a> {
|
|||||||
let g = self.intern_string("str", value);
|
let g = self.intern_string("str", value);
|
||||||
("ptr".to_string(), format!("@{g}"))
|
("ptr".to_string(), format!("@{g}"))
|
||||||
}
|
}
|
||||||
|
Literal::Float { .. } => unimplemented!("Floats milestone iter 4: codegen"),
|
||||||
};
|
};
|
||||||
if val_ty != lty {
|
if val_ty != lty {
|
||||||
return Err(CodegenError::Internal(format!(
|
return Err(CodegenError::Internal(format!(
|
||||||
@@ -1224,6 +1225,7 @@ impl<'a> Emitter<'a> {
|
|||||||
(format!("@{g}"), "ptr".into())
|
(format!("@{g}"), "ptr".into())
|
||||||
}
|
}
|
||||||
Literal::Unit => ("0".into(), "i8".into()),
|
Literal::Unit => ("0".into(), "i8".into()),
|
||||||
|
Literal::Float { .. } => unimplemented!("Floats milestone iter 4: codegen"),
|
||||||
}),
|
}),
|
||||||
Term::Var { name } => {
|
Term::Var { name } => {
|
||||||
// Iter 16d: `__unreachable__` is a polymorphic bottom
|
// Iter 16d: `__unreachable__` is a polymorphic bottom
|
||||||
@@ -2358,6 +2360,7 @@ impl<'a> Emitter<'a> {
|
|||||||
Literal::Bool { .. } => Type::bool_(),
|
Literal::Bool { .. } => Type::bool_(),
|
||||||
Literal::Str { .. } => Type::str_(),
|
Literal::Str { .. } => Type::str_(),
|
||||||
Literal::Unit => Type::unit(),
|
Literal::Unit => Type::unit(),
|
||||||
|
Literal::Float { .. } => Type::float(),
|
||||||
}),
|
}),
|
||||||
Term::Var { name } => {
|
Term::Var { name } => {
|
||||||
// Lookup precedence: extras (let-bindings introduced
|
// Lookup precedence: extras (let-bindings introduced
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ Atom forms (no parens):
|
|||||||
- `INT` — integer literal
|
- `INT` — integer literal
|
||||||
- `STRING` — string literal
|
- `STRING` — string literal
|
||||||
- `true`, `false` — bool literals
|
- `true`, `false` — bool literals
|
||||||
|
- `FLOAT` — IEEE-754 binary64 literal in surface form (e.g. `1.5`, `1.5e3`, `1e10`). In Form-A the literal carries the bit pattern as a 16-character lowercase hex string in `{"kind":"float","bits":"<hex>"}`. Surface lex lands in iter 2 of the Floats milestone; the Form-A canonical-bytes shape is stable from iter 1.
|
||||||
- `NAME` — variable reference (parameter, local, top-level def, or import alias)
|
- `NAME` — variable reference (parameter, local, top-level def, or import alias)
|
||||||
|
|
||||||
Parenthesised forms:
|
Parenthesised forms:
|
||||||
|
|||||||
@@ -533,10 +533,42 @@ pub enum Pattern {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private serde helper: emits a `u64` as a 16-character lowercase
|
||||||
|
/// hex JSON string and parses the same shape back. The
|
||||||
|
/// 16-character invariant covers the full `u64` range zero-padded
|
||||||
|
/// (`format!("{:016x}", 0u64) == "0000000000000000"`,
|
||||||
|
/// `format!("{:016x}", u64::MAX) == "ffffffffffffffff"`). Used by
|
||||||
|
/// [`Literal::Float`] so float bit patterns flow through the
|
||||||
|
/// canonical-JSON *string* path — guaranteeing bit stability across
|
||||||
|
/// `serde_json` versions and surfacing NaN / ±Inf, which JSON
|
||||||
|
/// numbers cannot represent.
|
||||||
|
mod hex_u64 {
|
||||||
|
use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(value: &u64, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
s.serialize_str(&format!("{:016x}", value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
|
||||||
|
let s = <&str>::deserialize(d)?;
|
||||||
|
if s.len() != 16 {
|
||||||
|
return Err(D::Error::custom(format!(
|
||||||
|
"Float bits: expected 16 hex chars, got {}",
|
||||||
|
s.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
u64::from_str_radix(s, 16).map_err(D::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A literal value.
|
/// A literal value.
|
||||||
///
|
///
|
||||||
/// The JSON discriminator is the `kind` field. `Unit` carries no
|
/// The JSON discriminator is the `kind` field. `Unit` carries no
|
||||||
/// payload and serializes as `{"kind":"unit"}`.
|
/// payload and serializes as `{"kind":"unit"}`. `Float` carries a
|
||||||
|
/// `u64` IEEE-754 binary64 bit pattern, serialized via the
|
||||||
|
/// [`hex_u64`] helper as a 16-character lowercase hex string —
|
||||||
|
/// canonical-JSON bytes therefore stay bit-stable for hashing and
|
||||||
|
/// can represent NaN / ±Inf (which JSON numbers cannot).
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||||
pub enum Literal {
|
pub enum Literal {
|
||||||
@@ -548,6 +580,12 @@ pub enum Literal {
|
|||||||
Str { value: String },
|
Str { value: String },
|
||||||
/// The unit value `()`.
|
/// The unit value `()`.
|
||||||
Unit,
|
Unit,
|
||||||
|
/// IEEE-754 binary64 (LLVM `double`) carried as its bit pattern.
|
||||||
|
/// See [`hex_u64`] for the serialization rationale.
|
||||||
|
Float {
|
||||||
|
#[serde(with = "hex_u64")]
|
||||||
|
bits: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type expression.
|
/// A type expression.
|
||||||
@@ -632,6 +670,11 @@ impl Type {
|
|||||||
pub fn str_() -> Type {
|
pub fn str_() -> Type {
|
||||||
Type::Con { name: "Str".into(), args: vec![] }
|
Type::Con { name: "Str".into(), args: vec![] }
|
||||||
}
|
}
|
||||||
|
/// Convenience constructor for `Float` (no args). IEEE-754
|
||||||
|
/// binary64. Parallel to [`Type::int`] / [`Type::bool_`].
|
||||||
|
pub fn float() -> Type {
|
||||||
|
Type::Con { name: "Float".into(), args: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
/// Iter 18a: build a `Type::Fn` with all parameter modes set to
|
/// Iter 18a: build a `Type::Fn` with all parameter modes set to
|
||||||
/// `ParamMode::Implicit` and `ret_mode` set to `Implicit`. This is
|
/// `ParamMode::Implicit` and `ret_mode` set to `Implicit`. This is
|
||||||
|
|||||||
@@ -132,4 +132,24 @@ mod tests {
|
|||||||
assert!(!s.contains(' '));
|
assert!(!s.contains(' '));
|
||||||
assert!(!s.contains('\n'));
|
assert!(!s.contains('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iter 22-floats.1 RED: a `Literal::Float` carries the IEEE-754
|
||||||
|
/// binary64 bit pattern as a `u64`; canonical JSON encodes it as
|
||||||
|
/// `{"bits":"<16-lowercase-hex>","kind":"float"}` — string path,
|
||||||
|
/// NOT through `serde_json::Number` (which is not bit-stable for
|
||||||
|
/// floats and cannot represent NaN / ±Inf at all).
|
||||||
|
///
|
||||||
|
/// Pinned literal: `1.5_f64` → bit pattern `0x3FF8000000000000` →
|
||||||
|
/// hex string `"3ff8000000000000"`. Sorted-key order is
|
||||||
|
/// `bits` < `kind` lexicographically.
|
||||||
|
#[test]
|
||||||
|
fn float_literal_canonical_bytes() {
|
||||||
|
use crate::ast::Literal;
|
||||||
|
let lit = Literal::Float {
|
||||||
|
bits: 0x3ff8_0000_0000_0000u64,
|
||||||
|
};
|
||||||
|
let bytes = to_bytes(&lit);
|
||||||
|
let s = std::str::from_utf8(&bytes).unwrap();
|
||||||
|
assert_eq!(s, r#"{"bits":"3ff8000000000000","kind":"float"}"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1058,7 +1058,10 @@ fn build_eq(scrutinee: Term, lit: &Literal) -> Term {
|
|||||||
Literal::Unit => Term::Lit {
|
Literal::Unit => Term::Lit {
|
||||||
lit: Literal::Bool { value: true },
|
lit: Literal::Bool { value: true },
|
||||||
},
|
},
|
||||||
Literal::Int { .. } | Literal::Bool { .. } | Literal::Str { .. } => Term::App {
|
Literal::Int { .. }
|
||||||
|
| Literal::Bool { .. }
|
||||||
|
| Literal::Str { .. }
|
||||||
|
| Literal::Float { .. } => Term::App {
|
||||||
callee: Box::new(Term::Var {
|
callee: Box::new(Term::Var {
|
||||||
name: "==".to_string(),
|
name: "==".to_string(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ fn lit_to_string(l: &Literal) -> String {
|
|||||||
serde_json::to_string(value).unwrap()
|
serde_json::to_string(value).unwrap()
|
||||||
}
|
}
|
||||||
Literal::Unit => "()".to_string(),
|
Literal::Unit => "()".to_string(),
|
||||||
|
Literal::Float { bits } => format!("(float-bits 0x{:016x})", bits),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ fn design_md_anchors_every_literal_variant() {
|
|||||||
(r#""kind": "bool""#, Literal::Bool { value: true }),
|
(r#""kind": "bool""#, Literal::Bool { value: true }),
|
||||||
(r#""kind": "str""#, Literal::Str { value: "x".into() }),
|
(r#""kind": "str""#, Literal::Str { value: "x".into() }),
|
||||||
(r#""kind": "unit""#, Literal::Unit),
|
(r#""kind": "unit""#, Literal::Unit),
|
||||||
|
(r#""kind": "float""#, Literal::Float { bits: 0 }),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (anchor, lit) in exemplars {
|
for (anchor, lit) in exemplars {
|
||||||
@@ -227,6 +228,7 @@ fn design_md_anchors_every_literal_variant() {
|
|||||||
Literal::Bool { .. } => "bool",
|
Literal::Bool { .. } => "bool",
|
||||||
Literal::Str { .. } => "str",
|
Literal::Str { .. } => "str",
|
||||||
Literal::Unit => "unit",
|
Literal::Unit => "unit",
|
||||||
|
Literal::Float { .. } => "float",
|
||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
DESIGN_MD.contains(anchor),
|
DESIGN_MD.contains(anchor),
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ fn spec_mentions_every_literal_variant() {
|
|||||||
("`true`, `false`", Literal::Bool { value: true }),
|
("`true`, `false`", Literal::Bool { value: true }),
|
||||||
("`STRING`", Literal::Str { value: "x".into() }),
|
("`STRING`", Literal::Str { value: "x".into() }),
|
||||||
("(lit-unit)", Literal::Unit),
|
("(lit-unit)", Literal::Unit),
|
||||||
|
("`FLOAT`", Literal::Float { bits: 0 }),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (anchor, lit) in exemplars {
|
for (anchor, lit) in exemplars {
|
||||||
@@ -227,6 +228,7 @@ fn spec_mentions_every_literal_variant() {
|
|||||||
Literal::Bool { .. } => "bool",
|
Literal::Bool { .. } => "bool",
|
||||||
Literal::Str { .. } => "str",
|
Literal::Str { .. } => "str",
|
||||||
Literal::Unit => "unit",
|
Literal::Unit => "unit",
|
||||||
|
Literal::Float { .. } => "float",
|
||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
FORM_A_SPEC.contains(anchor),
|
FORM_A_SPEC.contains(anchor),
|
||||||
|
|||||||
@@ -914,6 +914,7 @@ fn write_lit(out: &mut String, lit: &Literal) {
|
|||||||
Literal::Bool { value } => out.push_str(if *value { "true" } else { "false" }),
|
Literal::Bool { value } => out.push_str(if *value { "true" } else { "false" }),
|
||||||
Literal::Str { value } => write_string_lit(out, value),
|
Literal::Str { value } => write_string_lit(out, value),
|
||||||
Literal::Unit => out.push_str("()"),
|
Literal::Unit => out.push_str("()"),
|
||||||
|
Literal::Float { .. } => unimplemented!("Floats milestone iter 5: prose"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -565,6 +565,7 @@ fn write_lit(out: &mut String, lit: &Literal) {
|
|||||||
Literal::Bool { value } => out.push_str(if *value { "true" } else { "false" }),
|
Literal::Bool { value } => out.push_str(if *value { "true" } else { "false" }),
|
||||||
Literal::Str { value } => write_string_lit(out, value),
|
Literal::Str { value } => write_string_lit(out, value),
|
||||||
Literal::Unit => out.push_str("(lit-unit)"),
|
Literal::Unit => out.push_str("(lit-unit)"),
|
||||||
|
Literal::Float { .. } => unimplemented!("Floats milestone iter 2: surface print"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,6 +587,7 @@ fn write_pattern(out: &mut String, p: &Pattern) {
|
|||||||
// came through the typechecker.
|
// came through the typechecker.
|
||||||
out.push_str("(lit-unit)");
|
out.push_str("(lit-unit)");
|
||||||
}
|
}
|
||||||
|
Literal::Float { .. } => unimplemented!("Floats milestone iter 2: surface print"),
|
||||||
}
|
}
|
||||||
out.push(')');
|
out.push(')');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1865,6 +1865,7 @@ variables of its body are captured from the enclosing scope.
|
|||||||
{ "kind": "bool", "value": <bool> }
|
{ "kind": "bool", "value": <bool> }
|
||||||
{ "kind": "str", "value": "<utf-8>" }
|
{ "kind": "str", "value": "<utf-8>" }
|
||||||
{ "kind": "unit" }
|
{ "kind": "unit" }
|
||||||
|
{ "kind": "float", "bits": "<16-lowercase-hex>" }
|
||||||
```
|
```
|
||||||
|
|
||||||
**`Pattern`** (the `pat` field of an `Arm`; discriminator `p`):
|
**`Pattern`** (the `pat` field of an `Arm`; discriminator `p`):
|
||||||
|
|||||||
Reference in New Issue
Block a user