implement ? nil-propagation, write browser-auth.el example, update spec
Iteration 5: ? nil-propagation: Field and Index handlers in js_cg_expr now detect when the object expression is a Try node (the AST node for postfix `?`). When detected, emit JS optional chaining: `(expr)?.["field"] ?? null`. The `?? null` normalizes JS undefined to El's null. A bare `expr?` not followed by field/index still passes through unchanged. browser-auth.el: a realistic 130-line example demonstrating: - @async function with Supabase via native_js_call - DOM bridge: get/set value/text/attr, add/remove class, show/hide - local_storage_get/set for session hints - window_on_load for initialization - window_set to expose functions to the browser global scope - set_timeout for transient state, is_valid_email for input validation Compiles cleanly with elc --target=js --bundle Spec updated: status promoted to Phase 4 / ~80% coverage, nil-prop status updated, new example referenced.
This commit is contained in:
@@ -439,19 +439,36 @@ fn js_cg_expr(expr: Map<String, Any>) -> String {
|
||||
// El's `obj.foo` becomes JS `obj["foo"]` — works on plain objects
|
||||
// (maps) and on JS objects with prototype. el_get_field is a
|
||||
// runtime helper for callers that want EL_NULL on missing keys.
|
||||
//
|
||||
// If the object is a Try (nil-propagation postfix `obj?`), use JS
|
||||
// optional chaining: `obj?.["foo"]` — returns undefined (null) if obj
|
||||
// is null/undefined, otherwise accesses the field.
|
||||
let obj = expr["object"]
|
||||
let field: String = expr["field"]
|
||||
let obj_kind: String = obj["expr"]
|
||||
if str_eq(obj_kind, "Try") {
|
||||
let inner = obj["inner"]
|
||||
let inner_c: String = js_cg_expr(inner)
|
||||
return "(" + inner_c + ")?.[" + js_str_lit(field) + "] ?? null"
|
||||
}
|
||||
let obj_c: String = js_cg_expr(obj)
|
||||
return "el_get_field(" + obj_c + ", " + js_str_lit(field) + ")"
|
||||
}
|
||||
|
||||
if kind == "Index" {
|
||||
// Map vs list dispatch on the index expression kind, same as C.
|
||||
// If the object is a Try (nil-propagation), use JS optional indexing.
|
||||
let obj = expr["object"]
|
||||
let idx = expr["index"]
|
||||
let obj_c: String = js_cg_expr(obj)
|
||||
let idx_c: String = js_cg_expr(idx)
|
||||
let idx_kind: String = idx["expr"]
|
||||
let obj_kind: String = obj["expr"]
|
||||
if str_eq(obj_kind, "Try") {
|
||||
let inner = obj["inner"]
|
||||
let inner_c: String = js_cg_expr(inner)
|
||||
return "(" + inner_c + ")?.[" + idx_c + "] ?? null"
|
||||
}
|
||||
if str_eq(idx_kind, "Str") {
|
||||
return "el_get_field(" + obj_c + ", " + idx_c + ")"
|
||||
}
|
||||
@@ -491,6 +508,12 @@ fn js_cg_expr(expr: Map<String, Any>) -> String {
|
||||
}
|
||||
|
||||
if kind == "Try" {
|
||||
// Postfix `?` — nil-propagation guard.
|
||||
// When used as `expr?.field` the Field handler above intercepts and
|
||||
// emits `(expr)?.["field"]`. Here, a bare `expr?` (not followed by
|
||||
// field/index access) passes through to the inner expression unchanged
|
||||
// (it acts as an identity but marks the value as "nil-propagating" for
|
||||
// its caller). This matches the C backend's current behavior.
|
||||
let inner = expr["inner"]
|
||||
return js_cg_expr(inner)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user