mirror of https://github.com/deuill/grawkit.git
1260 lines
28 KiB
Go
1260 lines
28 KiB
Go
// Virtual machine: interpret GoAWK compiled opcodes
|
|
|
|
package interp
|
|
|
|
import (
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/benhoyt/goawk/internal/ast"
|
|
"github.com/benhoyt/goawk/internal/compiler"
|
|
"github.com/benhoyt/goawk/lexer"
|
|
)
|
|
|
|
// Execute a block of virtual machine instructions.
|
|
//
|
|
// A big switch seems to be the best way of doing this for now. I also tried
|
|
// an array of functions (https://github.com/benhoyt/goawk/commit/8e04b069b621ff9b9456de57a35ff2fe335cf201)
|
|
// and it was ever so slightly faster, but the code was harder to work with
|
|
// and it won't be improved when Go gets faster switches via jump tables
|
|
// (https://go-review.googlesource.com/c/go/+/357330/).
|
|
//
|
|
// Additionally, I've made this version faster since the above test by
|
|
// reducing the number of opcodes (replacing a couple dozen Call* opcodes with
|
|
// a single CallBuiltin -- that probably pushed it below a switch binary tree
|
|
// branch threshold).
|
|
func (p *interp) execute(code []compiler.Opcode) error {
|
|
for ip := 0; ip < len(code); {
|
|
op := code[ip]
|
|
ip++
|
|
|
|
if p.checkCtx {
|
|
err := p.checkContext()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
switch op {
|
|
case compiler.Num:
|
|
index := code[ip]
|
|
ip++
|
|
p.push(num(p.nums[index]))
|
|
|
|
case compiler.Str:
|
|
index := code[ip]
|
|
ip++
|
|
p.push(str(p.strs[index]))
|
|
|
|
case compiler.Dupe:
|
|
v := p.peekTop()
|
|
p.push(v)
|
|
|
|
case compiler.Drop:
|
|
p.pop()
|
|
|
|
case compiler.Swap:
|
|
l, r := p.peekTwo()
|
|
p.replaceTwo(r, l)
|
|
|
|
case compiler.Field:
|
|
index := p.peekTop()
|
|
v := p.getField(int(index.num()))
|
|
p.replaceTop(v)
|
|
|
|
case compiler.FieldInt:
|
|
index := code[ip]
|
|
ip++
|
|
v := p.getField(int(index))
|
|
p.push(v)
|
|
|
|
case compiler.FieldByName:
|
|
fieldName := p.peekTop()
|
|
field, err := p.getFieldByName(p.toString(fieldName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.replaceTop(field)
|
|
|
|
case compiler.FieldByNameStr:
|
|
index := code[ip]
|
|
fieldName := p.strs[index]
|
|
ip++
|
|
field, err := p.getFieldByName(fieldName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.push(field)
|
|
|
|
case compiler.Global:
|
|
index := code[ip]
|
|
ip++
|
|
p.push(p.globals[index])
|
|
|
|
case compiler.Local:
|
|
index := code[ip]
|
|
ip++
|
|
p.push(p.frame[index])
|
|
|
|
case compiler.Special:
|
|
index := code[ip]
|
|
ip++
|
|
p.push(p.getSpecial(int(index)))
|
|
|
|
case compiler.ArrayGlobal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.arrays[arrayIndex]
|
|
index := p.toString(p.peekTop())
|
|
v := arrayGet(array, index)
|
|
p.replaceTop(v)
|
|
|
|
case compiler.ArrayLocal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.localArray(int(arrayIndex))
|
|
index := p.toString(p.peekTop())
|
|
v := arrayGet(array, index)
|
|
p.replaceTop(v)
|
|
|
|
case compiler.InGlobal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.arrays[arrayIndex]
|
|
index := p.toString(p.peekTop())
|
|
_, ok := array[index]
|
|
p.replaceTop(boolean(ok))
|
|
|
|
case compiler.InLocal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.localArray(int(arrayIndex))
|
|
index := p.toString(p.peekTop())
|
|
_, ok := array[index]
|
|
p.replaceTop(boolean(ok))
|
|
|
|
case compiler.AssignField:
|
|
right, index := p.popTwo()
|
|
err := p.setField(int(index.num()), p.toString(right))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.AssignGlobal:
|
|
index := code[ip]
|
|
ip++
|
|
p.globals[index] = p.pop()
|
|
|
|
case compiler.AssignLocal:
|
|
index := code[ip]
|
|
ip++
|
|
p.frame[index] = p.pop()
|
|
|
|
case compiler.AssignSpecial:
|
|
index := code[ip]
|
|
ip++
|
|
err := p.setSpecial(int(index), p.pop())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.AssignArrayGlobal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.arrays[arrayIndex]
|
|
v, index := p.popTwo()
|
|
array[p.toString(index)] = v
|
|
|
|
case compiler.AssignArrayLocal:
|
|
arrayIndex := code[ip]
|
|
ip++
|
|
array := p.localArray(int(arrayIndex))
|
|
v, index := p.popTwo()
|
|
array[p.toString(index)] = v
|
|
|
|
case compiler.Delete:
|
|
arrayScope := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.array(ast.VarScope(arrayScope), int(arrayIndex))
|
|
index := p.toString(p.pop())
|
|
delete(array, index)
|
|
|
|
case compiler.DeleteAll:
|
|
arrayScope := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.array(ast.VarScope(arrayScope), int(arrayIndex))
|
|
for k := range array {
|
|
delete(array, k)
|
|
}
|
|
|
|
case compiler.IncrField:
|
|
amount := code[ip]
|
|
ip++
|
|
index := int(p.pop().num())
|
|
v := p.getField(index)
|
|
err := p.setField(index, p.toString(num(v.num()+float64(amount))))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.IncrGlobal:
|
|
amount := code[ip]
|
|
index := code[ip+1]
|
|
ip += 2
|
|
p.globals[index] = num(p.globals[index].num() + float64(amount))
|
|
|
|
case compiler.IncrLocal:
|
|
amount := code[ip]
|
|
index := code[ip+1]
|
|
ip += 2
|
|
p.frame[index] = num(p.frame[index].num() + float64(amount))
|
|
|
|
case compiler.IncrSpecial:
|
|
amount := code[ip]
|
|
index := int(code[ip+1])
|
|
ip += 2
|
|
v := p.getSpecial(index)
|
|
err := p.setSpecial(index, num(v.num()+float64(amount)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.IncrArrayGlobal:
|
|
amount := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.arrays[arrayIndex]
|
|
index := p.toString(p.pop())
|
|
array[index] = num(array[index].num() + float64(amount))
|
|
|
|
case compiler.IncrArrayLocal:
|
|
amount := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.localArray(int(arrayIndex))
|
|
index := p.toString(p.pop())
|
|
array[index] = num(array[index].num() + float64(amount))
|
|
|
|
case compiler.AugAssignField:
|
|
operation := compiler.AugOp(code[ip])
|
|
ip++
|
|
right, indexVal := p.popTwo()
|
|
index := int(indexVal.num())
|
|
field := p.getField(index)
|
|
v, err := p.augAssignOp(operation, field, right)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = p.setField(index, p.toString(v))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.AugAssignGlobal:
|
|
operation := compiler.AugOp(code[ip])
|
|
index := code[ip+1]
|
|
ip += 2
|
|
v, err := p.augAssignOp(operation, p.globals[index], p.pop())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.globals[index] = v
|
|
|
|
case compiler.AugAssignLocal:
|
|
operation := compiler.AugOp(code[ip])
|
|
index := code[ip+1]
|
|
ip += 2
|
|
v, err := p.augAssignOp(operation, p.frame[index], p.pop())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.frame[index] = v
|
|
|
|
case compiler.AugAssignSpecial:
|
|
operation := compiler.AugOp(code[ip])
|
|
index := int(code[ip+1])
|
|
ip += 2
|
|
v, err := p.augAssignOp(operation, p.getSpecial(index), p.pop())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = p.setSpecial(index, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.AugAssignArrayGlobal:
|
|
operation := compiler.AugOp(code[ip])
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.arrays[arrayIndex]
|
|
index := p.toString(p.pop())
|
|
v, err := p.augAssignOp(operation, array[index], p.pop())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
array[index] = v
|
|
|
|
case compiler.AugAssignArrayLocal:
|
|
operation := compiler.AugOp(code[ip])
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
array := p.localArray(int(arrayIndex))
|
|
right, indexVal := p.popTwo()
|
|
index := p.toString(indexVal)
|
|
v, err := p.augAssignOp(operation, array[index], right)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
array[index] = v
|
|
|
|
case compiler.Regex:
|
|
// Stand-alone /regex/ is equivalent to: $0 ~ /regex/
|
|
index := code[ip]
|
|
ip++
|
|
re := p.regexes[index]
|
|
p.push(boolean(re.MatchString(p.line)))
|
|
|
|
case compiler.IndexMulti:
|
|
numValues := int(code[ip])
|
|
ip++
|
|
values := p.popSlice(numValues)
|
|
indices := make([]string, 0, 3) // up to 3-dimensional indices won't require heap allocation
|
|
for _, v := range values {
|
|
indices = append(indices, p.toString(v))
|
|
}
|
|
p.push(str(strings.Join(indices, p.subscriptSep)))
|
|
|
|
case compiler.Add:
|
|
l, r := p.peekPop()
|
|
p.replaceTop(num(l.num() + r.num()))
|
|
|
|
case compiler.Subtract:
|
|
l, r := p.peekPop()
|
|
p.replaceTop(num(l.num() - r.num()))
|
|
|
|
case compiler.Multiply:
|
|
l, r := p.peekPop()
|
|
p.replaceTop(num(l.num() * r.num()))
|
|
|
|
case compiler.Divide:
|
|
l, r := p.peekPop()
|
|
rf := r.num()
|
|
if rf == 0.0 {
|
|
return newError("division by zero")
|
|
}
|
|
p.replaceTop(num(l.num() / rf))
|
|
|
|
case compiler.Power:
|
|
l, r := p.peekPop()
|
|
p.replaceTop(num(math.Pow(l.num(), r.num())))
|
|
|
|
case compiler.Modulo:
|
|
l, r := p.peekPop()
|
|
rf := r.num()
|
|
if rf == 0.0 {
|
|
return newError("division by zero in mod")
|
|
}
|
|
p.replaceTop(num(math.Mod(l.num(), rf)))
|
|
|
|
case compiler.Equals:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) == p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln == rn))
|
|
}
|
|
|
|
case compiler.NotEquals:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) != p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln != rn))
|
|
}
|
|
|
|
case compiler.Less:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) < p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln < rn))
|
|
}
|
|
|
|
case compiler.Greater:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) > p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln > rn))
|
|
}
|
|
|
|
case compiler.LessOrEqual:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) <= p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln <= rn))
|
|
}
|
|
|
|
case compiler.GreaterOrEqual:
|
|
l, r := p.peekPop()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
if lIsStr || rIsStr {
|
|
p.replaceTop(boolean(p.toString(l) >= p.toString(r)))
|
|
} else {
|
|
p.replaceTop(boolean(ln >= rn))
|
|
}
|
|
|
|
case compiler.Concat:
|
|
l, r := p.peekPop()
|
|
p.replaceTop(str(p.toString(l) + p.toString(r)))
|
|
|
|
case compiler.ConcatMulti:
|
|
numValues := int(code[ip])
|
|
ip++
|
|
values := p.popSlice(numValues)
|
|
var sb strings.Builder
|
|
|
|
for _, v := range values {
|
|
sb.WriteString(p.toString(v))
|
|
}
|
|
p.push(str(sb.String()))
|
|
|
|
case compiler.Match:
|
|
l, r := p.peekPop()
|
|
re, err := p.compileRegex(p.toString(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
matched := re.MatchString(p.toString(l))
|
|
p.replaceTop(boolean(matched))
|
|
|
|
case compiler.NotMatch:
|
|
l, r := p.peekPop()
|
|
re, err := p.compileRegex(p.toString(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
matched := re.MatchString(p.toString(l))
|
|
p.replaceTop(boolean(!matched))
|
|
|
|
case compiler.Not:
|
|
p.replaceTop(boolean(!p.peekTop().boolean()))
|
|
|
|
case compiler.UnaryMinus:
|
|
p.replaceTop(num(-p.peekTop().num()))
|
|
|
|
case compiler.UnaryPlus:
|
|
p.replaceTop(num(p.peekTop().num()))
|
|
|
|
case compiler.Boolean:
|
|
p.replaceTop(boolean(p.peekTop().boolean()))
|
|
|
|
case compiler.Jump:
|
|
offset := code[ip]
|
|
ip += 1 + int(offset)
|
|
|
|
case compiler.JumpFalse:
|
|
offset := code[ip]
|
|
ip++
|
|
v := p.pop()
|
|
if !v.boolean() {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpTrue:
|
|
offset := code[ip]
|
|
ip++
|
|
v := p.pop()
|
|
if v.boolean() {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpEquals:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) == p.toString(r)
|
|
} else {
|
|
b = ln == rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpNotEquals:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) != p.toString(r)
|
|
} else {
|
|
b = ln != rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpLess:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) < p.toString(r)
|
|
} else {
|
|
b = ln < rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpGreater:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) > p.toString(r)
|
|
} else {
|
|
b = ln > rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpLessOrEqual:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) <= p.toString(r)
|
|
} else {
|
|
b = ln <= rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.JumpGreaterOrEqual:
|
|
offset := code[ip]
|
|
ip++
|
|
l, r := p.popTwo()
|
|
ln, lIsStr := l.isTrueStr()
|
|
rn, rIsStr := r.isTrueStr()
|
|
var b bool
|
|
if lIsStr || rIsStr {
|
|
b = p.toString(l) >= p.toString(r)
|
|
} else {
|
|
b = ln >= rn
|
|
}
|
|
if b {
|
|
ip += int(offset)
|
|
}
|
|
|
|
case compiler.Next:
|
|
return errNext
|
|
|
|
case compiler.Exit:
|
|
p.exitStatus = int(p.pop().num())
|
|
// Return special errExit value "caught" by top-level executor
|
|
return errExit
|
|
|
|
case compiler.ForIn:
|
|
varScope := code[ip]
|
|
varIndex := code[ip+1]
|
|
arrayScope := code[ip+2]
|
|
arrayIndex := code[ip+3]
|
|
offset := code[ip+4]
|
|
ip += 5
|
|
array := p.array(ast.VarScope(arrayScope), int(arrayIndex))
|
|
loopCode := code[ip : ip+int(offset)]
|
|
for index := range array {
|
|
switch ast.VarScope(varScope) {
|
|
case ast.ScopeGlobal:
|
|
p.globals[varIndex] = str(index)
|
|
case ast.ScopeLocal:
|
|
p.frame[varIndex] = str(index)
|
|
default: // ScopeSpecial
|
|
err := p.setSpecial(int(varIndex), str(index))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err := p.execute(loopCode)
|
|
if err == errBreak {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ip += int(offset)
|
|
|
|
case compiler.BreakForIn:
|
|
return errBreak
|
|
|
|
case compiler.CallBuiltin:
|
|
builtinOp := compiler.BuiltinOp(code[ip])
|
|
ip++
|
|
err := p.callBuiltin(builtinOp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.CallSplit:
|
|
arrayScope := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
s := p.toString(p.peekTop())
|
|
n, err := p.split(s, ast.VarScope(arrayScope), int(arrayIndex), p.fieldSep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.replaceTop(num(float64(n)))
|
|
|
|
case compiler.CallSplitSep:
|
|
arrayScope := code[ip]
|
|
arrayIndex := code[ip+1]
|
|
ip += 2
|
|
s, fieldSep := p.peekPop()
|
|
n, err := p.split(p.toString(s), ast.VarScope(arrayScope), int(arrayIndex), p.toString(fieldSep))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.replaceTop(num(float64(n)))
|
|
|
|
case compiler.CallSprintf:
|
|
numArgs := code[ip]
|
|
ip++
|
|
args := p.popSlice(int(numArgs))
|
|
s, err := p.sprintf(p.toString(args[0]), args[1:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.push(str(s))
|
|
|
|
case compiler.CallUser:
|
|
funcIndex := code[ip]
|
|
numArrayArgs := int(code[ip+1])
|
|
ip += 2
|
|
|
|
f := p.program.Compiled.Functions[funcIndex]
|
|
if p.callDepth >= maxCallDepth {
|
|
return newError("calling %q exceeded maximum call depth of %d", f.Name, maxCallDepth)
|
|
}
|
|
|
|
// Set up frame for scalar arguments
|
|
oldFrame := p.frame
|
|
p.frame = p.peekSlice(f.NumScalars)
|
|
|
|
// Handle array arguments
|
|
var arrays []int
|
|
for j := 0; j < numArrayArgs; j++ {
|
|
arrayScope := ast.VarScope(code[ip])
|
|
arrayIndex := int(code[ip+1])
|
|
ip += 2
|
|
arrays = append(arrays, p.arrayIndex(arrayScope, arrayIndex))
|
|
}
|
|
oldArraysLen := len(p.arrays)
|
|
for j := numArrayArgs; j < f.NumArrays; j++ {
|
|
arrays = append(arrays, len(p.arrays))
|
|
p.arrays = append(p.arrays, make(map[string]value))
|
|
}
|
|
p.localArrays = append(p.localArrays, arrays)
|
|
|
|
// Execute the function!
|
|
p.callDepth++
|
|
err := p.execute(f.Body)
|
|
p.callDepth--
|
|
|
|
// Pop the locals off the stack
|
|
p.popSlice(f.NumScalars)
|
|
p.frame = oldFrame
|
|
p.localArrays = p.localArrays[:len(p.localArrays)-1]
|
|
p.arrays = p.arrays[:oldArraysLen]
|
|
|
|
if r, ok := err.(returnValue); ok {
|
|
p.push(r.Value)
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
p.push(null())
|
|
}
|
|
|
|
case compiler.CallNative:
|
|
funcIndex := int(code[ip])
|
|
numArgs := int(code[ip+1])
|
|
ip += 2
|
|
|
|
args := p.popSlice(numArgs)
|
|
r, err := p.callNative(funcIndex, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.push(r)
|
|
|
|
case compiler.Return:
|
|
v := p.pop()
|
|
return returnValue{v}
|
|
|
|
case compiler.ReturnNull:
|
|
return returnValue{null()}
|
|
|
|
case compiler.Nulls:
|
|
numNulls := int(code[ip])
|
|
ip++
|
|
p.pushNulls(numNulls)
|
|
|
|
case compiler.Print:
|
|
numArgs := code[ip]
|
|
redirect := lexer.Token(code[ip+1])
|
|
ip += 2
|
|
|
|
args := p.popSlice(int(numArgs))
|
|
|
|
// Determine what output stream to write to.
|
|
output := p.output
|
|
if redirect != lexer.ILLEGAL {
|
|
var err error
|
|
dest := p.pop()
|
|
output, err = p.getOutputStream(redirect, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if numArgs > 0 {
|
|
err := p.printArgs(output, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// "print" with no arguments prints the raw value of $0,
|
|
// regardless of output mode.
|
|
err := p.printLine(output, p.line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case compiler.Printf:
|
|
numArgs := code[ip]
|
|
redirect := lexer.Token(code[ip+1])
|
|
ip += 2
|
|
|
|
args := p.popSlice(int(numArgs))
|
|
s, err := p.sprintf(p.toString(args[0]), args[1:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
output := p.output
|
|
if redirect != lexer.ILLEGAL {
|
|
dest := p.pop()
|
|
output, err = p.getOutputStream(redirect, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = writeOutput(output, s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case compiler.Getline:
|
|
redirect := lexer.Token(code[ip])
|
|
ip++
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == 1 {
|
|
p.setLine(line, false)
|
|
}
|
|
p.push(num(ret))
|
|
|
|
case compiler.GetlineField:
|
|
redirect := lexer.Token(code[ip])
|
|
ip++
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == 1 {
|
|
err := p.setField(0, line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
p.push(num(ret))
|
|
|
|
case compiler.GetlineGlobal:
|
|
redirect := lexer.Token(code[ip])
|
|
index := code[ip+1]
|
|
ip += 2
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == 1 {
|
|
p.globals[index] = numStr(line)
|
|
}
|
|
p.push(num(ret))
|
|
|
|
case compiler.GetlineLocal:
|
|
redirect := lexer.Token(code[ip])
|
|
index := code[ip+1]
|
|
ip += 2
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == 1 {
|
|
p.frame[index] = numStr(line)
|
|
}
|
|
p.push(num(ret))
|
|
|
|
case compiler.GetlineSpecial:
|
|
redirect := lexer.Token(code[ip])
|
|
index := code[ip+1]
|
|
ip += 2
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == 1 {
|
|
err := p.setSpecial(int(index), numStr(line))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
p.push(num(ret))
|
|
|
|
case compiler.GetlineArray:
|
|
redirect := lexer.Token(code[ip])
|
|
arrayScope := code[ip+1]
|
|
arrayIndex := code[ip+2]
|
|
ip += 3
|
|
|
|
ret, line, err := p.getline(redirect)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
index := p.toString(p.peekTop())
|
|
if ret == 1 {
|
|
array := p.array(ast.VarScope(arrayScope), int(arrayIndex))
|
|
array[index] = numStr(line)
|
|
}
|
|
p.replaceTop(num(ret))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *interp) callBuiltin(builtinOp compiler.BuiltinOp) error {
|
|
switch builtinOp {
|
|
case compiler.BuiltinAtan2:
|
|
y, x := p.peekPop()
|
|
p.replaceTop(num(math.Atan2(y.num(), x.num())))
|
|
|
|
case compiler.BuiltinClose:
|
|
name := p.toString(p.peekTop())
|
|
var c io.Closer = p.inputStreams[name]
|
|
if c != nil {
|
|
// Close input stream
|
|
delete(p.inputStreams, name)
|
|
err := c.Close()
|
|
if err != nil {
|
|
p.replaceTop(num(-1))
|
|
} else {
|
|
p.replaceTop(num(0))
|
|
}
|
|
} else {
|
|
c = p.outputStreams[name]
|
|
if c != nil {
|
|
// Close output stream
|
|
delete(p.outputStreams, name)
|
|
err := c.Close()
|
|
if err != nil {
|
|
p.replaceTop(num(-1))
|
|
} else {
|
|
p.replaceTop(num(0))
|
|
}
|
|
} else {
|
|
// Nothing to close
|
|
p.replaceTop(num(-1))
|
|
}
|
|
}
|
|
|
|
case compiler.BuiltinCos:
|
|
p.replaceTop(num(math.Cos(p.peekTop().num())))
|
|
|
|
case compiler.BuiltinExp:
|
|
p.replaceTop(num(math.Exp(p.peekTop().num())))
|
|
|
|
case compiler.BuiltinFflush:
|
|
name := p.toString(p.peekTop())
|
|
var ok bool
|
|
if name != "" {
|
|
// Flush a single, named output stream
|
|
ok = p.flushStream(name)
|
|
} else {
|
|
// fflush() or fflush("") flushes all output streams
|
|
ok = p.flushAll()
|
|
}
|
|
if !ok {
|
|
p.replaceTop(num(-1))
|
|
} else {
|
|
p.replaceTop(num(0))
|
|
}
|
|
|
|
case compiler.BuiltinFflushAll:
|
|
ok := p.flushAll()
|
|
if !ok {
|
|
p.push(num(-1))
|
|
} else {
|
|
p.push(num(0))
|
|
}
|
|
|
|
case compiler.BuiltinGsub:
|
|
regex, repl, in := p.peekPeekPop()
|
|
out, n, err := p.sub(p.toString(regex), p.toString(repl), p.toString(in), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.replaceTwo(num(float64(n)), str(out))
|
|
|
|
case compiler.BuiltinIndex:
|
|
sValue, substr := p.peekPop()
|
|
s := p.toString(sValue)
|
|
index := strings.Index(s, p.toString(substr))
|
|
p.replaceTop(num(float64(index + 1)))
|
|
|
|
case compiler.BuiltinInt:
|
|
p.replaceTop(num(float64(int(p.peekTop().num()))))
|
|
|
|
case compiler.BuiltinLength:
|
|
p.push(num(float64(len(p.line))))
|
|
|
|
case compiler.BuiltinLengthArg:
|
|
s := p.toString(p.peekTop())
|
|
p.replaceTop(num(float64(len(s))))
|
|
|
|
case compiler.BuiltinLog:
|
|
p.replaceTop(num(math.Log(p.peekTop().num())))
|
|
|
|
case compiler.BuiltinMatch:
|
|
sValue, regex := p.peekPop()
|
|
s := p.toString(sValue)
|
|
re, err := p.compileRegex(p.toString(regex))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
loc := re.FindStringIndex(s)
|
|
if loc == nil {
|
|
p.matchStart = 0
|
|
p.matchLength = -1
|
|
p.replaceTop(num(0))
|
|
} else {
|
|
p.matchStart = loc[0] + 1
|
|
p.matchLength = loc[1] - loc[0]
|
|
p.replaceTop(num(float64(p.matchStart)))
|
|
}
|
|
|
|
case compiler.BuiltinRand:
|
|
p.push(num(p.random.Float64()))
|
|
|
|
case compiler.BuiltinSin:
|
|
p.replaceTop(num(math.Sin(p.peekTop().num())))
|
|
|
|
case compiler.BuiltinSqrt:
|
|
p.replaceTop(num(math.Sqrt(p.peekTop().num())))
|
|
|
|
case compiler.BuiltinSrand:
|
|
prevSeed := p.randSeed
|
|
p.random.Seed(time.Now().UnixNano())
|
|
p.push(num(prevSeed))
|
|
|
|
case compiler.BuiltinSrandSeed:
|
|
prevSeed := p.randSeed
|
|
p.randSeed = p.peekTop().num()
|
|
p.random.Seed(int64(math.Float64bits(p.randSeed)))
|
|
p.replaceTop(num(prevSeed))
|
|
|
|
case compiler.BuiltinSub:
|
|
regex, repl, in := p.peekPeekPop()
|
|
out, n, err := p.sub(p.toString(regex), p.toString(repl), p.toString(in), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.replaceTwo(num(float64(n)), str(out))
|
|
|
|
case compiler.BuiltinSubstr:
|
|
sValue, posValue := p.peekPop()
|
|
pos := int(posValue.num())
|
|
s := p.toString(sValue)
|
|
if pos > len(s) {
|
|
pos = len(s) + 1
|
|
}
|
|
if pos < 1 {
|
|
pos = 1
|
|
}
|
|
length := len(s) - pos + 1
|
|
p.replaceTop(str(s[pos-1 : pos-1+length]))
|
|
|
|
case compiler.BuiltinSubstrLength:
|
|
posValue, lengthValue := p.popTwo()
|
|
length := int(lengthValue.num())
|
|
pos := int(posValue.num())
|
|
s := p.toString(p.peekTop())
|
|
if pos > len(s) {
|
|
pos = len(s) + 1
|
|
}
|
|
if pos < 1 {
|
|
pos = 1
|
|
}
|
|
maxLength := len(s) - pos + 1
|
|
if length < 0 {
|
|
length = 0
|
|
}
|
|
if length > maxLength {
|
|
length = maxLength
|
|
}
|
|
p.replaceTop(str(s[pos-1 : pos-1+length]))
|
|
|
|
case compiler.BuiltinSystem:
|
|
if p.noExec {
|
|
return newError("can't call system() due to NoExec")
|
|
}
|
|
cmdline := p.toString(p.peekTop())
|
|
cmd := p.execShell(cmdline)
|
|
cmd.Stdin = p.stdin
|
|
cmd.Stdout = p.output
|
|
cmd.Stderr = p.errorOutput
|
|
_ = p.flushAll() // ensure synchronization
|
|
err := cmd.Run()
|
|
ret := 0.0
|
|
if err != nil {
|
|
if p.checkCtx && p.ctx.Err() != nil {
|
|
return p.ctx.Err()
|
|
}
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
ret = float64(exitErr.ProcessState.ExitCode())
|
|
} else {
|
|
p.printErrorf("%v\n", err)
|
|
ret = -1
|
|
}
|
|
}
|
|
p.replaceTop(num(ret))
|
|
|
|
case compiler.BuiltinTolower:
|
|
p.replaceTop(str(strings.ToLower(p.toString(p.peekTop()))))
|
|
|
|
case compiler.BuiltinToupper:
|
|
p.replaceTop(str(strings.ToUpper(p.toString(p.peekTop()))))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Fetch the value at the given index from array. This handles the strange
|
|
// POSIX behavior of creating a null entry for non-existent array elements.
|
|
// Per the POSIX spec, "Any other reference to a nonexistent array element
|
|
// [apart from "in" expressions] shall automatically create it."
|
|
func arrayGet(array map[string]value, index string) value {
|
|
v, ok := array[index]
|
|
if !ok {
|
|
array[index] = v
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Stack operations follow. These should be inlined. Instead of just push and
|
|
// pop, for efficiency we have custom operations for when we're replacing the
|
|
// top of stack without changing the stack pointer. Primarily this avoids the
|
|
// check for append in push.
|
|
func (p *interp) push(v value) {
|
|
sp := p.sp
|
|
if sp >= len(p.stack) {
|
|
p.stack = append(p.stack, null())
|
|
}
|
|
p.stack[sp] = v
|
|
sp++
|
|
p.sp = sp
|
|
}
|
|
|
|
func (p *interp) pushNulls(num int) {
|
|
sp := p.sp
|
|
for p.sp+num-1 >= len(p.stack) {
|
|
p.stack = append(p.stack, null())
|
|
}
|
|
for i := 0; i < num; i++ {
|
|
p.stack[sp] = null()
|
|
sp++
|
|
}
|
|
p.sp = sp
|
|
}
|
|
|
|
func (p *interp) pop() value {
|
|
p.sp--
|
|
return p.stack[p.sp]
|
|
}
|
|
|
|
func (p *interp) popTwo() (value, value) {
|
|
p.sp -= 2
|
|
return p.stack[p.sp], p.stack[p.sp+1]
|
|
}
|
|
|
|
func (p *interp) peekTop() value {
|
|
return p.stack[p.sp-1]
|
|
}
|
|
|
|
func (p *interp) peekTwo() (value, value) {
|
|
return p.stack[p.sp-2], p.stack[p.sp-1]
|
|
}
|
|
|
|
func (p *interp) peekPop() (value, value) {
|
|
p.sp--
|
|
return p.stack[p.sp-1], p.stack[p.sp]
|
|
}
|
|
|
|
func (p *interp) peekPeekPop() (value, value, value) {
|
|
p.sp--
|
|
return p.stack[p.sp-2], p.stack[p.sp-1], p.stack[p.sp]
|
|
}
|
|
|
|
func (p *interp) replaceTop(v value) {
|
|
p.stack[p.sp-1] = v
|
|
}
|
|
|
|
func (p *interp) replaceTwo(l, r value) {
|
|
p.stack[p.sp-2] = l
|
|
p.stack[p.sp-1] = r
|
|
}
|
|
|
|
func (p *interp) popSlice(n int) []value {
|
|
p.sp -= n
|
|
return p.stack[p.sp : p.sp+n]
|
|
}
|
|
|
|
func (p *interp) peekSlice(n int) []value {
|
|
return p.stack[p.sp-n:]
|
|
}
|
|
|
|
// Helper for getline operations. This performs the (possibly redirected) read
|
|
// of a line, and returns the result. If the result is 1 (success in AWK), the
|
|
// caller will set the target to the returned string.
|
|
func (p *interp) getline(redirect lexer.Token) (float64, string, error) {
|
|
switch redirect {
|
|
case lexer.PIPE: // redirect from command
|
|
name := p.toString(p.pop())
|
|
scanner, err := p.getInputScannerPipe(name)
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
if !scanner.Scan() {
|
|
if err := scanner.Err(); err != nil {
|
|
return -1, "", nil
|
|
}
|
|
return 0, "", nil
|
|
}
|
|
return 1, scanner.Text(), nil
|
|
|
|
case lexer.LESS: // redirect from file
|
|
name := p.toString(p.pop())
|
|
scanner, err := p.getInputScannerFile(name)
|
|
if err != nil {
|
|
if _, ok := err.(*os.PathError); ok {
|
|
// File not found is not a hard error, getline just returns -1.
|
|
// See: https://github.com/benhoyt/goawk/issues/41
|
|
return -1, "", nil
|
|
}
|
|
return 0, "", err
|
|
}
|
|
if !scanner.Scan() {
|
|
if err := scanner.Err(); err != nil {
|
|
return -1, "", nil
|
|
}
|
|
return 0, "", nil
|
|
}
|
|
return 1, scanner.Text(), nil
|
|
|
|
default: // no redirect
|
|
p.flushOutputAndError() // Flush output in case they've written a prompt
|
|
var err error
|
|
line, err := p.nextLine()
|
|
if err == io.EOF {
|
|
return 0, "", nil
|
|
}
|
|
if err != nil {
|
|
return -1, "", nil
|
|
}
|
|
return 1, line, nil
|
|
}
|
|
}
|
|
|
|
// Perform augmented assignment operation.
|
|
func (p *interp) augAssignOp(op compiler.AugOp, l, r value) (value, error) {
|
|
switch op {
|
|
case compiler.AugOpAdd:
|
|
return num(l.num() + r.num()), nil
|
|
case compiler.AugOpSub:
|
|
return num(l.num() - r.num()), nil
|
|
case compiler.AugOpMul:
|
|
return num(l.num() * r.num()), nil
|
|
case compiler.AugOpDiv:
|
|
rf := r.num()
|
|
if rf == 0.0 {
|
|
return null(), newError("division by zero")
|
|
}
|
|
return num(l.num() / rf), nil
|
|
case compiler.AugOpPow:
|
|
return num(math.Pow(l.num(), r.num())), nil
|
|
default: // AugOpMod
|
|
rf := r.num()
|
|
if rf == 0.0 {
|
|
return null(), newError("division by zero in mod")
|
|
}
|
|
return num(math.Mod(l.num(), rf)), nil
|
|
}
|
|
}
|