func (p *interp) setVarByName(name, value string) error {
	index := SpecialVarIndex(name)
	if index > 0 {
		return p.setVar(ScopeSpecial, index, numStr(value))
	}
	index, ok := p.program.Scalars[name]
	if ok {
		return p.setVar(ScopeGlobal, index, numStr(value))
	}
	return nil
}
Set a variable by name (specials and globals only)
package interp
import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"math/rand"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"unicode/utf8"
	. "github.com/benhoyt/goawk/internal/ast"
	. "github.com/benhoyt/goawk/lexer"
	. "github.com/benhoyt/goawk/parser"
)
var (
	errExit     = errors.New("exit")
	errBreak    = errors.New("break")
	errContinue = errors.New("continue")
	errNext     = errors.New("next")
	crlfNewline = runtime.GOOS == "windows"
	varRegex    = regexp.MustCompile(`^([_a-zA-Z][_a-zA-Z0-9]*)=(.*)`)
)
type Error struct {
	message string
}
func (e *Error) Error() string {
	return e.message
}
func newError(format string, args ...interface{}) error {
	return &Error{fmt.Sprintf(format, args...)}
}
type returnValue struct {
	Value value
}
func (r returnValue) Error() string {
	return "<return " + r.Value.str("%.6g") + ">"
}
type interp struct {
	output        io.Writer
	errorOutput   io.Writer
	scanner       *bufio.Scanner
	scanners      map[string]*bufio.Scanner
	stdin         io.Reader
	filenameIndex int
	hadFiles      bool
	input         io.Reader
	inputStreams  map[string]io.ReadCloser
	outputStreams map[string]io.WriteCloser
	commands      map[string]*exec.Cmd
	noExec        bool
	noFileWrites  bool
	noFileReads   bool
	shellCommand  []string
	globals     []value
	stack       []value
	frame       []value
	arrays      []map[string]value
	localArrays [][]int
	callDepth   int
	nativeFuncs []nativeFunc
	filename    string
	line        string
	lineNum     int
	fileLineNum int
	fields      []string
	numFields   int
	haveFields  bool
	argc            int
	convertFormat   string
	outputFormat    string
	fieldSep        string
	fieldSepRegex   *regexp.Regexp
	recordSep       string
	outputFieldSep  string
	outputRecordSep string
	subscriptSep    string
	matchLength     int
	matchStart      int
	program     *Program
	random      *rand.Rand
	randSeed    float64
	exitStatus  int
	regexCache  map[string]*regexp.Regexp
	formatCache map[string]cachedFormat
}
const (
	maxCachedRegexes = 100
	maxCachedFormats = 100
	maxRecordLength  = 10 * 1024 * 1024  
	maxFieldIndex    = 1000000
	maxCallDepth     = 1000
	initialStackSize = 100
	outputBufSize    = 64 * 1024
	inputBufSize     = 64 * 1024
)
type Config struct {
	Stdin io.Reader
	Output io.Writer
	Error io.Writer
	Argv0 string
	Args []string
	Vars []string
	Funcs map[string]interface{}
	NoExec       bool
	NoFileWrites bool
	NoFileReads  bool
	ShellCommand []string
}
func ExecProgram(program *Program, config *Config) (int, error) {
	if len(config.Vars)%2 != 0 {
		return 0, newError("length of config.Vars must be a multiple of 2, not %d", len(config.Vars))
	}
	p := &interp{program: program}
	p.globals = make([]value, len(program.Scalars))
	p.stack = make([]value, 0, initialStackSize)
	p.arrays = make([]map[string]value, len(program.Arrays), len(program.Arrays)+initialStackSize)
	for i := 0; i < len(program.Arrays); i++ {
		p.arrays[i] = make(map[string]value)
	}
	p.regexCache = make(map[string]*regexp.Regexp, 10)
	p.formatCache = make(map[string]cachedFormat, 10)
	p.randSeed = 1.0
	seed := math.Float64bits(p.randSeed)
	p.random = rand.New(rand.NewSource(int64(seed)))
	p.convertFormat = "%.6g"
	p.outputFormat = "%.6g"
	p.fieldSep = " "
	p.recordSep = "\n"
	p.outputFieldSep = " "
	p.outputRecordSep = "\n"
	p.subscriptSep = "\x1c"
	p.noExec = config.NoExec
	p.noFileWrites = config.NoFileWrites
	p.noFileReads = config.NoFileReads
	err := p.initNativeFuncs(config.Funcs)
	if err != nil {
		return 0, err
	}
	argvIndex := program.Arrays["ARGV"]
	p.setArrayValue(ScopeGlobal, argvIndex, "0", str(config.Argv0))
	p.argc = len(config.Args) + 1
	for i, arg := range config.Args {
		p.setArrayValue(ScopeGlobal, argvIndex, strconv.Itoa(i+1), str(arg))
	}
	p.filenameIndex = 1
	p.hadFiles = false
	for i := 0; i < len(config.Vars); i += 2 {
		err := p.setVarByName(config.Vars[i], config.Vars[i+1])
		if err != nil {
			return 0, err
		}
	}
	if len(config.ShellCommand) != 0 {
		p.shellCommand = config.ShellCommand
	} else {
		executable := "/bin/sh"
		if runtime.GOOS == "windows" {
			executable = "sh"
		}
		p.shellCommand = []string{executable, "-c"}
	}
	p.stdin = config.Stdin
	if p.stdin == nil {
		p.stdin = os.Stdin
	}
	p.output = config.Output
	if p.output == nil {
		p.output = bufio.NewWriterSize(os.Stdout, outputBufSize)
	}
	p.errorOutput = config.Error
	if p.errorOutput == nil {
		p.errorOutput = os.Stderr
	}
	p.inputStreams = make(map[string]io.ReadCloser)
	p.outputStreams = make(map[string]io.WriteCloser)
	p.commands = make(map[string]*exec.Cmd)
	p.scanners = make(map[string]*bufio.Scanner)
	defer p.closeAll()
	err = p.execBeginEnd(program.Begin)
	if err != nil && err != errExit {
		return 0, err
	}
	if program.Actions == nil && program.End == nil {
		return p.exitStatus, nil
	}
	if err != errExit {
		err = p.execActions(program.Actions)
		if err != nil && err != errExit {
			return 0, err
		}
	}
	err = p.execBeginEnd(program.End)
	if err != nil && err != errExit {
		return 0, err
	}
	return p.exitStatus, nil
}
func Exec(source, fieldSep string, input io.Reader, output io.Writer) error {
	prog, err := ParseProgram([]byte(source), nil)
	if err != nil {
		return err
	}
	config := &Config{
		Stdin:  input,
		Output: output,
		Error:  ioutil.Discard,
		Vars:   []string{"FS", fieldSep},
	}
	_, err = ExecProgram(prog, config)
	return err
}
func (p *interp) execBeginEnd(beginEnd []Stmts) error {
	for _, statements := range beginEnd {
		err := p.executes(statements)
		if err != nil {
			return err
		}
	}
	return nil
}
func (p *interp) execActions(actions []Action) error {
	inRange := make([]bool, len(actions))
lineLoop:
	for {
		line, err := p.nextLine()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		p.setLine(line)
		for i, action := range actions {
			matched := false
			switch len(action.Pattern) {
			case 0:
				matched = true
			case 1:
				v, err := p.eval(action.Pattern[0])
				if err != nil {
					return err
				}
				matched = v.boolean()
			case 2:
				if !inRange[i] {
					v, err := p.eval(action.Pattern[0])
					if err != nil {
						return err
					}
					inRange[i] = v.boolean()
				}
				matched = inRange[i]
				if inRange[i] {
					v, err := p.eval(action.Pattern[1])
					if err != nil {
						return err
					}
					inRange[i] = !v.boolean()
				}
			}
			if !matched {
				continue
			}
			if action.Stmts == nil {
				err := p.printLine(p.output, p.line)
				if err != nil {
					return err
				}
				continue
			}
			err := p.executes(action.Stmts)
			if err == errNext {
				continue lineLoop
			}
			if err != nil {
				return err
			}
		}
	}
	return nil
}
func (p *interp) executes(stmts Stmts) error {
	for _, s := range stmts {
		err := p.execute(s)
		if err != nil {
			return err
		}
	}
	return nil
}
func (p *interp) execute(stmt Stmt) error {
	switch s := stmt.(type) {
	case *ExprStmt:
		_, err := p.eval(s.Expr)
		return err
	case *PrintStmt:
		var line string
		if len(s.Args) > 0 {
			strs := make([]string, len(s.Args))
			for i, a := range s.Args {
				v, err := p.eval(a)
				if err != nil {
					return err
				}
				strs[i] = v.str(p.outputFormat)
			}
			line = strings.Join(strs, p.outputFieldSep)
		} else {
			line = p.line
		}
		output, err := p.getOutputStream(s.Redirect, s.Dest)
		if err != nil {
			return err
		}
		return p.printLine(output, line)
	case *PrintfStmt:
		formatValue, err := p.eval(s.Args[0])
		if err != nil {
			return err
		}
		format := p.toString(formatValue)
		args := make([]value, len(s.Args)-1)
		for i, a := range s.Args[1:] {
			args[i], err = p.eval(a)
			if err != nil {
				return err
			}
		}
		output, err := p.getOutputStream(s.Redirect, s.Dest)
		if err != nil {
			return err
		}
		str, err := p.sprintf(format, args)
		if err != nil {
			return err
		}
		err = writeOutput(output, str)
		if err != nil {
			return err
		}
	case *IfStmt:
		v, err := p.eval(s.Cond)
		if err != nil {
			return err
		}
		if v.boolean() {
			return p.executes(s.Body)
		} else {
			return p.executes(s.Else)
		}
	case *ForStmt:
		if s.Pre != nil {
			err := p.execute(s.Pre)
			if err != nil {
				return err
			}
		}
		for {
			if s.Cond != nil {
				v, err := p.eval(s.Cond)
				if err != nil {
					return err
				}
				if !v.boolean() {
					break
				}
			}
			err := p.executes(s.Body)
			if err == errBreak {
				break
			}
			if err != nil && err != errContinue {
				return err
			}
			if s.Post != nil {
				err := p.execute(s.Post)
				if err != nil {
					return err
				}
			}
		}
	case *ForInStmt:
		array := p.arrays[p.getArrayIndex(s.Array.Scope, s.Array.Index)]
		for index := range array {
			err := p.setVar(s.Var.Scope, s.Var.Index, str(index))
			if err != nil {
				return err
			}
			err = p.executes(s.Body)
			if err == errBreak {
				break
			}
			if err == errContinue {
				continue
			}
			if err != nil {
				return err
			}
		}
	case *ReturnStmt:
		var v value
		if s.Value != nil {
			var err error
			v, err = p.eval(s.Value)
			if err != nil {
				return err
			}
		}
		return returnValue{v}
	case *WhileStmt:
		for {
			v, err := p.eval(s.Cond)
			if err != nil {
				return err
			}
			if !v.boolean() {
				break
			}
			err = p.executes(s.Body)
			if err == errBreak {
				break
			}
			if err == errContinue {
				continue
			}
			if err != nil {
				return err
			}
		}
	case *DoWhileStmt:
		for {
			err := p.executes(s.Body)
			if err == errBreak {
				break
			}
			if err == errContinue {
				continue
			}
			if err != nil {
				return err
			}
			v, err := p.eval(s.Cond)
			if err != nil {
				return err
			}
			if !v.boolean() {
				break
			}
		}
	case *BreakStmt:
		return errBreak
	case *ContinueStmt:
		return errContinue
	case *NextStmt:
		return errNext
	case *ExitStmt:
		if s.Status != nil {
			status, err := p.eval(s.Status)
			if err != nil {
				return err
			}
			p.exitStatus = int(status.num())
		}
		return errExit
	case *DeleteStmt:
		if len(s.Index) > 0 {
			index, err := p.evalIndex(s.Index)
			if err != nil {
				return err
			}
			array := p.arrays[p.getArrayIndex(s.Array.Scope, s.Array.Index)]
			delete(array, index)  
		} else {
			array := p.arrays[p.getArrayIndex(s.Array.Scope, s.Array.Index)]
			for k := range array {
				delete(array, k)
			}
		}
	case *BlockStmt:
		return p.executes(s.Body)
	default:
		panic(fmt.Sprintf("unexpected stmt type: %T", stmt))
	}
	return nil
}
func (p *interp) eval(expr Expr) (value, error) {
	switch e := expr.(type) {
	case *NumExpr:
		return num(e.Value), nil
	case *StrExpr:
		return str(e.Value), nil
	case *FieldExpr:
		index, err := p.eval(e.Index)
		if err != nil {
			return null(), err
		}
		return p.getField(int(index.num()))
	case *VarExpr:
		return p.getVar(e.Scope, e.Index), nil
	case *RegExpr:
		re, err := p.compileRegex(e.Regex)
		if err != nil {
			return null(), err
		}
		return boolean(re.MatchString(p.line)), nil
	case *BinaryExpr:
		left, err := p.eval(e.Left)
		if err != nil {
			return null(), err
		}
		switch e.Op {
		case AND:
			if !left.boolean() {
				return num(0), nil
			}
			right, err := p.eval(e.Right)
			if err != nil {
				return null(), err
			}
			return boolean(right.boolean()), nil
		case OR:
			if left.boolean() {
				return num(1), nil
			}
			right, err := p.eval(e.Right)
			if err != nil {
				return null(), err
			}
			return boolean(right.boolean()), nil
		default:
			right, err := p.eval(e.Right)
			if err != nil {
				return null(), err
			}
			return p.evalBinary(e.Op, left, right)
		}
	case *IncrExpr:
		exprValue, arrayIndex, fieldIndex, err := p.evalForAugAssign(e.Expr)
		if err != nil {
			return null(), err
		}
		exprNum := exprValue.num()
		var incr float64
		if e.Op == INCR {
			incr = exprNum + 1
		} else {
			incr = exprNum - 1
		}
		incrValue := num(incr)
		err = p.assignAug(e.Expr, arrayIndex, fieldIndex, incrValue)
		if err != nil {
			return null(), err
		}
		if e.Pre {
			return incrValue, nil
		} else {
			return num(exprNum), nil
		}
	case *AssignExpr:
		right, err := p.eval(e.Right)
		if err != nil {
			return null(), err
		}
		err = p.assign(e.Left, right)
		if err != nil {
			return null(), err
		}
		return right, nil
	case *AugAssignExpr:
		right, err := p.eval(e.Right)
		if err != nil {
			return null(), err
		}
		left, arrayIndex, fieldIndex, err := p.evalForAugAssign(e.Left)
		if err != nil {
			return null(), err
		}
		right, err = p.evalBinary(e.Op, left, right)
		if err != nil {
			return null(), err
		}
		err = p.assignAug(e.Left, arrayIndex, fieldIndex, right)
		if err != nil {
			return null(), err
		}
		return right, nil
	case *CondExpr:
		cond, err := p.eval(e.Cond)
		if err != nil {
			return null(), err
		}
		if cond.boolean() {
			return p.eval(e.True)
		} else {
			return p.eval(e.False)
		}
	case *IndexExpr:
		index, err := p.evalIndex(e.Index)
		if err != nil {
			return null(), err
		}
		return p.getArrayValue(e.Array.Scope, e.Array.Index, index), nil
	case *CallExpr:
		return p.callBuiltin(e.Func, e.Args)
	case *UnaryExpr:
		v, err := p.eval(e.Value)
		if err != nil {
			return null(), err
		}
		return p.evalUnary(e.Op, v), nil
	case *InExpr:
		index, err := p.evalIndex(e.Index)
		if err != nil {
			return null(), err
		}
		array := p.arrays[p.getArrayIndex(e.Array.Scope, e.Array.Index)]
		_, ok := array[index]
		return boolean(ok), nil
	case *UserCallExpr:
		if e.Native {
			return p.callNative(e.Index, e.Args)
		} else {
			return p.callUser(e.Index, e.Args)
		}
	case *GetlineExpr:
		var line string
		switch {
		case e.Command != nil:
			nameValue, err := p.eval(e.Command)
			if err != nil {
				return null(), err
			}
			name := p.toString(nameValue)
			scanner, err := p.getInputScannerPipe(name)
			if err != nil {
				return null(), err
			}
			if !scanner.Scan() {
				if err := scanner.Err(); err != nil {
					return num(-1), nil
				}
				return num(0), nil
			}
			line = scanner.Text()
		case e.File != nil:
			nameValue, err := p.eval(e.File)
			if err != nil {
				return null(), err
			}
			name := p.toString(nameValue)
			scanner, err := p.getInputScannerFile(name)
			if err != nil {
				if _, ok := err.(*os.PathError); ok {
					return num(-1), nil
				}
				return null(), err
			}
			if !scanner.Scan() {
				if err := scanner.Err(); err != nil {
					return num(-1), nil
				}
				return num(0), nil
			}
			line = scanner.Text()
		default:
			var err error
			line, err = p.nextLine()
			if err == io.EOF {
				return num(0), nil
			}
			if err != nil {
				return num(-1), nil
			}
		}
		if e.Var != nil {
			err := p.setVar(e.Var.Scope, e.Var.Index, str(line))
			if err != nil {
				return null(), err
			}
		} else {
			p.setLine(line)
		}
		return num(1), nil
	default:
		panic(fmt.Sprintf("unexpected expr type: %T", expr))
	}
}
func (p *interp) evalForAugAssign(expr Expr) (v value, arrayIndex string, fieldIndex int, err error) {
	switch expr := expr.(type) {
	case *VarExpr:
		v = p.getVar(expr.Scope, expr.Index)
	case *IndexExpr:
		arrayIndex, err = p.evalIndex(expr.Index)
		if err != nil {
			return null(), "", 0, err
		}
		v = p.getArrayValue(expr.Array.Scope, expr.Array.Index, arrayIndex)
	case *FieldExpr:
		index, err := p.eval(expr.Index)
		if err != nil {
			return null(), "", 0, err
		}
		fieldIndex = int(index.num())
		v, err = p.getField(fieldIndex)
		if err != nil {
			return null(), "", 0, err
		}
	}
	return v, arrayIndex, fieldIndex, nil
}
func (p *interp) assignAug(expr Expr, arrayIndex string, fieldIndex int, v value) error {
	switch expr := expr.(type) {
	case *VarExpr:
		return p.setVar(expr.Scope, expr.Index, v)
	case *IndexExpr:
		p.setArrayValue(expr.Array.Scope, expr.Array.Index, arrayIndex, v)
	default:  
		return p.setField(fieldIndex, p.toString(v))
	}
	return nil
}
func (p *interp) getVar(scope VarScope, index int) value {
	switch scope {
	case ScopeGlobal:
		return p.globals[index]
	case ScopeLocal:
		return p.frame[index]
	default:  
		switch index {
		case V_NF:
			p.ensureFields()
			return num(float64(p.numFields))
		case V_NR:
			return num(float64(p.lineNum))
		case V_RLENGTH:
			return num(float64(p.matchLength))
		case V_RSTART:
			return num(float64(p.matchStart))
		case V_FNR:
			return num(float64(p.fileLineNum))
		case V_ARGC:
			return num(float64(p.argc))
		case V_CONVFMT:
			return str(p.convertFormat)
		case V_FILENAME:
			return str(p.filename)
		case V_FS:
			return str(p.fieldSep)
		case V_OFMT:
			return str(p.outputFormat)
		case V_OFS:
			return str(p.outputFieldSep)
		case V_ORS:
			return str(p.outputRecordSep)
		case V_RS:
			return str(p.recordSep)
		case V_SUBSEP:
			return str(p.subscriptSep)
		default:
			panic(fmt.Sprintf("unexpected special variable index: %d", index))
		}
	}
}