mirror of https://github.com/deuill/grawkit.git
177 lines
4.6 KiB
Go
177 lines
4.6 KiB
Go
// The New...Execute API (allows you to efficiently execute the same program repeatedly).
|
|
|
|
package interp
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
|
|
"github.com/benhoyt/goawk/parser"
|
|
)
|
|
|
|
const checkContextOps = 1000 // for efficiency, only check context every N instructions
|
|
|
|
// Interpreter is an interpreter for a specific program, allowing you to
|
|
// efficiently execute the same program over and over with different inputs.
|
|
// Use New to create an Interpreter.
|
|
//
|
|
// Most programs won't need reusable execution, and should use the simpler
|
|
// Exec or ExecProgram functions instead.
|
|
type Interpreter struct {
|
|
interp *interp
|
|
}
|
|
|
|
// New creates a reusable interpreter for the given program.
|
|
//
|
|
// Most programs won't need reusable execution, and should use the simpler
|
|
// Exec or ExecProgram functions instead.
|
|
func New(program *parser.Program) (*Interpreter, error) {
|
|
p := newInterp(program)
|
|
return &Interpreter{interp: p}, nil
|
|
}
|
|
|
|
// Execute runs this program with the given execution configuration (input,
|
|
// output, and variables) and returns the exit status code of the program. A
|
|
// nil config is valid and will use the defaults (zero values).
|
|
//
|
|
// Internal memory allocations are reused, so calling Execute on the same
|
|
// Interpreter instance is significantly more efficient than calling
|
|
// ExecProgram multiple times.
|
|
//
|
|
// I/O state is reset between each run, but variables and the random number
|
|
// generator seed are not; use ResetVars and ResetRand to reset those.
|
|
//
|
|
// It's best to set config.Environ to a non-nil slice, otherwise Execute will
|
|
// call the relatively inefficient os.Environ each time. Set config.Environ to
|
|
// []string{} if the script doesn't need environment variables, or call
|
|
// os.Environ once and set config.Environ to that value each execution.
|
|
//
|
|
// Note that config.Funcs must be the same value provided to
|
|
// parser.ParseProgram, and must not change between calls to Execute.
|
|
func (p *Interpreter) Execute(config *Config) (int, error) {
|
|
p.interp.resetCore()
|
|
p.interp.checkCtx = false
|
|
|
|
err := p.interp.setExecuteConfig(config)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return p.interp.executeAll()
|
|
}
|
|
|
|
func (p *interp) resetCore() {
|
|
p.scanner = nil
|
|
for k := range p.scanners {
|
|
delete(p.scanners, k)
|
|
}
|
|
p.input = nil
|
|
for k := range p.inputStreams {
|
|
delete(p.inputStreams, k)
|
|
}
|
|
for k := range p.outputStreams {
|
|
delete(p.outputStreams, k)
|
|
}
|
|
for k := range p.commands {
|
|
delete(p.commands, k)
|
|
}
|
|
|
|
p.sp = 0
|
|
p.localArrays = p.localArrays[:0]
|
|
p.callDepth = 0
|
|
|
|
p.filename = null()
|
|
p.line = ""
|
|
p.lineIsTrueStr = false
|
|
p.lineNum = 0
|
|
p.fileLineNum = 0
|
|
p.fields = nil
|
|
p.fieldsIsTrueStr = nil
|
|
p.numFields = 0
|
|
p.haveFields = false
|
|
|
|
p.exitStatus = 0
|
|
}
|
|
|
|
func (p *interp) resetVars() {
|
|
// Reset global scalars
|
|
for i := range p.globals {
|
|
p.globals[i] = null()
|
|
}
|
|
|
|
// Reset global arrays
|
|
for _, array := range p.arrays {
|
|
for k := range array {
|
|
delete(array, k)
|
|
}
|
|
}
|
|
|
|
// Reset special variables
|
|
p.convertFormat = "%.6g"
|
|
p.outputFormat = "%.6g"
|
|
p.fieldSep = " "
|
|
p.fieldSepRegex = nil
|
|
p.recordSep = "\n"
|
|
p.recordSepRegex = nil
|
|
p.recordTerminator = ""
|
|
p.outputFieldSep = " "
|
|
p.outputRecordSep = "\n"
|
|
p.subscriptSep = "\x1c"
|
|
p.matchLength = 0
|
|
p.matchStart = 0
|
|
}
|
|
|
|
// ResetVars resets this interpreter's variables, setting scalar variables to
|
|
// null, clearing arrays, and resetting special variables such as FS and RS to
|
|
// their defaults.
|
|
func (p *Interpreter) ResetVars() {
|
|
p.interp.resetVars()
|
|
}
|
|
|
|
// ResetRand resets this interpreter's random number generator seed, so that
|
|
// rand() produces the same sequence it would have after calling New. This is
|
|
// a relatively CPU-intensive operation.
|
|
func (p *Interpreter) ResetRand() {
|
|
p.interp.randSeed = 1.0
|
|
p.interp.random.Seed(int64(math.Float64bits(p.interp.randSeed)))
|
|
}
|
|
|
|
// ExecuteContext is like Execute, but takes a context to allow the caller to
|
|
// set an execution timeout or cancel the execution. For efficiency, the
|
|
// context is only tested every 1000 virtual machine instructions.
|
|
//
|
|
// Context handling is not preemptive: currently long-running operations like
|
|
// system() won't be interrupted.
|
|
func (p *Interpreter) ExecuteContext(ctx context.Context, config *Config) (int, error) {
|
|
p.interp.resetCore()
|
|
p.interp.checkCtx = ctx != context.Background() && ctx != context.TODO()
|
|
p.interp.ctx = ctx
|
|
p.interp.ctxDone = ctx.Done()
|
|
p.interp.ctxOps = 0
|
|
|
|
err := p.interp.setExecuteConfig(config)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return p.interp.executeAll()
|
|
}
|
|
|
|
func (p *interp) checkContext() error {
|
|
p.ctxOps++
|
|
if p.ctxOps < checkContextOps {
|
|
return nil
|
|
}
|
|
p.ctxOps = 0
|
|
return p.checkContextNow()
|
|
}
|
|
|
|
func (p *interp) checkContextNow() error {
|
|
select {
|
|
case <-p.ctxDone:
|
|
return p.ctx.Err()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|