Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f832c8def | |||
| d71fc4c1c0 | |||
| a118d19393 |
Vendored
BIN
Binary file not shown.
@@ -5327,8 +5327,8 @@ el_val_t str_to_float(el_val_t s) {
|
||||
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t math_sqrt(el_val_t f) { return el_from_float(sqrt(el_to_float(f))); }
|
||||
el_val_t math_log(el_val_t f) { return el_from_float(log(el_to_float(f))); }
|
||||
el_val_t math_ln(el_val_t f) { return el_from_float(log(el_to_float(f))); }
|
||||
el_val_t math_log(el_val_t f) { return el_from_float(log10(el_to_float(f))); } /* base-10, per runtime/math.el */
|
||||
el_val_t math_ln(el_val_t f) { return el_from_float(log(el_to_float(f))); } /* natural log */
|
||||
el_val_t math_sin(el_val_t f) { return el_from_float(sin(el_to_float(f))); }
|
||||
el_val_t math_cos(el_val_t f) { return el_from_float(cos(el_to_float(f))); }
|
||||
el_val_t math_pi(void) { return el_from_float(3.141592653589793238462643383279502884); }
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h> /* fmod, sin, sqrt, ... — used by codegen'd float arithmetic */
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
|
||||
@@ -731,6 +731,41 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
||||
// because both share the int64 slot).
|
||||
}
|
||||
|
||||
// -- Float arithmetic / comparison dispatch -------------------------
|
||||
// When either operand is provably Float, promote BOTH operands to a C
|
||||
// double and emit real double arithmetic. Arithmetic results are
|
||||
// re-wrapped into the el_val_t float slot via el_from_float(); the
|
||||
// integer % becomes fmod(); comparisons yield a bare 0/1. Without this
|
||||
// path, float `+` fell to el_str_concat (segfault) and `- * / %` fell
|
||||
// to integer ops on the raw IEEE-754 bit pattern (garbage). Mixed
|
||||
// Int/Float promotes the Int side with a (double) cast. This runs
|
||||
// AFTER temporal dispatch (a Float is never temporal) and BEFORE the
|
||||
// Int/String paths, and is skipped when either side is a bare Str so
|
||||
// string concat/equality is never disturbed.
|
||||
if !str_eq(left_kind, "Str") {
|
||||
if !str_eq(right_kind, "Str") {
|
||||
let any_float: Bool = false
|
||||
if is_float_expr(left) { let any_float = true }
|
||||
if is_float_expr(right) { let any_float = true }
|
||||
if any_float {
|
||||
let l_d: String = float_operand_c(left, left_c)
|
||||
let r_d: String = float_operand_c(right, right_c)
|
||||
if str_eq(op, "Plus") { return "el_from_float(" + l_d + " + " + r_d + ")" }
|
||||
if str_eq(op, "Minus") { return "el_from_float(" + l_d + " - " + r_d + ")" }
|
||||
if str_eq(op, "Star") { return "el_from_float(" + l_d + " * " + r_d + ")" }
|
||||
if str_eq(op, "Slash") { return "el_from_float(" + l_d + " / " + r_d + ")" }
|
||||
if str_eq(op, "Percent") { return "el_from_float(fmod(" + l_d + ", " + r_d + "))" }
|
||||
if str_eq(op, "Lt") { return "(" + l_d + " < " + r_d + ")" }
|
||||
if str_eq(op, "Gt") { return "(" + l_d + " > " + r_d + ")" }
|
||||
if str_eq(op, "LtEq") { return "(" + l_d + " <= " + r_d + ")" }
|
||||
if str_eq(op, "GtEq") { return "(" + l_d + " >= " + r_d + ")" }
|
||||
if str_eq(op, "EqEq") { return "(" + l_d + " == " + r_d + ")" }
|
||||
if str_eq(op, "NotEq") { return "(" + l_d + " != " + r_d + ")" }
|
||||
// And/Or with a float operand is ill-typed — fall through.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if op == "Plus" {
|
||||
// If either side is a string literal, always concat
|
||||
if left_kind == "Str" {
|
||||
@@ -1406,6 +1441,9 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
||||
if str_eq(ltype, "Int") {
|
||||
add_int_name(name)
|
||||
}
|
||||
if str_eq(ltype, "Float") {
|
||||
add_float_name(name)
|
||||
}
|
||||
// Temporal type annotations register the name with the matching
|
||||
// typed-set so BinOp / comparison codegen routes through the
|
||||
// typed wrappers and forbids cross-type ops.
|
||||
@@ -1465,6 +1503,12 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
||||
if is_zone_expr(val) {
|
||||
add_zone_name(name)
|
||||
}
|
||||
// Float inference: an unannotated `let` whose RHS is provably Float
|
||||
// (literal, math builtin, or float arithmetic) registers the name so
|
||||
// later `name <op> ...` routes through the double path.
|
||||
if is_float_expr(val) {
|
||||
add_float_name(name)
|
||||
}
|
||||
let vk: String = val["expr"]
|
||||
if str_eq(vk, "Int") {
|
||||
add_int_name(name)
|
||||
@@ -1784,6 +1828,15 @@ fn is_int_name(name: String) -> Bool {
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
// Float-typed binding set — parallel to is_int_name. Populated from `let x:
|
||||
// Float = ...` annotations, `: Float` parameters, and inference from a Float
|
||||
// RHS. Consulted by is_float_expr to route arithmetic through the double path.
|
||||
fn is_float_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__float_names")
|
||||
if str_eq(csv, "") { return false }
|
||||
return str_contains(csv, "," + name + ",")
|
||||
}
|
||||
|
||||
// Same shape as is_int_name, for Instant- and Duration-typed bindings.
|
||||
// Used by the BinOp/comparison codegen to dispatch arithmetic through the
|
||||
// typed runtime wrappers (el_instant_add_dur, el_duration_lt, -) and to
|
||||
@@ -1847,6 +1900,28 @@ fn is_int_call(call_expr: Map<String, Any>) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Known runtime builtins that return Float. Parallel to is_int_call — lets a
|
||||
// Call participate in float arithmetic (and get inferred into __float_names on
|
||||
// an unannotated `let`). New Float-returning builtins must be added here.
|
||||
fn is_float_call(call_expr: Map<String, Any>) -> Bool {
|
||||
let func = call_expr["func"]
|
||||
let fk: String = func["expr"]
|
||||
if !str_eq(fk, "Ident") { return false }
|
||||
let name: String = func["name"]
|
||||
if str_eq(name, "int_to_float") { return true }
|
||||
if str_eq(name, "str_to_float") { return true }
|
||||
if str_eq(name, "json_get_float") { return true }
|
||||
if str_eq(name, "decimal_round") { return true }
|
||||
if str_eq(name, "math_pi") { return true }
|
||||
if str_eq(name, "math_sin") { return true }
|
||||
if str_eq(name, "math_cos") { return true }
|
||||
if str_eq(name, "math_sqrt") { return true }
|
||||
if str_eq(name, "math_log") { return true }
|
||||
if str_eq(name, "math_ln") { return true }
|
||||
if str_eq(name, "el_from_float") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Builtins that return an Instant. Used by is_instant_expr and the BinOp
|
||||
// dispatch - `now() + 5.seconds` types as Instant only because we can see
|
||||
// that now() is an Instant-returning Call.
|
||||
@@ -2255,19 +2330,71 @@ fn is_int_expr(expr: Map<String, Any>) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// is_float_expr — true when expr is (or evaluates to) a Float-typed value.
|
||||
// Used in EqEq/NotEq codegen to avoid str_eq on float values.
|
||||
// is_float_expr — true when expr is (or provably evaluates to) a Float value.
|
||||
// Mirrors is_int_expr: Float literal, Neg of a Float, a Float-typed Ident
|
||||
// (registered in __float_names), a Float-returning builtin Call, or a Float
|
||||
// arithmetic BinOp (float propagates: if either operand is Float the result
|
||||
// is Float). Drives the float-arithmetic dispatch in cg_expr and the EqEq/
|
||||
// NotEq float comparison. Kept conservative (only provably-Float) so it never
|
||||
// intercepts a value the Int paths must own — the two predicates are mutually
|
||||
// exclusive on well-typed source.
|
||||
fn is_float_expr(expr: Map<String, Any>) -> Bool {
|
||||
let k: String = expr["expr"]
|
||||
if str_eq(k, "Float") { return true }
|
||||
if str_eq(k, "Neg") {
|
||||
let inner = expr["inner"]
|
||||
let ik: String = inner["expr"]
|
||||
if str_eq(ik, "Float") { return true }
|
||||
return is_float_expr(expr["inner"])
|
||||
}
|
||||
if str_eq(k, "Ident") {
|
||||
let name: String = expr["name"]
|
||||
return is_float_name(name)
|
||||
}
|
||||
if str_eq(k, "Call") {
|
||||
return is_float_call(expr)
|
||||
}
|
||||
if str_eq(k, "BinOp") {
|
||||
let op: String = expr["op"]
|
||||
if str_eq(op, "Plus") {
|
||||
if is_float_expr(expr["left"]) { return true }
|
||||
if is_float_expr(expr["right"]) { return true }
|
||||
return false
|
||||
}
|
||||
if str_eq(op, "Minus") {
|
||||
if is_float_expr(expr["left"]) { return true }
|
||||
if is_float_expr(expr["right"]) { return true }
|
||||
return false
|
||||
}
|
||||
if str_eq(op, "Star") {
|
||||
if is_float_expr(expr["left"]) { return true }
|
||||
if is_float_expr(expr["right"]) { return true }
|
||||
return false
|
||||
}
|
||||
if str_eq(op, "Slash") {
|
||||
if is_float_expr(expr["left"]) { return true }
|
||||
if is_float_expr(expr["right"]) { return true }
|
||||
return false
|
||||
}
|
||||
if str_eq(op, "Percent") {
|
||||
if is_float_expr(expr["left"]) { return true }
|
||||
if is_float_expr(expr["right"]) { return true }
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// float_operand_c — render a BinOp operand as a C double for float arithmetic.
|
||||
// A provably-Float operand round-trips through el_to_float() (recovering the
|
||||
// double from the int64 bit-slot); anything else is treated as Int-like and
|
||||
// numerically converted with (double). This is why Float locals/params must
|
||||
// be annotated `: Float` — an unannotated var holding float bits would be
|
||||
// mis-converted by the (double) cast, exactly as the Int system requires
|
||||
// annotations to dispatch arithmetic vs concat.
|
||||
fn float_operand_c(expr: Map<String, Any>, expr_c: String) -> String {
|
||||
if is_float_expr(expr) { return "el_to_float(" + expr_c + ")" }
|
||||
return "(double)(" + expr_c + ")"
|
||||
}
|
||||
|
||||
// -- Capability-kind enforcement ----------------------------------------------
|
||||
//
|
||||
// A program's top-level block (cgi / service / none) determines which
|
||||
@@ -2784,6 +2911,15 @@ fn add_int_name(name: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_float_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__float_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
let key: String = "," + name + ","
|
||||
if str_contains(csv, key) { return true }
|
||||
state_set("__float_names", csv + name + ",")
|
||||
return true
|
||||
}
|
||||
|
||||
fn add_instant_name(name: String) -> Bool {
|
||||
let csv: String = state_get("__instant_names")
|
||||
if str_eq(csv, "") { csv = "," }
|
||||
@@ -2867,6 +3003,7 @@ fn add_zone_name(name: String) -> Bool {
|
||||
|
||||
fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
||||
state_set("__int_names", ",")
|
||||
state_set("__float_names", ",")
|
||||
state_set("__instant_names", ",")
|
||||
state_set("__duration_names", ",")
|
||||
state_set("__calendar_names", ",")
|
||||
@@ -2885,6 +3022,9 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
||||
if str_eq(ptype, "Int") {
|
||||
add_int_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "Float") {
|
||||
add_float_name(pname)
|
||||
}
|
||||
if str_eq(ptype, "Instant") {
|
||||
add_instant_name(pname)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user