///|
// Canonical object order and safe numeric representations give equal keys the
// same full JSON encoding. Unsafe numeric precision remains the evaluator's
// existing boundary; original large-number representations are not rounded.
fn grouping_key_normalizer() -> @ast.Expr {
let object : @ast.Expr = Pipe(
ToEntries,
Pipe(SortBy(Key("key")), FromEntries),
)
let number : @ast.Expr = IfThenElse(
Operation(Abs, LessThan, Literal(Number(9007199254740992.0))),
Operation(Identity, Add, Literal(Number(0.0))),
Identity,
)
Walk(
IfThenElse(
Operation(Type, Equal, Literal(String("object"))),
object,
IfThenElse(
Operation(Type, Equal, Literal(String("number"))),
number,
Identity,
),
),
)
}
///|
// Evaluate the original key stream once for each member, before introducing
// any private binding. Group a flat full encoding instead of depth-limited
// debug keys, retain encounter order within groups, then sort the groups by
// their semantic keys. This avoids sorting members or repeated array append.
fn lower_grouping(key : @ast.Expr) -> @ast.Expr {
let binding = "__cli_group_keys"
let keys : @ast.Expr = Variable(binding)
let decorated : @ast.Expr = Map(
As(
Pipe(ArrayConstruct(Some(key)), grouping_key_normalizer()),
binding,
ObjectConstruct([
(Literal(String("keys")), Some(keys)),
(Literal(String("encoded")), Some(Pipe(keys, ToJsonString))),
(Literal(String("value")), Some(Identity)),
]),
),
)
let grouped : @ast.Expr = Pipe(
decorated,
Pipe(
GroupBy(Key("encoded")),
Pipe(SortBy(Pipe(Index([0]), Key("keys"))), Map(Map(Key("value")))),
),
)
IfThenElse(
Operation(Type, Equal, Literal(String("array"))),
grouped,
GroupBy(Literal(Null)),
)
}