func (p *interp) sub(regex, repl, in string, global bool) (out string, num int, err error) {
	re, err := p.compileRegex(regex)
	if err != nil {
		return "", 0, err
	}
	count := 0
	out = re.ReplaceAllStringFunc(in, func(s string) string {
		if !global && count > 0 {
			return s
		}
		count++
		r := make([]byte, 0, 64)  
		for i := 0; i < len(repl); i++ {
			switch repl[i] {
			case '&':
				r = append(r, s...)
			case '\\':
				i++
				if i < len(repl) {
					switch repl[i] {
					case '&':
						r = append(r, '&')
					case '\\':
						r = append(r, '\\')
					default:
						r = append(r, '\\', repl[i])
					}
				} else {
					r = append(r, '\\')
				}
			default:
				r = append(r, repl[i])
			}
		}
		return string(r)
	})
	return out, count, nil
}
Guts of the sub() and gsub() functions
package interp
import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"math"
	"os/exec"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
	. "github.com/benhoyt/goawk/internal/ast"
	. "github.com/benhoyt/goawk/lexer"
)
func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {
	switch op {
	case F_SPLIT:
		strValue, err := p.eval(argExprs[0])
		if err != nil {
			return null(), err
		}
		str := p.toString(strValue)
		var fieldSep string
		if len(argExprs) == 3 {
			sepValue, err := p.eval(argExprs[2])
			if err != nil {
				return null(), err
			}
			fieldSep = p.toString(sepValue)
		} else {
			fieldSep = p.fieldSep
		}
		arrayExpr := argExprs[1].(*ArrayExpr)
		n, err := p.split(str, arrayExpr.Scope, arrayExpr.Index, fieldSep)
		if err != nil {
			return null(), err
		}
		return num(float64(n)), nil
	case F_SUB, F_GSUB:
		regexValue, err := p.eval(argExprs[0])
		if err != nil {
			return null(), err
		}
		regex := p.toString(regexValue)
		replValue, err := p.eval(argExprs[1])
		if err != nil {
			return null(), err
		}
		repl := p.toString(replValue)
		var in string
		if len(argExprs) == 3 {
			inValue, err := p.eval(argExprs[2])
			if err != nil {
				return null(), err
			}
			in = p.toString(inValue)
		} else {
			in = p.line
		}
		out, n, err := p.sub(regex, repl, in, op == F_GSUB)
		if err != nil {
			return null(), err
		}
		if len(argExprs) == 3 {
			err := p.assign(argExprs[2], str(out))
			if err != nil {
				return null(), err
			}
		} else {
			p.setLine(out)
		}
		return num(float64(n)), nil
	}
	args := make([]value, 0, 7)
	for _, a := range argExprs {
		arg, err := p.eval(a)
		if err != nil {
			return null(), err
		}
		args = append(args, arg)
	}
	switch op {
	case F_LENGTH:
		switch len(args) {
		case 0:
			return num(float64(len(p.line))), nil
		default:
			return num(float64(len(p.toString(args[0])))), nil
		}
	case F_MATCH:
		re, err := p.compileRegex(p.toString(args[1]))
		if err != nil {
			return null(), err
		}
		loc := re.FindStringIndex(p.toString(args[0]))
		if loc == nil {
			p.matchStart = 0
			p.matchLength = -1
			return num(0), nil
		}
		p.matchStart = loc[0] + 1
		p.matchLength = loc[1] - loc[0]
		return num(float64(p.matchStart)), nil
	case F_SUBSTR:
		s := p.toString(args[0])
		pos := int(args[1].num())
		if pos > len(s) {
			pos = len(s) + 1
		}
		if pos < 1 {
			pos = 1
		}
		maxLength := len(s) - pos + 1
		length := maxLength
		if len(args) == 3 {
			length = int(args[2].num())
			if length < 0 {
				length = 0
			}
			if length > maxLength {
				length = maxLength
			}
		}
		return str(s[pos-1 : pos-1+length]), nil
	case F_SPRINTF:
		s, err := p.sprintf(p.toString(args[0]), args[1:])
		if err != nil {
			return null(), err
		}
		return str(s), nil
	case F_INDEX:
		s := p.toString(args[0])
		substr := p.toString(args[1])
		return num(float64(strings.Index(s, substr) + 1)), nil
	case F_TOLOWER:
		return str(strings.ToLower(p.toString(args[0]))), nil
	case F_TOUPPER:
		return str(strings.ToUpper(p.toString(args[0]))), nil
	case F_ATAN2:
		return num(math.Atan2(args[0].num(), args[1].num())), nil
	case F_COS:
		return num(math.Cos(args[0].num())), nil
	case F_EXP:
		return num(math.Exp(args[0].num())), nil
	case F_INT:
		return num(float64(int(args[0].num()))), nil
	case F_LOG:
		return num(math.Log(args[0].num())), nil
	case F_SQRT:
		return num(math.Sqrt(args[0].num())), nil
	case F_RAND:
		return num(p.random.Float64()), nil
	case F_SIN:
		return num(math.Sin(args[0].num())), nil
	case F_SRAND:
		prevSeed := p.randSeed
		switch len(args) {
		case 0:
			p.random.Seed(time.Now().UnixNano())
		case 1:
			p.randSeed = args[0].num()
			p.random.Seed(int64(math.Float64bits(p.randSeed)))
		}
		return num(prevSeed), nil
	case F_SYSTEM:
		if p.noExec {
			return null(), newError("can't call system() due to NoExec")
		}
		cmdline := p.toString(args[0])
		cmd := p.execShell(cmdline)
		cmd.Stdout = p.output
		cmd.Stderr = p.errorOutput
		err := cmd.Start()
		if err != nil {
			fmt.Fprintln(p.errorOutput, err)
			return num(-1), nil
		}
		err = cmd.Wait()
		if err != nil {
			if exitErr, ok := err.(*exec.ExitError); ok {
				code := exitErr.ProcessState.ExitCode()
				return num(float64(code)), nil
			} else {
				fmt.Fprintf(p.errorOutput, "unexpected error running command %q: %v\n", cmdline, err)
				return num(-1), nil
			}
		}
		return num(0), nil
	case F_CLOSE:
		name := p.toString(args[0])
		var c io.Closer = p.inputStreams[name]
		if c != nil {
			delete(p.inputStreams, name)
			err := c.Close()
			if err != nil {
				return num(-1), nil
			}
			return num(0), nil
		}
		c = p.outputStreams[name]
		if c != nil {
			delete(p.outputStreams, name)
			err := c.Close()
			if err != nil {
				return num(-1), nil
			}
			return num(0), nil
		}
		return num(-1), nil
	case F_FFLUSH:
		var name string
		if len(args) > 0 {
			name = p.toString(args[0])
		}
		var ok bool
		if name != "" {
			ok = p.flushStream(name)
		} else {
			ok = p.flushAll()
		}
		if !ok {
			return num(-1), nil
		}
		return num(0), nil
	default:
		panic(fmt.Sprintf("unexpected function: %s", op))
	}
}
func (p *interp) execShell(code string) *exec.Cmd {
	executable := p.shellCommand[0]
	args := p.shellCommand[1:]
	args = append(args, code)
	cmd := exec.Command(executable, args...)
	return cmd
}
func (p *interp) callUser(index int, args []Expr) (value, error) {
	f := p.program.Functions[index]
	if p.callDepth >= maxCallDepth {
		return null(), newError("calling %q exceeded maximum call depth of %d", f.Name, maxCallDepth)
	}
	oldFrame := p.frame
	newFrameStart := len(p.stack)
	var arrays []int
	for i, arg := range args {
		if f.Arrays[i] {
			a := arg.(*VarExpr)
			arrays = append(arrays, p.getArrayIndex(a.Scope, a.Index))
		} else {
			argValue, err := p.eval(arg)
			if err != nil {
				return null(), err
			}
			p.stack = append(p.stack, argValue)
		}
	}
	oldArraysLen := len(p.arrays)
	for i := len(args); i < len(f.Params); i++ {
		if f.Arrays[i] {
			arrays = append(arrays, len(p.arrays))
			p.arrays = append(p.arrays, make(map[string]value))
		} else {
			p.stack = append(p.stack, null())
		}
	}
	p.frame = p.stack[newFrameStart:]
	p.localArrays = append(p.localArrays, arrays)
	p.callDepth++
	err := p.executes(f.Body)
	p.callDepth--
	p.stack = p.stack[:newFrameStart]
	p.frame = oldFrame
	p.localArrays = p.localArrays[:len(p.localArrays)-1]
	p.arrays = p.arrays[:oldArraysLen]
	if r, ok := err.(returnValue); ok {
		return r.Value, nil
	}
	if err != nil {
		return null(), err
	}
	return null(), nil
}
func (p *interp) callNative(index int, args []Expr) (value, error) {
	f := p.nativeFuncs[index]
	minIn := len(f.in)  
	var variadicType reflect.Type
	if f.isVariadic {
		variadicType = f.in[len(f.in)-1].Elem()
		minIn--
	}
	values := make([]reflect.Value, 0, 7)  
	for i, arg := range args {
		a, err := p.eval(arg)
		if err != nil {
			return null(), err
		}
		var argType reflect.Type
		if !f.isVariadic || i < len(f.in)-1 {
			argType = f.in[i]
		} else {
			argType = variadicType
		}
		values = append(values, p.toNative(a, argType))
	}
	for i := len(args); i < minIn; i++ {
		values = append(values, reflect.Zero(f.in[i]))
	}
	outs := f.value.Call(values)
	switch len(outs) {
	case 0:
		return null(), nil
	case 1:
		return fromNative(outs[0]), nil
	case 2:
		if !outs[1].IsNil() {
			return null(), outs[1].Interface().(error)
		}
		return fromNative(outs[0]), nil
	default:
		panic(fmt.Sprintf("unexpected number of return values: %d", len(outs)))
	}
}
func (p *interp) toNative(v value, typ reflect.Type) reflect.Value {
	switch typ.Kind() {
	case reflect.Bool:
		return reflect.ValueOf(v.boolean())
	case reflect.Int:
		return reflect.ValueOf(int(v.num()))
	case reflect.Int8:
		return reflect.ValueOf(int8(v.num()))
	case reflect.Int16:
		return reflect.ValueOf(int16(v.num()))
	case reflect.Int32:
		return reflect.ValueOf(int32(v.num()))
	case reflect.Int64:
		return reflect.ValueOf(int64(v.num()))
	case reflect.Uint:
		return reflect.ValueOf(uint(v.num()))
	case reflect.Uint8:
		return reflect.ValueOf(uint8(v.num()))
	case reflect.Uint16:
		return reflect.ValueOf(uint16(v.num()))
	case reflect.Uint32:
		return reflect.ValueOf(uint32(v.num()))
	case reflect.Uint64:
		return reflect.ValueOf(uint64(v.num()))
	case reflect.Float32:
		return reflect.ValueOf(float32(v.num()))
	case reflect.Float64:
		return reflect.ValueOf(v.num())
	case reflect.String:
		return reflect.ValueOf(p.toString(v))
	case reflect.Slice:
		if typ.Elem().Kind() != reflect.Uint8 {
			panic(fmt.Sprintf("unexpected argument slice: %s", typ.Elem().Kind()))
		}
		return reflect.ValueOf([]byte(p.toString(v)))
	default:
		panic(fmt.Sprintf("unexpected argument type: %s", typ.Kind()))
	}
}
func fromNative(v reflect.Value) value {
	switch v.Kind() {
	case reflect.Bool:
		return boolean(v.Bool())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return num(float64(v.Int()))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return num(float64(v.Uint()))
	case reflect.Float32, reflect.Float64:
		return num(v.Float())
	case reflect.String:
		return str(v.String())
	case reflect.Slice:
		if b, ok := v.Interface().([]byte); ok {
			return str(string(b))
		}
		panic(fmt.Sprintf("unexpected return slice: %s", v.Type().Elem().Kind()))
	default:
		panic(fmt.Sprintf("unexpected return type: %s", v.Kind()))
	}
}
type nativeFunc struct {
	isVariadic bool
	in         []reflect.Type
	value      reflect.Value
}
func (p *interp) initNativeFuncs(funcs map[string]interface{}) error {
	for name, f := range funcs {
		err := checkNativeFunc(name, f)
		if err != nil {
			return err
		}
	}
	names := make([]string, 0, len(funcs))
	for name := range funcs {
		names = append(names, name)
	}
	sort.Strings(names)
	p.nativeFuncs = make([]nativeFunc, len(names))
	for i, name := range names {
		f := funcs[name]
		typ := reflect.TypeOf(f)
		in := make([]reflect.Type, typ.NumIn())
		for j := 0; j < len(in); j++ {
			in[j] = typ.In(j)
		}
		p.nativeFuncs[i] = nativeFunc{
			isVariadic: typ.IsVariadic(),
			in:         in,
			value:      reflect.ValueOf(f),
		}
	}
	return nil
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
func checkNativeFunc(name string, f interface{}) error {
	if KeywordToken(name) != ILLEGAL {
		return newError("can't use keyword %q as native function name", name)
	}
	typ := reflect.TypeOf(f)
	if typ.Kind() != reflect.Func {
		return newError("native function %q is not a function", name)
	}
	for i := 0; i < typ.NumIn(); i++ {
		param := typ.In(i)
		if typ.IsVariadic() && i == typ.NumIn()-1 {
			param = param.Elem()
		}
		if !validNativeType(param) {
			return newError("native function %q param %d is not int or string", name, i)
		}
	}
	switch typ.NumOut() {
	case 0:
	case 1:
		if !validNativeType(typ.Out(0)) {
			return newError("native function %q return value is not int or string", name)
		}
	case 2:
		if !validNativeType(typ.Out(0)) {
			return newError("native function %q first return value is not int or string", name)
		}
		if typ.Out(1) != errorType {
			return newError("native function %q second return value is not an error", name)
		}
	default:
		return newError("native function %q returns more than two values", name)
	}
	return nil
}
func validNativeType(typ reflect.Type) bool {
	switch typ.Kind() {
	case reflect.Bool:
		return true
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return true
	case reflect.Float32, reflect.Float64:
		return true
	case reflect.String:
		return true
	case reflect.Slice:
		return typ.Elem().Kind() == reflect.Uint8
	default:
		return false
	}
}
func (p *interp) split(s string, scope VarScope, index int, fs string) (int, error) {
	var parts []string
	if fs == " " {
		parts = strings.Fields(s)
	} else if s == "" {
	} else if utf8.RuneCountInString(fs) <= 1 {
		parts = strings.Split(s, fs)
	} else {
		re, err := p.compileRegex(fs)
		if err != nil {
			return 0, err
		}
		parts = re.Split(s, -1)
	}
	array := make(map[string]value, len(parts))
	for i, part := range parts {
		array[strconv.Itoa(i+1)] = numStr(part)
	}
	p.arrays[p.getArrayIndex(scope, index)] = array
	return len(array), nil
}