fix: three foundation/el root-cause bugs (no more bandaids)

1. Parser+codegen: bare reassignment `x = expr` inside an if-body
   was compiling to three orphan expressions with no store. Now
   emits a real assignment.

2. Runtime json_get: dot-path segments that are all digits now
   correctly traverse array indices. `json_get(s, "0.field")` works.

3. Runtime HTTP writer: response bodies starting with
   `{"__status__":<int>,...}` now set the HTTP status header to
   that value and strip the marker from the served body. Existing
   404/401/503 paths in product code now produce real status codes
   instead of HTTP 200 with the status hidden in the body.

Self-host fixed point holds: gen2 == gen3 byte-identical.
Snapshot tagged at dist/platform/elc.20260502-1231-self-host.

Backlog: bl-c121edda
This commit is contained in:
Will Anderson
2026-05-02 12:32:23 -05:00
parent 1274bcde35
commit 742bd0b4f9
8 changed files with 332 additions and 30 deletions
+28 -3
View File
@@ -595,9 +595,20 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
let out = out + "(void)(" + val_c + "); "
}
} else {
// Non-trivial stmt kinds (While/For) shouldn't appear in
// expression-position arm bodies; emit nothing rather
// than malformed C.
if str_eq(sk, "Assign") {
// Real reassignment in an expression-position arm
// emit the store; the arm's "value" stays whatever
// result_var was last set to, which is the El
// semantics (assignment is a statement, not a value).
let aname: String = s["name"]
let aval = s["value"]
let aval_c: String = cg_expr(aval)
let out = out + aname + " = " + aval_c + "; "
} else {
// Non-trivial stmt kinds (While/For) shouldn't appear in
// expression-position arm bodies; emit nothing rather
// than malformed C.
}
}
}
}
@@ -686,6 +697,20 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
return declared
}
// Bare reassignment: `name = expr`. Always emits a plain C assignment
// (no `el_val_t` prefix) by construction the parser only produces
// Assign for an existing identifier. If the name happens NOT to be in
// `declared` for the current C scope (it was let-bound by an enclosing
// block) the emit still resolves at C level because the variable lives
// in the surrounding scope.
if kind == "Assign" {
let name: String = stmt["name"]
let val = stmt["value"]
let val_c: String = cg_expr(val)
emit_line(indent + name + " = " + val_c + ";")
return declared
}
if kind == "Expr" {
let val = stmt["value"]
let val_kind: String = val["expr"]
+18
View File
@@ -946,6 +946,24 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
}, p)
}
// Bare reassignment: `name = expr`. Handled BEFORE the expression
// fallback so we don't drop the assign on the floor and emit three
// orphan expressions (the original silent-miscompile bug). El's `let`
// already permits redeclaration, so this only applies when the parser
// sees an Ident followed directly by `=`. `==` is a separate kind
// (EqEq) so there's no ambiguity.
if k == "Ident" {
let k2 = tok_kind(tokens, pos + 1)
if k2 == "Eq" {
let name = tok_value(tokens, pos)
let p = pos + 2
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Assign", "name": name, "value": val }, p)
}
}
// bare expression or if/match statement
let r = parse_expr(tokens, pos)
let val = r["node"]