Upstream itchyny/gojq state of the touched files before a0fd150fb8
This commit is contained in:
1 parent
af32cf4c58
commit
33d02f2fdb
4 files changed
+82
-899
No files matched your search
+79
-208
@@ -17,7 +17,6 @@ type compiler struct {
|
||||
variables []string
|
||||
customFuncs map[string]function
|
||||
inputIter Iter
|
||||
inputMu sync.Mutex
|
||||
codes []*code
|
||||
codeinfos []codeinfo
|
||||
builtinScope *scopeinfo
|
||||
@@ -394,7 +393,7 @@ func (c *compiler) compileQuery(e *Query) error {
|
||||
return c.compileAlt(e.Left, e.Right)
|
||||
case OpAssign, OpModify, OpUpdateAdd, OpUpdateSub,
|
||||
OpUpdateMul, OpUpdateDiv, OpUpdateMod, OpUpdateAlt:
|
||||
return c.compileQueryUpdate(e.Left, e.Right, e.Op, nil)
|
||||
return c.compileQueryUpdate(e.Left, e.Right, e.Op)
|
||||
case OpOr:
|
||||
return c.compileIf(
|
||||
&If{
|
||||
@@ -468,10 +467,7 @@ func (c *compiler) compileAlt(l, r *Query) error {
|
||||
return c.compileQuery(r)
|
||||
}
|
||||
|
||||
// compileQueryUpdate compiles an assignment operator. alloc is nil unless the
|
||||
// assignment may update the input value in place, in which case it yields the
|
||||
// allocator of the enclosing reduce expression.
|
||||
func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) error {
|
||||
func (c *compiler) compileQueryUpdate(l, r *Query, op Operator) error {
|
||||
switch op {
|
||||
case OpAssign:
|
||||
// optimize assignment operator with constant indexing and slicing
|
||||
@@ -491,7 +487,12 @@ func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) er
|
||||
}
|
||||
fallthrough
|
||||
case OpModify:
|
||||
return c.compileUpdateFunc(op.getFunc(), []*Query{l, r}, alloc)
|
||||
return c.compileFunc(
|
||||
&Func{
|
||||
Name: op.getFunc(),
|
||||
Args: []*Query{l, r},
|
||||
},
|
||||
)
|
||||
default:
|
||||
name := "$%0"
|
||||
c.append(&code{op: opdup})
|
||||
@@ -499,53 +500,27 @@ func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) er
|
||||
return err
|
||||
}
|
||||
c.append(&code{op: opstore, v: c.pushVariable(name)})
|
||||
return c.compileUpdateFunc(
|
||||
"_modify",
|
||||
[]*Query{
|
||||
l,
|
||||
{Term: &Term{
|
||||
Type: TermTypeFunc,
|
||||
Func: &Func{
|
||||
Name: op.getFunc(),
|
||||
Args: []*Query{
|
||||
{Term: &Term{Type: TermTypeIdentity}},
|
||||
{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: name}}},
|
||||
return c.compileFunc(
|
||||
&Func{
|
||||
Name: "_modify",
|
||||
Args: []*Query{
|
||||
l,
|
||||
{Term: &Term{
|
||||
Type: TermTypeFunc,
|
||||
Func: &Func{
|
||||
Name: op.getFunc(),
|
||||
Args: []*Query{
|
||||
{Term: &Term{Type: TermTypeIdentity}},
|
||||
{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: name}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
alloc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// Both names are deliberately invalid identifiers, so that a query cannot
|
||||
// reach the allocator variants directly.
|
||||
allocatorArgSuffix = "/3"
|
||||
allocatorName = "$%allocator"
|
||||
)
|
||||
|
||||
// compileUpdateFunc calls _assign or _modify, or the variant taking an
|
||||
// allocator as its third argument.
|
||||
func (c *compiler) compileUpdateFunc(name string, args []*Query, alloc *Query) error {
|
||||
if alloc == nil {
|
||||
return c.compileFunc(&Func{Name: name, Args: args})
|
||||
}
|
||||
args = append(args, alloc)
|
||||
fn := c.lookupBuiltin(name+allocatorArgSuffix, len(args))
|
||||
if fn == nil {
|
||||
switch name {
|
||||
case "_assign":
|
||||
c.compileAssign(true)
|
||||
case "_modify":
|
||||
c.compileModify(true)
|
||||
}
|
||||
fn = c.lookupBuiltin(name+allocatorArgSuffix, len(args))
|
||||
}
|
||||
return c.compileCallPc(fn, args)
|
||||
}
|
||||
|
||||
func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
|
||||
defer c.newScopeDepth()()
|
||||
c.append(&code{op: opdup})
|
||||
@@ -553,36 +528,23 @@ func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
|
||||
if err := c.compileQuery(l); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.compilePatterns(patterns, func() error {
|
||||
if len(patterns) == 1 && c.codes[len(c.codes)-2].op == opexpbegin {
|
||||
c.codes[len(c.codes)-2].op = opnop
|
||||
} else {
|
||||
c.append(&code{op: opexpend})
|
||||
}
|
||||
return c.compileQuery(r)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *compiler) compilePatterns(patterns []*Pattern, body func() error) error {
|
||||
var pc int
|
||||
var vs [][2]int
|
||||
if len(patterns) > 1 {
|
||||
for _, p := range patterns {
|
||||
vs = c.pushPatternVariables(vs, p)
|
||||
}
|
||||
}
|
||||
for i, p := range patterns {
|
||||
var pcc int
|
||||
var err error
|
||||
if i < len(patterns)-1 {
|
||||
defer c.lazy(func() *code {
|
||||
return &code{op: opforkalt, v: pcc}
|
||||
})()
|
||||
}
|
||||
for _, v := range vs {
|
||||
c.append(&code{op: oppush, v: nil})
|
||||
c.append(&code{op: opstore, v: v})
|
||||
if 0 < i {
|
||||
for _, v := range vs {
|
||||
c.append(&code{op: oppush, v: nil})
|
||||
c.append(&code{op: opstore, v: v})
|
||||
}
|
||||
}
|
||||
if _, err := c.compilePattern(nil, p); err != nil {
|
||||
if vs, err = c.compilePattern(vs[:0], p); err != nil {
|
||||
return err
|
||||
}
|
||||
if i < len(patterns)-1 {
|
||||
@@ -595,29 +557,12 @@ func (c *compiler) compilePatterns(patterns []*Pattern, body func() error) error
|
||||
if len(patterns) > 1 {
|
||||
pc = len(c.codes)
|
||||
}
|
||||
return body()
|
||||
}
|
||||
|
||||
func (c *compiler) pushPatternVariables(vs [][2]int, p *Pattern) [][2]int {
|
||||
if p.Name != "" {
|
||||
if v := c.pushVariable(p.Name); !slices.Contains(vs, v) {
|
||||
vs = append(vs, v)
|
||||
}
|
||||
} else if len(p.Array) > 0 {
|
||||
for _, p := range p.Array {
|
||||
vs = c.pushPatternVariables(vs, p)
|
||||
}
|
||||
} else if len(p.Object) > 0 {
|
||||
for _, kv := range p.Object {
|
||||
if kv.Key != "" && kv.Key[0] == '$' {
|
||||
vs = c.pushPatternVariables(vs, &Pattern{Name: kv.Key})
|
||||
}
|
||||
if kv.Val != nil {
|
||||
vs = c.pushPatternVariables(vs, kv.Val)
|
||||
}
|
||||
}
|
||||
if len(patterns) == 1 && c.codes[len(c.codes)-2].op == opexpbegin {
|
||||
c.codes[len(c.codes)-2].op = opnop
|
||||
} else {
|
||||
c.append(&code{op: opexpend})
|
||||
}
|
||||
return vs
|
||||
return c.compileQuery(r)
|
||||
}
|
||||
|
||||
func (c *compiler) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) {
|
||||
@@ -771,46 +716,23 @@ func (c *compiler) compileReduce(e *Reduce) error {
|
||||
}
|
||||
f()
|
||||
c.append(&code{op: opstore, v: v})
|
||||
// An assignment forming the whole update query is the only thing that
|
||||
// observes the accumulated value, so it may modify it in place; see
|
||||
// Query#inPlaceUpdate. The allocator lives across the whole reduction.
|
||||
l, r, op, inPlace := e.Update.inPlaceUpdate()
|
||||
var alloc *Query
|
||||
if inPlace {
|
||||
c.appends(
|
||||
&code{op: oppush, v: nil},
|
||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||
&code{op: opstore, v: c.pushVariable(allocatorName)},
|
||||
)
|
||||
alloc = &Query{Term: &Term{
|
||||
Type: TermTypeFunc, Func: &Func{Name: allocatorName},
|
||||
}}
|
||||
}
|
||||
setfork := c.lazy(func() *code {
|
||||
return &code{op: opfork, v: len(c.codes)}
|
||||
})
|
||||
if err := c.compileQuery(e.Query); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.compilePatterns(e.Patterns, func() error {
|
||||
c.append(&code{op: opload, v: v})
|
||||
f := c.newScopeDepth()
|
||||
var err error
|
||||
if inPlace {
|
||||
err = c.compileQueryUpdate(l, r, op, alloc)
|
||||
} else {
|
||||
err = c.compileQuery(e.Update)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f()
|
||||
c.append(&code{op: opstore, v: v})
|
||||
c.append(&code{op: opbacktrack})
|
||||
return nil
|
||||
}); err != nil {
|
||||
if _, err := c.compilePattern(nil, e.Pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
c.append(&code{op: opload, v: v})
|
||||
f = c.newScopeDepth()
|
||||
if err := c.compileQuery(e.Update); err != nil {
|
||||
return err
|
||||
}
|
||||
f()
|
||||
c.append(&code{op: opstore, v: v})
|
||||
c.append(&code{op: opbacktrack})
|
||||
setfork()
|
||||
c.append(&code{op: oppop})
|
||||
c.append(&code{op: opload, v: v})
|
||||
@@ -831,21 +753,22 @@ func (c *compiler) compileForeach(e *Foreach) error {
|
||||
if err := c.compileQuery(e.Query); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.compilePatterns(e.Patterns, func() error {
|
||||
c.append(&code{op: opload, v: v})
|
||||
f := c.newScopeDepth()
|
||||
if err := c.compileQuery(e.Update); err != nil {
|
||||
return err
|
||||
}
|
||||
f()
|
||||
c.append(&code{op: opdup})
|
||||
c.append(&code{op: opstore, v: v})
|
||||
if e.Extract != nil {
|
||||
defer c.newScopeDepth()()
|
||||
return c.compileQuery(e.Extract)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if _, err := c.compilePattern(nil, e.Pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
c.append(&code{op: opload, v: v})
|
||||
f = c.newScopeDepth()
|
||||
if err := c.compileQuery(e.Update); err != nil {
|
||||
return err
|
||||
}
|
||||
f()
|
||||
c.append(&code{op: opdup})
|
||||
c.append(&code{op: opstore, v: v})
|
||||
if e.Extract != nil {
|
||||
defer c.newScopeDepth()()
|
||||
return c.compileQuery(e.Extract)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *compiler) compileLabel(e *Label) error {
|
||||
@@ -1009,9 +932,9 @@ func (c *compiler) compileFunc(e *Func) error {
|
||||
if !compiled {
|
||||
switch e.Name {
|
||||
case "_assign":
|
||||
c.compileAssign(false)
|
||||
c.compileAssign()
|
||||
case "_modify":
|
||||
c.compileModify(false)
|
||||
c.compileModify()
|
||||
case "_last":
|
||||
c.compileLast()
|
||||
}
|
||||
@@ -1107,54 +1030,29 @@ func (c *compiler) compileFunc(e *Func) error {
|
||||
//
|
||||
// To overcome the difficulty of reducing allocations on `setpath`, we use the
|
||||
// `allocator` type and track the allocated addresses during the reduction.
|
||||
//
|
||||
// The shared variant reuses the allocator of the enclosing reduce expression
|
||||
// instead of creating a new one, modifying the value in place.
|
||||
func (c *compiler) compileAssign(shared bool) {
|
||||
name, argcnt := "_assign", 2
|
||||
if shared {
|
||||
name, argcnt = name+allocatorArgSuffix, 3
|
||||
}
|
||||
defer c.appendBuiltin(name, argcnt)()
|
||||
func (c *compiler) compileAssign() {
|
||||
defer c.appendBuiltin("_assign", 2)()
|
||||
scope := c.newScope()
|
||||
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
||||
x, a := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
||||
// Cannot reuse v, p due to backtracking in x.
|
||||
w, q := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
||||
c.appends(
|
||||
&code{op: opscope, v: [3]int{scope.id, 6, argcnt}},
|
||||
&code{op: opscope, v: [3]int{scope.id, 6, 2}},
|
||||
&code{op: opstore, v: v}, // def _assign(p; $x):
|
||||
&code{op: opstore, v: p},
|
||||
&code{op: opstore, v: x},
|
||||
)
|
||||
if shared {
|
||||
c.append(&code{op: opstore, v: a}) // the allocator argument
|
||||
}
|
||||
c.appends(
|
||||
&code{op: opload, v: v},
|
||||
&code{op: opexpbegin},
|
||||
&code{op: opload, v: x},
|
||||
&code{op: opcallpc},
|
||||
&code{op: opstore, v: x},
|
||||
&code{op: opexpend},
|
||||
)
|
||||
if shared {
|
||||
c.appends(
|
||||
&code{op: opload, v: v},
|
||||
&code{op: opload, v: a},
|
||||
&code{op: opcallpc},
|
||||
&code{op: opstore, v: a},
|
||||
)
|
||||
} else {
|
||||
c.appends(
|
||||
&code{op: oppush, v: nil},
|
||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||
&code{op: opstore, v: a},
|
||||
)
|
||||
}
|
||||
c.appends(
|
||||
&code{op: oppush, v: nil},
|
||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||
&code{op: opstore, v: a},
|
||||
&code{op: opload, v: v},
|
||||
&code{op: opfork, v: len(c.codes) + 17}, // reduce [L1]
|
||||
&code{op: opfork, v: len(c.codes) + 30}, // reduce [L1]
|
||||
&code{op: opdup},
|
||||
&code{op: opstore, v: w},
|
||||
&code{op: oppathbegin}, // path(p)
|
||||
@@ -1178,49 +1076,24 @@ func (c *compiler) compileAssign(shared bool) {
|
||||
|
||||
// Appends the compiled code for the update-assignment operator (`|=`) to
|
||||
// maximize performance. We use the `allocator` type, just like `_assign/2`.
|
||||
//
|
||||
// The shared variant reuses the allocator of the enclosing reduce expression;
|
||||
// see compiler#compileAssign.
|
||||
func (c *compiler) compileModify(shared bool) {
|
||||
name, argcnt := "_modify", 2
|
||||
if shared {
|
||||
name, argcnt = name+allocatorArgSuffix, 3
|
||||
}
|
||||
defer c.appendBuiltin(name, argcnt)()
|
||||
func (c *compiler) compileModify() {
|
||||
defer c.appendBuiltin("_modify", 2)()
|
||||
scope := c.newScope()
|
||||
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
||||
f, d := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
||||
a, l := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
||||
c.appends(
|
||||
&code{op: opscope, v: [3]int{scope.id, 6, argcnt}},
|
||||
&code{op: opscope, v: [3]int{scope.id, 6, 2}},
|
||||
&code{op: opstore, v: v}, // def _modify(p; f):
|
||||
&code{op: opstore, v: p},
|
||||
&code{op: opstore, v: f},
|
||||
)
|
||||
if shared {
|
||||
c.append(&code{op: opstore, v: a}) // the allocator argument
|
||||
}
|
||||
c.appends(
|
||||
&code{op: oppush, v: []any{}},
|
||||
&code{op: opstore, v: d},
|
||||
)
|
||||
if shared {
|
||||
c.appends(
|
||||
&code{op: opload, v: v},
|
||||
&code{op: opload, v: a},
|
||||
&code{op: opcallpc},
|
||||
&code{op: opstore, v: a},
|
||||
)
|
||||
} else {
|
||||
c.appends(
|
||||
&code{op: oppush, v: nil},
|
||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||
&code{op: opstore, v: a},
|
||||
)
|
||||
}
|
||||
c.appends(
|
||||
&code{op: oppush, v: nil},
|
||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||
&code{op: opstore, v: a},
|
||||
&code{op: opload, v: v},
|
||||
&code{op: opfork, v: len(c.codes) + 31}, // reduce [L1]
|
||||
&code{op: opfork, v: len(c.codes) + 40}, // reduce [L1]
|
||||
&code{op: oppathbegin}, // path(p)
|
||||
&code{op: opload, v: p},
|
||||
&code{op: opcallpc},
|
||||
@@ -1229,7 +1102,7 @@ func (c *compiler) compileModify(shared bool) {
|
||||
&code{op: opstore, v: p}, // as $p (.;
|
||||
&code{op: opforklabel, v: l}, // label $l |
|
||||
&code{op: opload, v: v}, //
|
||||
&code{op: opfork, v: len(c.codes) + 28}, // [L2]
|
||||
&code{op: opfork, v: len(c.codes) + 37}, // [L2]
|
||||
&code{op: oppop}, // (getpath($p) |
|
||||
&code{op: opload, v: a},
|
||||
&code{op: opload, v: a},
|
||||
@@ -1243,8 +1116,8 @@ func (c *compiler) compileModify(shared bool) {
|
||||
&code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}},
|
||||
&code{op: opstore, v: v},
|
||||
&code{op: opload, v: v}, // ., break $l
|
||||
&code{op: opfork, v: len(c.codes) + 26}, // [L4]
|
||||
&code{op: opjump, v: len(c.codes) + 30}, // [L3]
|
||||
&code{op: opfork, v: len(c.codes) + 35}, // [L4]
|
||||
&code{op: opjump, v: len(c.codes) + 39}, // [L3]
|
||||
&code{op: opload, v: l}, // [L4]
|
||||
&code{op: opcall, v: [3]any{funcBreak(""), 0, "_break"}},
|
||||
&code{op: opload, v: p}, // append $p to $d [L2]
|
||||
@@ -1330,8 +1203,6 @@ func (c *compiler) funcBuiltins(any, []any) any {
|
||||
}
|
||||
|
||||
func (c *compiler) funcInput(any, []any) any {
|
||||
c.inputMu.Lock()
|
||||
defer c.inputMu.Unlock()
|
||||
v, ok := c.inputIter.Next()
|
||||
if !ok {
|
||||
return errors.New("break")
|
||||
|
||||
Reference in new issue
Block a user