// List-scoped expression forms: the all/any/none/single quantifiers, // reduce folds, and list comprehensions. Each binds its iteration // variable in a slot appended past the current row.
package eval import ( ) // scopeFor binds extra variables in slots appended past row, returning the // extended row, slot map, and iteration-variable indices. It caches the // lexically-invariant slot map and idx per node on the Ctx and reuses a // refilled row buffer, so a list scope evaluated once per outer row (a // comprehension or quantifier in a hot filter) allocates nothing after the // first call. The originals are untouched. func ( *Ctx) ( ast.Expr, []value.Value, map[string]int, ...string) ([]value.Value, map[string]int, []int) { := .scopes[] if == nil || .baseLen != len() { = &scopeScratch{ slots: make(map[string]int, len()+len()), idx: make([]int, len()), baseLen: len(), } maps.Copy(.slots, ) for , := range { .idx[] = len() + .slots[] = len() + } if .scopes == nil { .scopes = map[ast.Expr]*scopeScratch{} } .scopes[] = } := len() + len() if cap(.row) < { .row = make([]value.Value, ) } .row = .row[:] copy(.row, ) for := range { .row[len()+] = value.Null() } return .row, .slots, .idx } // listSeq is a list-source view for the list-scope forms: either a // materialized list or a lazily iterated integer range, so a range(a,b[,s]) // source -- the common quantifier counter idiom -- never materializes a // boxed list per evaluation. type listSeq struct { items []value.Value start, step int64 n int ranged bool } // at returns element i without bounds concern beyond [0, n). func ( *listSeq) ( int) value.Value { if .ranged { return value.Int(.start + int64()*.step) } return .items[] } // listSource evaluates a list-scope source expression. A range() call // becomes a lazy sequence under exactly applyRange's semantics (non-int // bound or zero step is null, a non-int step falls back to 1); anything // else evaluates eagerly and unwraps as a list. ok=false is a null or // non-list source. func listSource( *Ctx, ast.Expr, []value.Value, map[string]int) (listSeq, bool) { if , := .(*ast.Func); && !.Star && len(.Args) >= 2 && len(.Args) <= 3 { if , := ResolveFuncOp(.Name); && == FuncRange { , := Eval(, .Args[0], , ).AsInt() , := Eval(, .Args[1], , ).AsInt() if ! || ! { return listSeq{}, false } := int64(1) if len(.Args) == 3 { if , := Eval(, .Args[2], , ).AsInt(); { = } } if == 0 { return listSeq{}, false } := 0 if ( > 0 && <= ) || ( < 0 && >= ) { = int((-)/) + 1 } return listSeq{start: , step: , n: , ranged: true}, true } } , := Eval(, , , ).AsList() return listSeq{items: , n: len()}, } // evalListPred evaluates all/any/none/single(var IN list WHERE pred): // fold the inner predicate under three-valued logic -- a null predicate // result makes the quantifier null only when the definite matches don't // decide it. A non-list source is null. Empty-list folds: all/none true, // any/single false. func evalListPred( *Ctx, *ast.ListPred, []value.Value, map[string]int) value.Value { , := listSource(, .List, , ) if ! { return value.Null() } , , := .scopeFor(, , , .Var) , := 0, 0 for := 0; < .n; ++ { [[0]] = .at() , := value.ThreeValued(Eval(, .Pred, , )) switch { case !: ++ case : ++ } } := .n - - := == 0 switch .Quant { case ast.QuantAll: if > 0 { return value.Bool(false) } return boolOrNull(, true) case ast.QuantAny: if > 0 { return value.Bool(true) } return boolOrNull(, false) case ast.QuantNone: if > 0 { return value.Bool(false) } return boolOrNull(, true) default: // ast.QuantSingle if > 1 { return value.Bool(false) } return boolOrNull(, == 1) } } // boolOrNull is Bool(v) when the result is definite, else Null (unknown // due to a null predicate result that could still change the outcome). func boolOrNull(, bool) value.Value { if { return value.Bool() } return value.Null() } // evalReduce evaluates reduce(acc = init, var IN list | body) as a left // fold; a non-list source is null, an empty list returns the initial // accumulator unchanged. func evalReduce( *Ctx, *ast.Reduce, []value.Value, map[string]int) value.Value { , := listSource(, .List, , ) if ! { return value.Null() } , , := .scopeFor(, , , .Acc, .Var) [[0]] = Eval(, .Init, , ) for := 0; < .n; ++ { [[1]] = .at() [[0]] = Eval(, .Body, , ) } return [[0]] } // evalListComp evaluates [var IN list WHERE filter | map]: keep elements // passing the filter (when present) and collect the mapped value (or the // element itself). A non-list source is null. func evalListComp( *Ctx, *ast.ListComp, []value.Value, map[string]int) value.Value { , := listSource(, .List, , ) if ! { return value.Null() } , , := .scopeFor(, , , .Var) // Presized to the source length -- exact without a filter, an upper // bound with one -- so the collect loop never regrows. := make([]value.Value, 0, .n) for := 0; < .n; ++ { := .at() [[0]] = if .Filter != nil && !Eval(, .Filter, , ).IsTruthy() { continue } if .Map != nil { = append(, Eval(, .Map, , )) } else { = append(, ) } } return value.List() }