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:
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user