Files
kyle 9444a35322 p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification
is ignored. No 502. keys add writes the secret only to --out. Release binary
has no -test-allow-prefix.
2026-09-22 21:54:01 -07:00

1716 lines
42 KiB
Go

package gojq
import (
"context"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
)
type compiler struct {
moduleLoader ModuleLoader
environLoader func() []string
variables []string
customFuncs map[string]function
inputIter Iter
codes []*code
codeinfos []codeinfo
builtinScope *scopeinfo
scopes []*scopeinfo
scopecnt int
regexpCache sync.Map
}
// Code is a compiled jq query.
type Code struct {
variables []string
codes []*code
codeinfos []codeinfo
}
// Run runs the code with the variable values (which should be in the
// same order as the given variables using [WithVariables]) and returns
// a result iterator.
//
// It is safe to call this method in goroutines, to reuse a compiled [*Code].
func (c *Code) Run(v any, values ...any) Iter {
return c.RunWithContext(context.Background(), v, values...)
}
// RunWithContext runs the code with context.
func (c *Code) RunWithContext(ctx context.Context, v any, values ...any) Iter {
if len(values) > len(c.variables) {
return NewIter(&tooManyVariableValuesError{})
} else if len(values) < len(c.variables) {
return NewIter(&expectedVariableError{c.variables[len(values)]})
}
return newEnv(ctx).execute(c, v, values...)
}
type scopeinfo struct {
variables []*varinfo
funcs []*funcinfo
id int
depth int
variablecnt int
}
type varinfo struct {
name string
index [2]int
depth int
}
type funcinfo struct {
name string
pc int
argcnt int
}
// Compile compiles a query.
func Compile(q *Query, options ...CompilerOption) (*Code, error) {
c := &compiler{}
for _, opt := range options {
opt(c)
}
c.builtinScope = c.newScope()
scope := c.newScope()
c.scopes = []*scopeinfo{scope}
setscope := c.lazy(func() *code {
return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, 0}}
})
for _, name := range c.variables {
if !newLexer(name).validVarName() {
return nil, &variableNameError{name}
}
c.appendCodeInfo(name)
c.append(&code{op: opstore, v: c.pushVariable(name)})
}
if c.moduleLoader != nil {
if moduleLoader, ok := c.moduleLoader.(interface {
LoadInitModules() ([]*Query, error)
}); ok {
qs, err := moduleLoader.LoadInitModules()
if err != nil {
return nil, err
}
for _, q := range qs {
if err := c.compileModule(q, ""); err != nil {
return nil, err
}
}
}
}
if err := c.compile(q); err != nil {
return nil, err
}
setscope()
c.optimizeTailRec()
c.optimizeCodeOps()
return &Code{
variables: c.variables,
codes: c.codes,
codeinfos: c.codeinfos,
}, nil
}
func (c *compiler) compile(q *Query) error {
for _, i := range q.Imports {
if err := c.compileImport(i); err != nil {
return err
}
}
if err := c.compileQuery(q); err != nil {
return err
}
c.append(&code{op: opret})
return nil
}
func (c *compiler) compileImport(i *Import) error {
var path, alias string
var err error
if i.ImportPath != "" {
path, alias = i.ImportPath, i.ImportAlias
} else {
path = i.IncludePath
}
if c.moduleLoader == nil {
return fmt.Errorf("cannot load module: %q", path)
}
if strings.HasPrefix(alias, "$") {
var vals any
if moduleLoader, ok := c.moduleLoader.(interface {
LoadJSONWithMeta(string, map[string]any) (any, error)
}); ok {
if vals, err = moduleLoader.LoadJSONWithMeta(path, i.Meta.ToValue()); err != nil {
return err
}
} else if moduleLoader, ok := c.moduleLoader.(interface {
LoadJSON(string) (any, error)
}); ok {
if vals, err = moduleLoader.LoadJSON(path); err != nil {
return err
}
} else {
return fmt.Errorf("module not found: %q", path)
}
c.append(&code{op: oppush, v: vals})
c.append(&code{op: opstore, v: c.pushVariable(alias)})
c.append(&code{op: oppush, v: vals})
c.append(&code{op: opstore, v: c.pushVariable(alias + "::" + alias[1:])})
return nil
}
var q *Query
if moduleLoader, ok := c.moduleLoader.(interface {
LoadModuleWithMeta(string, map[string]any) (*Query, error)
}); ok {
if q, err = moduleLoader.LoadModuleWithMeta(path, i.Meta.ToValue()); err != nil {
return err
}
} else if moduleLoader, ok := c.moduleLoader.(interface {
LoadModule(string) (*Query, error)
}); ok {
if q, err = moduleLoader.LoadModule(path); err != nil {
return err
}
}
c.appendCodeInfo("module " + path)
if err = c.compileModule(q, alias); err != nil {
return err
}
c.appendCodeInfo("end of module " + path)
return nil
}
func (c *compiler) compileModule(q *Query, alias string) error {
scope := c.scopes[len(c.scopes)-1]
scope.depth++
defer func(l int) {
scope.depth--
scope.variables = scope.variables[:l]
}(len(scope.variables))
if alias != "" {
defer func(l int) {
for _, f := range scope.funcs[l:] {
f.name = alias + "::" + f.name
}
}(len(scope.funcs))
}
for _, i := range q.Imports {
if err := c.compileImport(i); err != nil {
return err
}
}
for _, fd := range q.FuncDefs {
if err := c.compileFuncDef(fd, false); err != nil {
return err
}
}
return nil
}
func (c *compiler) newVariable() [2]int {
return c.createVariable("")
}
func (c *compiler) pushVariable(name string) [2]int {
s := c.scopes[len(c.scopes)-1]
for _, v := range s.variables {
if v.name == name && v.depth == s.depth {
return v.index
}
}
return c.createVariable(name)
}
func (c *compiler) createVariable(name string) [2]int {
s := c.scopes[len(c.scopes)-1]
v := [2]int{s.id, s.variablecnt}
s.variablecnt++
s.variables = append(s.variables, &varinfo{name, v, s.depth})
return v
}
func (c *compiler) lookupVariable(name string) ([2]int, error) {
for i := len(c.scopes) - 1; i >= 0; i-- {
s := c.scopes[i]
for j := len(s.variables) - 1; j >= 0; j-- {
if w := s.variables[j]; w.name == name {
return w.index, nil
}
}
}
return [2]int{}, &variableNotFoundError{name}
}
func (c *compiler) lookupFuncOrVariable(name string) (*funcinfo, *varinfo) {
for i, isFunc := len(c.scopes)-1, name[0] != '$'; i >= 0; i-- {
s := c.scopes[i]
if isFunc {
for j := len(s.funcs) - 1; j >= 0; j-- {
if f := s.funcs[j]; f.name == name && f.argcnt == 0 {
return f, nil
}
}
}
for j := len(s.variables) - 1; j >= 0; j-- {
if v := s.variables[j]; v.name == name {
return nil, v
}
}
}
return nil, nil
}
func (c *compiler) lookupBuiltin(name string, argcnt int) *funcinfo {
s := c.builtinScope
for i := len(s.funcs) - 1; i >= 0; i-- {
if f := s.funcs[i]; f.name == name && f.argcnt == argcnt {
return f
}
}
return nil
}
func (c *compiler) appendBuiltin(name string, argcnt int) func() {
setjump := c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})
c.appendCodeInfo(name)
c.builtinScope.funcs = append(
c.builtinScope.funcs,
&funcinfo{name, len(c.codes), argcnt},
)
return func() {
setjump()
c.appendCodeInfo("end of " + name)
}
}
func (c *compiler) newScope() *scopeinfo {
i := c.scopecnt // do not use len(c.scopes) because it pops
c.scopecnt++
return &scopeinfo{id: i}
}
func (c *compiler) newScopeDepth() func() {
scope := c.scopes[len(c.scopes)-1]
l, m := len(scope.variables), len(scope.funcs)
scope.depth++
return func() {
scope.depth--
scope.variables = scope.variables[:l]
scope.funcs = scope.funcs[:m]
}
}
func (c *compiler) compileFuncDef(e *FuncDef, builtin bool) error {
var scope *scopeinfo
if builtin {
scope = c.builtinScope
} else {
scope = c.scopes[len(c.scopes)-1]
}
defer c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})()
c.appendCodeInfo(e.Name)
scope.funcs = append(scope.funcs, &funcinfo{e.Name, len(c.codes), len(e.Args)})
defer func(scopes []*scopeinfo, variables []string) {
c.scopes, c.variables = scopes, variables
}(c.scopes, c.variables)
c.variables = c.variables[len(c.variables):]
scope = c.newScope()
if builtin {
c.scopes = []*scopeinfo{c.builtinScope, scope}
} else {
c.scopes = append(c.scopes, scope)
}
defer c.lazy(func() *code {
return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, len(e.Args)}}
})()
if len(e.Args) > 0 {
type varIndex struct {
name string
index [2]int
}
vis := make([]varIndex, 0, len(e.Args))
v := c.newVariable()
c.append(&code{op: opstore, v: v})
for _, arg := range e.Args {
if arg[0] == '$' {
c.appendCodeInfo(arg[1:])
w := c.createVariable(arg[1:])
c.append(&code{op: opstore, v: w})
vis = append(vis, varIndex{arg, w})
} else {
c.appendCodeInfo(arg)
c.append(&code{op: opstore, v: c.createVariable(arg)})
}
}
for _, w := range vis {
c.append(&code{op: opload, v: v})
c.append(&code{op: opexpbegin})
c.append(&code{op: opload, v: w.index})
c.append(&code{op: opcallpc})
c.appendCodeInfo(w.name)
c.append(&code{op: opstore, v: c.pushVariable(w.name)})
c.append(&code{op: opexpend})
}
c.append(&code{op: opload, v: v})
}
if err := c.compile(e.Body); err != nil {
return err
}
c.appendCodeInfo("end of " + e.Name)
return nil
}
func (c *compiler) compileQuery(e *Query) error {
for _, fd := range e.FuncDefs {
if err := c.compileFuncDef(fd, false); err != nil {
return err
}
}
if e.Term != nil {
return c.compileTerm(e.Term)
}
switch e.Op {
case Operator(0):
return errors.New(`missing query (try ".")`)
case OpPipe:
if len(e.Patterns) > 0 {
return c.compileBind(e.Left, e.Right, e.Patterns)
}
if err := c.compileQuery(e.Left); err != nil {
return err
}
return c.compileQuery(e.Right)
case OpComma:
return c.compileComma(e.Left, e.Right)
case OpAlt:
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)
case OpOr:
return c.compileIf(
&If{
Cond: e.Left,
Then: &Query{Term: &Term{Type: TermTypeTrue}},
Else: &Query{Term: &Term{Type: TermTypeIf, If: &If{
Cond: e.Right,
Then: &Query{Term: &Term{Type: TermTypeTrue}},
Else: &Query{Term: &Term{Type: TermTypeFalse}},
}}},
},
)
case OpAnd:
return c.compileIf(
&If{
Cond: e.Left,
Then: &Query{Term: &Term{Type: TermTypeIf, If: &If{
Cond: e.Right,
Then: &Query{Term: &Term{Type: TermTypeTrue}},
Else: &Query{Term: &Term{Type: TermTypeFalse}},
}}},
Else: &Query{Term: &Term{Type: TermTypeFalse}},
},
)
default:
return c.compileCall(
e.Op.getFunc(),
[]*Query{e.Left, e.Right},
)
}
}
func (c *compiler) compileComma(l, r *Query) error {
setfork := c.lazy(func() *code {
return &code{op: opfork, v: len(c.codes)}
})
if err := c.compileQuery(l); err != nil {
return err
}
defer c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})()
setfork()
return c.compileQuery(r)
}
func (c *compiler) compileAlt(l, r *Query) error {
c.append(&code{op: oppush, v: false})
found := c.newVariable()
c.append(&code{op: opstore, v: found})
setfork := c.lazy(func() *code {
return &code{op: opfork, v: len(c.codes)} // opload found
})
if err := c.compileQuery(l); err != nil {
return err
}
c.append(&code{op: opdup})
c.append(&code{op: opjumpifnot, v: len(c.codes) + 4}) // oppop
c.append(&code{op: oppush, v: true}) // found some value
c.append(&code{op: opstore, v: found})
defer c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})()
c.append(&code{op: oppop})
c.append(&code{op: opbacktrack})
setfork()
c.append(&code{op: opload, v: found})
c.append(&code{op: opjumpifnot, v: len(c.codes) + 3})
c.append(&code{op: opbacktrack}) // if found, backtrack
c.append(&code{op: oppop})
return c.compileQuery(r)
}
func (c *compiler) compileQueryUpdate(l, r *Query, op Operator) error {
switch op {
case OpAssign:
// optimize assignment operator with constant indexing and slicing
// .foo.[0].[1:2] = f => setpath(["foo",0,{"start":1,"end":2}]; f)
if xs := l.toIndices(nil); xs != nil {
// ref: compileCall
v := c.newVariable()
c.append(&code{op: opstore, v: v})
c.append(&code{op: opload, v: v})
if err := c.compileQuery(r); err != nil {
return err
}
c.append(&code{op: oppush, v: xs})
c.append(&code{op: opload, v: v})
c.append(&code{op: opcall, v: [3]any{internalFuncs["setpath"].callback, 2, "setpath"}})
return nil
}
fallthrough
case OpModify:
return c.compileFunc(
&Func{
Name: op.getFunc(),
Args: []*Query{l, r},
},
)
default:
name := "$%0"
c.append(&code{op: opdup})
if err := c.compileQuery(r); err != nil {
return err
}
c.append(&code{op: opstore, v: c.pushVariable(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}}},
},
},
}},
},
},
)
}
}
func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
defer c.newScopeDepth()()
c.append(&code{op: opdup})
c.append(&code{op: opexpbegin})
if err := c.compileQuery(l); err != nil {
return err
}
var pc int
var vs [][2]int
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}
})()
}
if 0 < i {
for _, v := range vs {
c.append(&code{op: oppush, v: nil})
c.append(&code{op: opstore, v: v})
}
}
if vs, err = c.compilePattern(vs[:0], p); err != nil {
return err
}
if i < len(patterns)-1 {
defer c.lazy(func() *code {
return &code{op: opjump, v: pc}
})()
pcc = len(c.codes)
}
}
if len(patterns) > 1 {
pc = len(c.codes)
}
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) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) {
var err error
c.appendCodeInfo(p)
if p.Name != "" {
v := c.pushVariable(p.Name)
c.append(&code{op: opstore, v: v})
return append(vs, v), nil
} else if len(p.Array) > 0 {
v := c.newVariable()
c.append(&code{op: opstore, v: v})
for i, p := range p.Array {
c.append(&code{op: opload, v: v})
c.append(&code{op: opindexarray, v: i})
if vs, err = c.compilePattern(vs, p); err != nil {
return nil, err
}
}
return vs, nil
} else if len(p.Object) > 0 {
v := c.newVariable()
c.append(&code{op: opstore, v: v})
for _, kv := range p.Object {
var key, name string
c.append(&code{op: opload, v: v})
if key = kv.Key; key != "" {
if key[0] == '$' {
key, name = key[1:], key
}
} else if kv.KeyString != nil {
if key = kv.KeyString.Str; key == "" {
if err := c.compileString(kv.KeyString, nil); err != nil {
return nil, err
}
}
} else if kv.KeyQuery != nil {
if err := c.compileQuery(kv.KeyQuery); err != nil {
return nil, err
}
}
if key != "" {
c.append(&code{op: opindex, v: key})
} else {
c.append(&code{op: opload, v: v})
c.append(&code{op: oppush, v: nil})
// ref: compileCall
c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}})
}
if name != "" {
if kv.Val != nil {
c.append(&code{op: opdup})
}
if vs, err = c.compilePattern(vs, &Pattern{Name: name}); err != nil {
return nil, err
}
}
if kv.Val != nil {
if vs, err = c.compilePattern(vs, kv.Val); err != nil {
return nil, err
}
}
}
return vs, nil
} else {
return nil, fmt.Errorf("invalid pattern: %s", p)
}
}
func (c *compiler) compileIf(e *If) error {
c.appendCodeInfo(e)
c.append(&code{op: opdup}) // duplicate the value for then or else clause
c.append(&code{op: opexpbegin})
pc := len(c.codes)
f := c.newScopeDepth()
if err := c.compileQuery(e.Cond); err != nil {
return err
}
f()
if pc == len(c.codes) {
c.codes = c.codes[:pc-1]
} else {
c.append(&code{op: opexpend})
}
pcc := len(c.codes)
setjumpifnot := c.lazy(func() *code {
return &code{op: opjumpifnot, v: len(c.codes)} // skip then clause
})
f = c.newScopeDepth()
if err := c.compileQuery(e.Then); err != nil {
return err
}
f()
defer c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})()
setjumpifnot()
if len(e.Elif) > 0 {
return c.compileIf(&If{e.Elif[0].Cond, e.Elif[0].Then, e.Elif[1:], e.Else})
}
if e.Else != nil {
defer c.newScopeDepth()()
defer func() {
// optimize constant results
// opdup, ..., opjumpifnot, opconst, opjump, opconst
// => opnop, ..., opjumpifnot, oppush, opjump, oppush
if pcc+4 == len(c.codes) &&
c.codes[pcc+1] != nil && c.codes[pcc+1].op == opconst &&
c.codes[pcc+3] != nil && c.codes[pcc+3].op == opconst {
c.codes[pc-2].op = opnop
c.codes[pcc+1].op = oppush
c.codes[pcc+3].op = oppush
}
}()
return c.compileQuery(e.Else)
}
return nil
}
func (c *compiler) compileTry(e *Try) error {
c.appendCodeInfo(e)
setforktrybegin := c.lazy(func() *code {
return &code{op: opforktrybegin, v: len(c.codes)}
})
f := c.newScopeDepth()
if err := c.compileQuery(e.Body); err != nil {
return err
}
f()
c.append(&code{op: opforktryend})
defer c.lazy(func() *code {
return &code{op: opjump, v: len(c.codes)}
})()
setforktrybegin()
if e.Catch != nil {
defer c.newScopeDepth()()
return c.compileQuery(e.Catch)
}
c.append(&code{op: opbacktrack})
return nil
}
func (c *compiler) compileReduce(e *Reduce) error {
c.appendCodeInfo(e)
defer c.newScopeDepth()()
c.append(&code{op: opdup})
v := c.newVariable()
f := c.newScopeDepth()
if err := c.compileQuery(e.Start); err != nil {
return err
}
f()
c.append(&code{op: opstore, v: v})
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.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})
return nil
}
func (c *compiler) compileForeach(e *Foreach) error {
c.appendCodeInfo(e)
defer c.newScopeDepth()()
c.append(&code{op: opdup})
v := c.newVariable()
f := c.newScopeDepth()
if err := c.compileQuery(e.Start); err != nil {
return err
}
f()
c.append(&code{op: opstore, v: v})
if err := c.compileQuery(e.Query); err != nil {
return err
}
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 {
c.appendCodeInfo(e)
v := c.pushVariable("$%" + e.Ident[1:])
c.append(&code{op: opforklabel, v: v})
return c.compileQuery(e.Body)
}
func (c *compiler) compileBreak(label string) error {
v, err := c.lookupVariable("$%" + label[1:])
if err != nil {
return &breakError{label, nil}
}
c.append(&code{op: oppop})
c.append(&code{op: opload, v: v})
c.append(&code{op: opcall, v: [3]any{funcBreak(label), 0, "_break"}})
return nil
}
func funcBreak(label string) func(any, []any) any {
return func(v any, _ []any) any {
return &breakError{label, v}
}
}
func (c *compiler) compileTerm(e *Term) error {
if len(e.SuffixList) > 0 {
s := e.SuffixList[len(e.SuffixList)-1]
t := *e // clone without changing e
t.SuffixList = t.SuffixList[:len(e.SuffixList)-1]
return c.compileTermSuffix(&t, s)
}
switch e.Type {
case TermTypeIdentity:
return nil
case TermTypeRecurse:
return c.compileFunc(&Func{Name: "recurse"})
case TermTypeNull:
c.append(&code{op: opconst, v: nil})
return nil
case TermTypeTrue:
c.append(&code{op: opconst, v: true})
return nil
case TermTypeFalse:
c.append(&code{op: opconst, v: false})
return nil
case TermTypeIndex:
return c.compileIndex(&Term{Type: TermTypeIdentity}, e.Index)
case TermTypeFunc:
return c.compileFunc(e.Func)
case TermTypeObject:
return c.compileObject(e.Object)
case TermTypeArray:
return c.compileArray(e.Array)
case TermTypeNumber:
c.append(&code{op: opconst, v: toNumber(e.Number)})
return nil
case TermTypeUnary:
return c.compileUnary(e.Unary)
case TermTypeFormat:
return c.compileFormat(e.Format, e.Str)
case TermTypeString:
return c.compileString(e.Str, nil)
case TermTypeIf:
return c.compileIf(e.If)
case TermTypeTry:
return c.compileTry(e.Try)
case TermTypeReduce:
return c.compileReduce(e.Reduce)
case TermTypeForeach:
return c.compileForeach(e.Foreach)
case TermTypeLabel:
return c.compileLabel(e.Label)
case TermTypeBreak:
return c.compileBreak(e.Break)
case TermTypeQuery:
defer c.newScopeDepth()()
return c.compileQuery(e.Query)
default:
panic("invalid term: " + e.String())
}
}
func (c *compiler) compileIndex(e *Term, x *Index) error {
if k := x.toIndexKey(); k != nil {
if err := c.compileTerm(e); err != nil {
return err
}
c.appendCodeInfo(x)
c.append(&code{op: opindex, v: k})
return nil
}
c.appendCodeInfo(x)
if x.Str != nil {
return c.compileCall("_index", []*Query{{Term: e}, {Term: &Term{Type: TermTypeString, Str: x.Str}}})
}
if !x.IsSlice {
return c.compileCall("_index", []*Query{{Term: e}, x.Start})
}
if x.Start == nil {
return c.compileCall("_slice", []*Query{{Term: e}, x.End, {Term: &Term{Type: TermTypeNull}}})
}
if x.End == nil {
return c.compileCall("_slice", []*Query{{Term: e}, {Term: &Term{Type: TermTypeNull}}, x.Start})
}
return c.compileCall("_slice", []*Query{{Term: e}, x.End, x.Start})
}
func (c *compiler) compileFunc(e *Func) error {
if len(e.Args) == 0 {
if f, v := c.lookupFuncOrVariable(e.Name); f != nil {
return c.compileCallPc(f, e.Args)
} else if v != nil {
if e.Name[0] == '$' {
c.append(&code{op: oppop})
c.append(&code{op: opload, v: v.index})
} else {
c.append(&code{op: opload, v: v.index})
c.append(&code{op: opcallpc})
}
return nil
} else if e.Name == "$ENV" || e.Name == "env" {
env := make(map[string]any)
if c.environLoader != nil {
for _, kv := range c.environLoader() {
if k, v, ok := strings.Cut(kv, "="); ok && k != "" {
env[k] = v
}
}
}
c.append(&code{op: opconst, v: env})
return nil
} else if e.Name[0] == '$' {
return &variableNotFoundError{e.Name}
}
} else {
for i := len(c.scopes) - 1; i >= 0; i-- {
s := c.scopes[i]
for j := len(s.funcs) - 1; j >= 0; j-- {
if f := s.funcs[j]; f.name == e.Name && f.argcnt == len(e.Args) {
return c.compileCallPc(f, e.Args)
}
}
}
}
if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil {
return c.compileCallPc(f, e.Args)
}
if fds, ok := builtinFuncDefs[e.Name]; ok {
var compiled bool
for _, fd := range fds {
if len(fd.Args) == len(e.Args) {
if err := c.compileFuncDef(fd, true); err != nil {
return err
}
compiled = true
break
}
}
if !compiled {
switch e.Name {
case "_assign":
c.compileAssign()
case "_modify":
c.compileModify()
case "_last":
c.compileLast()
}
}
if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil {
return c.compileCallPc(f, e.Args)
}
}
if fn, ok := internalFuncs[e.Name]; ok && fn.accept(len(e.Args)) {
switch e.Name {
case "empty":
c.append(&code{op: opbacktrack})
return nil
case "path":
c.append(&code{op: oppathbegin})
if err := c.compileCall(e.Name, e.Args); err != nil {
return err
}
c.codes[len(c.codes)-1] = &code{op: oppathend}
return nil
case "builtins":
return c.compileCallInternal(
[3]any{c.funcBuiltins, 0, e.Name},
e.Args,
true,
-1,
)
case "input":
if c.inputIter == nil {
return &inputNotAllowedError{}
}
return c.compileCallInternal(
[3]any{c.funcInput, 0, e.Name},
e.Args,
true,
-1,
)
case "modulemeta":
return c.compileCallInternal(
[3]any{c.funcModulemeta, 0, e.Name},
e.Args,
true,
-1,
)
case "debug":
setfork := c.lazy(func() *code {
return &code{op: opfork, v: len(c.codes)}
})
if err := c.compileQuery(e.Args[0]); err != nil {
return err
}
if err := c.compileFunc(&Func{Name: "debug"}); err != nil {
if _, ok := err.(*funcNotFoundError); ok {
err = &funcNotFoundError{e}
}
return err
}
c.append(&code{op: opbacktrack})
setfork()
return nil
case "_match":
return c.compileCallInternal(
[3]any{c.funcMatch, len(e.Args), e.Name},
e.Args,
true,
-1,
)
default:
return c.compileCall(e.Name, e.Args)
}
}
if fn, ok := c.customFuncs[e.Name]; ok && fn.accept(len(e.Args)) {
if err := c.compileCallInternal(
[3]any{fn.callback, len(e.Args), e.Name},
e.Args,
true,
-1,
); err != nil {
return err
}
if fn.iter {
c.append(&code{op: opiter})
}
return nil
}
return &funcNotFoundError{e}
}
// Appends the compiled code for the assignment operator (`=`) to maximize
// performance. Originally the operator was implemented as follows.
//
// def _assign(p; $x): reduce path(p) as $q (.; setpath($q; $x));
//
// To overcome the difficulty of reducing allocations on `setpath`, we use the
// `allocator` type and track the allocated addresses during the reduction.
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, 2}},
&code{op: opstore, v: v}, // def _assign(p; $x):
&code{op: opstore, v: p},
&code{op: opstore, v: x},
&code{op: opload, v: v},
&code{op: opexpbegin},
&code{op: opload, v: x},
&code{op: opcallpc},
&code{op: opstore, v: x},
&code{op: opexpend},
&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) + 30}, // reduce [L1]
&code{op: opdup},
&code{op: opstore, v: w},
&code{op: oppathbegin}, // path(p)
&code{op: opload, v: p},
&code{op: opcallpc},
&code{op: opload, v: w},
&code{op: oppathend},
&code{op: opstore, v: q}, // as $q (.;
&code{op: opload, v: a}, // setpath($q; $x)
&code{op: opload, v: x},
&code{op: opload, v: q},
&code{op: opload, v: w},
&code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}},
&code{op: opstore, v: w},
&code{op: opbacktrack}, // );
&code{op: oppop}, // [L1]
&code{op: opload, v: w},
&code{op: opret},
)
}
// Appends the compiled code for the update-assignment operator (`|=`) to
// maximize performance. We use the `allocator` type, just like `_assign/2`.
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, 2}},
&code{op: opstore, v: v}, // def _modify(p; f):
&code{op: opstore, v: p},
&code{op: opstore, v: f},
&code{op: oppush, v: []any{}},
&code{op: opstore, v: d},
&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) + 39}, // reduce [L1]
&code{op: oppathbegin}, // path(p)
&code{op: opload, v: p},
&code{op: opcallpc},
&code{op: opload, v: v},
&code{op: oppathend},
&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) + 36}, // [L2]
&code{op: oppop}, // (getpath($p) |
&code{op: opload, v: a},
&code{op: opload, v: p},
&code{op: opload, v: v},
&code{op: opcall, v: [3]any{internalFuncs["getpath"].callback, 1, "getpath"}},
&code{op: opload, v: f}, // f)
&code{op: opcallpc},
&code{op: opload, v: p}, // setpath($p; ...)
&code{op: opload, v: v},
&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) + 34}, // [L4]
&code{op: opjump, v: len(c.codes) + 38}, // [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]
&code{op: opappend, v: d}, //
&code{op: opbacktrack}, // ) | [L3]
&code{op: oppop}, // delpaths($d); [L1]
&code{op: opload, v: a},
&code{op: opload, v: d},
&code{op: opload, v: v},
&code{op: opcall, v: [3]any{funcDelpathsWithAllocator, 2, "_delpaths"}},
&code{op: opret},
)
}
// Appends the compiled code for the `last/1` function to
// maximize performance avoiding unnecessary boxing.
func (c *compiler) compileLast() {
defer c.appendBuiltin("_last", 1)()
scope := c.newScope()
v, g, x := [2]int{scope.id, 0}, [2]int{scope.id, 1}, [2]int{scope.id, 2}
c.appends(
&code{op: opscope, v: [3]int{scope.id, 3, 1}},
&code{op: opstore, v: v},
&code{op: opstore, v: g},
&code{op: oppush, v: true}, // $x = true
&code{op: opstore, v: x},
&code{op: opload, v: v},
&code{op: opfork, v: len(c.codes) + 13}, // reduce [L1]
&code{op: opload, v: g}, // g
&code{op: opcallpc},
&code{op: opstore, v: v}, // as $v (
&code{op: oppush, v: false}, // $x = false
&code{op: opstore, v: x},
&code{op: opbacktrack}, // );
&code{op: oppop}, // [L1]
&code{op: opload, v: x}, // if $x then $v else empty end
&code{op: opjumpifnot, v: len(c.codes) + 17},
&code{op: opbacktrack},
&code{op: opload, v: v},
&code{op: opret},
)
}
func (c *compiler) funcBuiltins(any, []any) any {
type funcNameArity struct {
name string
arity int
}
var xs []*funcNameArity
for _, fds := range builtinFuncDefs {
for _, fd := range fds {
if fd.Name[0] != '_' {
xs = append(xs, &funcNameArity{fd.Name, len(fd.Args)})
}
}
}
for name, fn := range internalFuncs {
if name[0] != '_' {
for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 {
if cnt&1 > 0 {
xs = append(xs, &funcNameArity{name, i})
}
}
}
}
for name, fn := range c.customFuncs {
if name[0] != '_' {
for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 {
if cnt&1 > 0 {
xs = append(xs, &funcNameArity{name, i})
}
}
}
}
sort.Slice(xs, func(i, j int) bool {
return xs[i].name < xs[j].name ||
xs[i].name == xs[j].name && xs[i].arity < xs[j].arity
})
ys := make([]any, len(xs))
for i, x := range xs {
ys[i] = x.name + "/" + strconv.Itoa(x.arity)
}
return ys
}
func (c *compiler) funcInput(any, []any) any {
v, ok := c.inputIter.Next()
if !ok {
return errors.New("break")
}
return v
}
func (c *compiler) funcModulemeta(v any, _ []any) any {
s, ok := v.(string)
if !ok {
return &func0TypeError{"modulemeta", v}
}
if c.moduleLoader == nil {
return fmt.Errorf("cannot load module: %q", s)
}
var q *Query
var err error
if moduleLoader, ok := c.moduleLoader.(interface {
LoadModuleWithMeta(string, map[string]any) (*Query, error)
}); ok {
if q, err = moduleLoader.LoadModuleWithMeta(s, nil); err != nil {
return err
}
} else if moduleLoader, ok := c.moduleLoader.(interface {
LoadModule(string) (*Query, error)
}); ok {
if q, err = moduleLoader.LoadModule(s); err != nil {
return err
}
}
meta := q.Meta.ToValue()
if meta == nil {
meta = make(map[string]any)
}
meta["defs"] = listModuleDefs(q)
meta["deps"] = listModuleDeps(q)
return meta
}
func listModuleDefs(q *Query) []any {
type funcNameArity struct {
name string
arity int
}
var xs []*funcNameArity
for _, fd := range q.FuncDefs {
if fd.Name[0] != '_' {
xs = append(xs, &funcNameArity{fd.Name, len(fd.Args)})
}
}
sort.Slice(xs, func(i, j int) bool {
return xs[i].name < xs[j].name ||
xs[i].name == xs[j].name && xs[i].arity < xs[j].arity
})
defs := make([]any, len(xs))
for i, x := range xs {
defs[i] = x.name + "/" + strconv.Itoa(x.arity)
}
return defs
}
func listModuleDeps(q *Query) []any {
deps := make([]any, len(q.Imports))
for j, i := range q.Imports {
v := i.Meta.ToValue()
if v == nil {
v = make(map[string]any)
}
relpath := i.ImportPath
if relpath == "" {
relpath = i.IncludePath
}
v["relpath"] = relpath
if i.ImportAlias != "" {
v["as"] = strings.TrimPrefix(i.ImportAlias, "$")
}
v["is_data"] = strings.HasPrefix(i.ImportAlias, "$")
deps[j] = v
}
return deps
}
func (c *compiler) funcMatch(v any, args []any) any {
return funcMatch(v, args[0], args[1], args[2], &c.regexpCache)
}
func (c *compiler) compileObject(e *Object) error {
c.appendCodeInfo(e)
if len(e.KeyVals) == 0 {
c.append(&code{op: opconst, v: map[string]any{}})
return nil
}
defer c.newScopeDepth()()
v := c.newVariable()
c.append(&code{op: opstore, v: v})
pc := len(c.codes)
for _, kv := range e.KeyVals {
if err := c.compileObjectKeyVal(v, kv); err != nil {
return err
}
}
c.append(&code{op: opobject, v: len(e.KeyVals)})
// optimize constant objects
l := len(e.KeyVals)
if pc+l*3+1 != len(c.codes) {
return nil
}
for i := range l {
if c.codes[pc+i*3].op != oppush ||
c.codes[pc+i*3+1].op != opload ||
c.codes[pc+i*3+2].op != opconst {
return nil
}
}
w := make(map[string]any, l)
for i := range l {
w[c.codes[pc+i*3].v.(string)] = c.codes[pc+i*3+2].v
}
c.codes[pc-1] = &code{op: opconst, v: w}
c.codes = c.codes[:pc]
return nil
}
func (c *compiler) compileObjectKeyVal(v [2]int, kv *ObjectKeyVal) error {
if key := kv.Key; key != "" {
if key[0] == '$' {
if kv.Val == nil { // {$foo} == {foo:$foo}
c.append(&code{op: oppush, v: key[1:]})
}
c.append(&code{op: opload, v: v})
if err := c.compileFunc(&Func{Name: key}); err != nil {
return err
}
} else {
c.append(&code{op: oppush, v: key})
if kv.Val == nil { // {foo} == {foo:.foo}
c.append(&code{op: opload, v: v})
c.append(&code{op: opindex, v: key})
}
}
} else if key := kv.KeyString; key != nil {
if key.Queries == nil {
c.append(&code{op: oppush, v: key.Str})
if kv.Val == nil { // {"foo"} == {"foo":.["foo"]}
c.append(&code{op: opload, v: v})
c.append(&code{op: opindex, v: key.Str})
}
} else {
c.append(&code{op: opload, v: v})
if err := c.compileString(key, nil); err != nil {
return err
}
if kv.Val == nil {
c.append(&code{op: opdup})
c.append(&code{op: opload, v: v})
c.append(&code{op: oppush, v: nil})
// ref: compileCall
c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}})
}
}
} else if kv.KeyQuery != nil {
c.append(&code{op: opload, v: v})
f := c.newScopeDepth()
if err := c.compileQuery(kv.KeyQuery); err != nil {
return err
}
f()
}
if kv.Val != nil {
c.append(&code{op: opload, v: v})
if err := c.compileQuery(kv.Val); err != nil {
return err
}
}
return nil
}
func (c *compiler) compileArray(e *Array) error {
c.appendCodeInfo(e)
if e.Query == nil {
c.append(&code{op: opconst, v: []any{}})
return nil
}
c.append(&code{op: oppush, v: []any{}})
arr := c.newVariable()
c.append(&code{op: opstore, v: arr})
pc := len(c.codes)
setfork := c.lazy(func() *code {
return &code{op: opfork, v: len(c.codes)}
})
defer c.newScopeDepth()()
if err := c.compileQuery(e.Query); err != nil {
return err
}
c.append(&code{op: opappend, v: arr})
c.append(&code{op: opbacktrack})
setfork()
c.append(&code{op: oppop})
c.append(&code{op: opload, v: arr})
if e.Query.Op == OpPipe {
return nil
}
// optimize constant arrays
if (len(c.codes)-pc)%3 != 0 {
return nil
}
l := (len(c.codes) - pc - 3) / 3
for i := range l {
if c.codes[pc+i].op != opfork ||
c.codes[pc+i*2+l].op != opconst ||
(i < l-1 && c.codes[pc+i*2+l+1].op != opjump) {
return nil
}
}
v := make([]any, l)
for i := range l {
v[i] = c.codes[pc+i*2+l].v
}
c.codes[pc-2] = &code{op: opconst, v: v}
c.codes = c.codes[:pc-1]
return nil
}
func (c *compiler) compileUnary(e *Unary) error {
c.appendCodeInfo(e)
if v := e.toNumber(); v != nil {
c.append(&code{op: opconst, v: v})
return nil
}
if err := c.compileTerm(e.Term); err != nil {
return err
}
switch e.Op {
case OpAdd:
return c.compileCall("_plus", nil)
case OpSub:
return c.compileCall("_negate", nil)
default:
return fmt.Errorf("unexpected operator in Unary: %s", e.Op)
}
}
func (c *compiler) compileFormat(format string, str *String) error {
f := formatToFunc(format)
if f == nil {
f = &Func{
Name: "format",
Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: format[1:]}}}},
}
}
if str == nil {
return c.compileFunc(f)
}
return c.compileString(str, f)
}
func formatToFunc(format string) *Func {
switch format {
case "@text":
return &Func{Name: "tostring"}
case "@json":
return &Func{Name: "tojson"}
case "@html":
return &Func{Name: "_tohtml"}
case "@uri":
return &Func{Name: "_touri"}
case "@urid":
return &Func{Name: "_tourid"}
case "@csv":
return &Func{Name: "_tocsv"}
case "@tsv":
return &Func{Name: "_totsv"}
case "@sh":
return &Func{Name: "_tosh"}
case "@base64":
return &Func{Name: "_tobase64"}
case "@base64d":
return &Func{Name: "_tobase64d"}
default:
return nil
}
}
func (c *compiler) compileString(s *String, f *Func) error {
if s.Queries == nil {
c.append(&code{op: opconst, v: s.Str})
return nil
}
if f == nil {
f = &Func{Name: "tostring"}
}
var q *Query
for _, e := range s.Queries {
if e.Term.Str == nil {
e = &Query{Left: e, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: f}}}
}
if q == nil {
q = e
} else {
q = &Query{Left: q, Op: OpAdd, Right: e}
}
}
return c.compileQuery(q)
}
func (c *compiler) compileTermSuffix(e *Term, s *Suffix) error {
if s.Index != nil {
return c.compileIndex(e, s.Index)
} else if s.Iter {
if err := c.compileTerm(e); err != nil {
return err
}
c.append(&code{op: opiter})
return nil
} else if s.Optional {
if len(e.SuffixList) > 0 {
if u := e.SuffixList[len(e.SuffixList)-1].toTerm(); u != nil {
// no need to clone (ref: compileTerm)
e.SuffixList = e.SuffixList[:len(e.SuffixList)-1]
if err := c.compileTerm(e); err != nil {
return err
}
e = u
}
}
return c.compileTry(&Try{Body: &Query{Term: e}})
} else {
return fmt.Errorf("invalid suffix: %s", s)
}
}
func (c *compiler) compileCall(name string, args []*Query) error {
fn := internalFuncs[name]
var indexing int
switch name {
case "_index", "_slice":
indexing = 1
case "getpath":
indexing = 0
default:
indexing = -1
}
if err := c.compileCallInternal(
[3]any{fn.callback, len(args), name},
args,
true,
indexing,
); err != nil {
return err
}
if fn.iter {
c.append(&code{op: opiter})
}
return nil
}
func (c *compiler) compileCallPc(fn *funcinfo, args []*Query) error {
return c.compileCallInternal(fn.pc, args, false, -1)
}
func (c *compiler) compileCallInternal(
fn any, args []*Query, internal bool, indexing int,
) error {
if len(args) == 0 {
c.append(&code{op: opcall, v: fn})
return nil
}
v := c.newVariable()
c.append(&code{op: opstore, v: v})
if indexing >= 0 {
c.append(&code{op: opexpbegin})
}
for i := len(args) - 1; i >= 0; i-- {
pc := len(c.codes) + 1 // skip opjump (ref: compileFuncDef)
name := "lambda:" + strconv.Itoa(pc)
if err := c.compileFuncDef(&FuncDef{Name: name, Body: args[i]}, false); err != nil {
return err
}
if internal {
switch len(c.codes) - pc {
case 2: // optimize identity argument (opscope, opret)
j := len(c.codes) - 3
c.codes[j] = &code{op: opload, v: v}
c.codes = c.codes[:j+1]
s := c.scopes[len(c.scopes)-1]
s.funcs = s.funcs[:len(s.funcs)-1]
c.deleteCodeInfo(name)
case 3: // optimize one instruction argument (opscope, opX, opret)
j := len(c.codes) - 4
if c.codes[j+2].op == opconst {
c.codes[j] = &code{op: oppush, v: c.codes[j+2].v}
c.codes = c.codes[:j+1]
} else {
c.codes[j] = &code{op: opload, v: v}
c.codes[j+1] = c.codes[j+2]
c.codes = c.codes[:j+2]
}
s := c.scopes[len(c.scopes)-1]
s.funcs = s.funcs[:len(s.funcs)-1]
c.deleteCodeInfo(name)
default:
c.append(&code{op: opload, v: v})
c.append(&code{op: oppushpc, v: pc})
c.append(&code{op: opcallpc})
}
} else {
c.append(&code{op: oppushpc, v: pc})
}
if i == indexing {
if c.codes[len(c.codes)-2].op == opexpbegin {
c.codes[len(c.codes)-2] = c.codes[len(c.codes)-1]
c.codes = c.codes[:len(c.codes)-1]
} else {
c.append(&code{op: opexpend})
}
}
}
if indexing > 0 {
c.append(&code{op: oppush, v: nil})
} else {
c.append(&code{op: opload, v: v})
}
c.append(&code{op: opcall, v: fn})
return nil
}
func (c *compiler) append(code *code) {
c.codes = append(c.codes, code)
}
func (c *compiler) appends(codes ...*code) {
c.codes = append(c.codes, codes...)
}
func (c *compiler) lazy(f func() *code) func() {
i := len(c.codes)
c.codes = append(c.codes, nil)
return func() { c.codes[i] = f() }
}
func (c *compiler) optimizeTailRec() {
var pcs []int
scopes := map[int]bool{}
L:
for i, code := range c.codes {
switch code.op {
case opscope:
pcs = append(pcs, i)
if v := code.v.([3]int); v[2] == 0 {
scopes[i] = v[1] == 0
}
case opcall:
var canjump bool
if j, ok := code.v.(int); !ok ||
len(pcs) == 0 || pcs[len(pcs)-1] != j {
break
} else if canjump, ok = scopes[j]; !ok {
break
}
for j := i + 1; j < len(c.codes); {
switch c.codes[j].op {
case opjump:
j = c.codes[j].v.(int)
case opret:
if canjump {
code.op = opjump
code.v = pcs[len(pcs)-1] + 1
} else {
code.op = opcallrec
}
fallthrough
default:
continue L
}
}
case opret:
if len(pcs) == 0 {
break L
}
pcs = pcs[:len(pcs)-1]
}
}
}
func (c *compiler) optimizeCodeOps() {
for i, next := len(c.codes)-1, (*code)(nil); i >= 0; i-- {
code := c.codes[i]
switch code.op {
case oppush, opdup, opload:
switch next.op {
case oppop:
code.op = opnop
next.op = opnop
case opconst:
code.op = opnop
next.op = oppush
}
case opjump, opjumpifnot:
if j := code.v.(int); j-1 == i {
code.op = opnop
} else if next = c.codes[j]; next.op == opjump {
code.v = next.v
}
}
next = code
}
}