///|
// Go's public debugger traverses the lowered tree: direct children, thunks,
// then children evaluated in a separate scope. Preserve duplicates from sugar.
fn debugger_children(node : Expr) -> Array[Expr] {
match node.kind {
Local(bindings, body) => [body] + bindings.map(b => b.value)
Func(parameters, body) =>
[body] + parameters.filter_map(p => p.default_value)
Assert(condition, message, body) =>
[
condition,
body,
{
kind: Fail(
message.unwrap_or({
kind: Literal(Json::string("Assertion failed")),
loc: node.loc,
end: None,
}),
),
loc: node.loc,
end: Some(diagnostic_expr_end(node)),
},
]
Obj(fields, locals, assertions, sites) => {
let result = fields.map(f => f.key)
for index, (condition, message) in assertions {
let range = sites.get(index).bind(site => site)
let loc = range.map(r => r.begin).unwrap_or(condition.loc)
let end = range.map(r => r.end)
let failure : Expr = {
kind: Fail(
message.unwrap_or({
kind: Literal(Json::string("Object assertion failed.")),
loc,
end: None,
}),
),
loc,
end,
}
result.push({
kind: If(
condition,
{ kind: Literal(Json::boolean(true)), loc: node.loc, end: None, },
failure,
),
loc,
end,
})
}
result.append(fields.map(f => f.value))
result.append(locals.map(b => b.value))
result
}
ArrayComp(_, _) | ObjectComp(_, _, _, _) =>
debugger_children(debugger_lower_comprehension(node))
_ => ast_export_children(node)
}
}
///|
fn debugger_std_call(node : Expr, name : String, args : Array[Expr]) -> Expr {
let target : Expr = {
kind: Index({ kind: Var("$std"), loc: node.loc, end: None, }, {
kind: Literal(Json::string(name)),
loc: node.loc,
end: None,
}),
loc: node.loc,
end: None,
}
{
kind: Call(target, args.map(arg => (None, arg)), false),
loc: node.loc,
end: node.end,
}
}
///|
// A hook observes the same lowering as the public Go debugger. Ordinary
// execution retains the direct comprehension path and its existing budgets.
fn debugger_lower_comprehension(node : Expr) -> Expr {
let (body, clauses, object) = match node.kind {
ArrayComp(body, clauses) => (body, clauses, false)
ObjectComp(key, body, clauses, locals) =>
(
{
kind: Obj(
[
{
key,
value: if locals.is_empty() {
body
} else {
{ kind: Local(locals, body), loc: node.loc, end: None, }
},
visibility: 1,
additive: false,
},
],
[],
[],
[],
),
loc: node.loc,
end: node.end,
},
clauses,
true,
)
_ => return node
}
let mut inside : Expr = { kind: Arr([body]), loc: node.loc, end: None, }
for i = clauses.length() - 1; i >= 0; i = i - 1 {
match clauses[i] {
When(condition) =>
inside = {
kind: If(condition, inside, {
kind: Arr([]),
loc: node.loc,
end: None,
}),
loc: node.loc,
end: None,
}
For(name, source) =>
inside = debugger_std_call(node, "$flatMapArray", [
{
kind: Func([{ name, default_value: None, range: None, }], inside),
loc: node.loc,
end: None,
},
source,
])
}
}
if object {
debugger_std_call(node, "$objectFlatMerge", [inside])
} else {
inside
}
}