// 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 evalimport ()// 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()ifcap(.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 {returnvalue.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 ! || ! {returnlistSeq{}, false } := int64(1)iflen(.Args) == 3 {if , := Eval(, .Args[2], , ).AsInt(); { = } }if == 0 {returnlistSeq{}, false } := 0if ( > 0 && <= ) || ( < 0 && >= ) { = int((-)/) + 1 }returnlistSeq{start: , step: , n: , ranged: true}, true } } , := Eval(, , , ).AsList()returnlistSeq{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 ! {returnvalue.Null() } , , := .scopeFor(, , , .Var) , := 0, 0for := 0; < .n; ++ { [[0]] = .at() , := value.ThreeValued(Eval(, .Pred, , ))switch {case !: ++case : ++ } } := .n - - := == 0switch .Quant {caseast.QuantAll:if > 0 {returnvalue.Bool(false) }returnboolOrNull(, true)caseast.QuantAny:if > 0 {returnvalue.Bool(true) }returnboolOrNull(, false)caseast.QuantNone:if > 0 {returnvalue.Bool(false) }returnboolOrNull(, true)default: // ast.QuantSingleif > 1 {returnvalue.Bool(false) }returnboolOrNull(, == 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 {returnvalue.Bool() }returnvalue.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 ! {returnvalue.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 ! {returnvalue.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(, ) } }returnvalue.List()}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.