Repository: benhoyt/goawk
Pull request #75 (2021-11-20 11:01:47)

AWKGo: basic AWK to Go compiler


Patch:
diff --git a/awkgo/awkgo.go b/awkgo/awkgo.go
new file mode 100644
index 0000000..06fb6fa
--- /dev/null
+++ b/awkgo/awkgo.go
@@ -0,0 +1,195 @@
+// AWKGo: AWK to Go compiler
+
+/*
+NOT SUPPORTED:
+- functions
+- dynamic typing
+- non-literal [s]printf format strings?
+- assigning numStr values (but using $0 in conditionals works)
+- null values (unset number variable should output "", we output "0")
+- "next" in functions
+- print redirection
+- getline
+- reference to nonexistent array element should create it (POSIX, but yuck)
+- some forms of augmented assignment (no reason we can't, just TODO)
+*/
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/benhoyt/goawk/lexer"
+	"github.com/benhoyt/goawk/parser"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"unicode/utf8"
+)
+
+const (
+	version    = "v1.0.0"
+	copyright  = "AWKGo " + version + " - Copyright (c) 2021 Ben Hoyt"
+	shortUsage = "usage: awkgo [-f progfile | 'prog']"
+	longUsage  = `AWKGo arguments:
+  -f progfile
+        load AWK source from progfile (multiple allowed)
+  -h    show this usage message
+  -version
+        show AWKGo version and exit
+`
+)
+
+func main() {
+	var progFiles []string
+
+	var i int
+	for i = 1; i < len(os.Args); i++ {
+		// Stop on explicit end of args or first arg not prefixed with "-"
+		arg := os.Args[i]
+		if arg == "--" {
+			i++
+			break
+		}
+		if arg == "-" || !strings.HasPrefix(arg, "-") {
+			break
+		}
+
+		switch arg {
+		case "-f":
+			if i+1 >= len(os.Args) {
+				errorExitf("flag needs an argument: -f")
+			}
+			i++
+			progFiles = append(progFiles, os.Args[i])
+		case "-h", "--help":
+			fmt.Printf("%s\n\n%s\n\n%s", copyright, shortUsage, longUsage)
+			os.Exit(0)
+		case "-version", "--version":
+			fmt.Println(version)
+			os.Exit(0)
+		default:
+			switch {
+			case strings.HasPrefix(arg, "-f"):
+				progFiles = append(progFiles, arg[2:])
+			default:
+				errorExitf("flag provided but not defined: %s", arg)
+			}
+		}
+	}
+
+	// Any remaining arg is the program source
+	args := os.Args[i:]
+
+	var src []byte
+	if len(progFiles) > 0 {
+		// Read source: the concatenation of all source files specified
+		buf := &bytes.Buffer{}
+		progFiles = expandWildcardsOnWindows(progFiles)
+		for _, progFile := range progFiles {
+			if progFile == "-" {
+				_, err := buf.ReadFrom(os.Stdin)
+				if err != nil {
+					errorExit(err)
+				}
+			} else {
+				f, err := os.Open(progFile)
+				if err != nil {
+					errorExit(err)
+				}
+				_, err = buf.ReadFrom(f)
+				if err != nil {
+					_ = f.Close()
+					errorExit(err)
+				}
+				_ = f.Close()
+			}
+			// Append newline to file in case it doesn't end with one
+			_ = buf.WriteByte('\n')
+		}
+		src = buf.Bytes()
+	} else {
+		if len(args) < 1 {
+			errorExitf(shortUsage)
+		}
+		src = []byte(args[0])
+		args = args[1:]
+	}
+
+	if len(args) > 0 {
+		errorExitf("additional arguments not allowed: %v\n%s", strings.Join(args, " "), shortUsage)
+	}
+
+	prog, err := parser.ParseProgram(src, nil)
+	if err != nil {
+		errMsg := fmt.Sprintf("%s", err)
+		if err, ok := err.(*parser.ParseError); ok {
+			showSourceLine(src, err.Position, len(errMsg))
+		}
+		errorExitf("%s", errMsg)
+	}
+
+	err = compile(prog, os.Stdout)
+	if err != nil {
+		errorExitf("%v", err)
+	}
+}
+
+// For parse errors, show source line and position of error, eg:
+//
+// -----------------------------------------------------
+// BEGIN { x*; }
+//           ^
+// -----------------------------------------------------
+// parse error at 1:11: expected expression instead of ;
+//
+func showSourceLine(src []byte, pos lexer.Position, dividerLen int) {
+	divider := strings.Repeat("-", dividerLen)
+	if divider != "" {
+		fmt.Fprintln(os.Stderr, divider)
+	}
+	lines := bytes.Split(src, []byte{'\n'})
+	srcLine := string(lines[pos.Line-1])
+	numTabs := strings.Count(srcLine[:pos.Column-1], "\t")
+	runeColumn := utf8.RuneCountInString(srcLine[:pos.Column-1])
+	fmt.Fprintln(os.Stderr, strings.Replace(srcLine, "\t", "    ", -1))
+	fmt.Fprintln(os.Stderr, strings.Repeat(" ", runeColumn)+strings.Repeat("   ", numTabs)+"^")
+	if divider != "" {
+		fmt.Fprintln(os.Stderr, divider)
+	}
+}
+
+func errorExit(err error) {
+	pathErr, ok := err.(*os.PathError)
+	if ok && os.IsNotExist(err) {
+		errorExitf("file %q not found", pathErr.Path)
+	}
+	errorExitf("%s", err)
+}
+
+func errorExitf(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format+"\n", args...)
+	os.Exit(1)
+}
+
+func expandWildcardsOnWindows(args []string) []string {
+	if runtime.GOOS != "windows" {
+		return args
+	}
+	return expandWildcards(args)
+}
+
+// Originally from https://github.com/mattn/getwild (compatible LICENSE).
+func expandWildcards(args []string) []string {
+	result := make([]string, 0, len(args))
+	for _, arg := range args {
+		matches, err := filepath.Glob(arg)
+		if err == nil && len(matches) > 0 {
+			result = append(result, matches...)
+		} else {
+			result = append(result, arg)
+		}
+	}
+	return result
+}
diff --git a/awkgo/compiler.go b/awkgo/compiler.go
new file mode 100644
index 0000000..9f818c7
--- /dev/null
+++ b/awkgo/compiler.go
@@ -0,0 +1,1014 @@
+// AWKGo: the main compiler
+
+package main
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"math"
+	"sort"
+	"strconv"
+
+	. "github.com/benhoyt/goawk/internal/ast"
+	. "github.com/benhoyt/goawk/lexer"
+	. "github.com/benhoyt/goawk/parser"
+)
+
+// compile compiles the parsed AWK program to Go and outputs to writer.
+func compile(prog *Program, writer io.Writer) (err error) {
+	// Both typer and compiler signal errors internally with
+	// panic(&errorExit{...}), so recover and return an error.
+	defer func() {
+		r := recover()
+		switch r := r.(type) {
+		case nil:
+		case *exitError:
+			err = errors.New(r.message)
+		default:
+			panic(r) // another type, re-panic
+		}
+	}()
+
+	// Determine the types of variables and expressions.
+	t := newTyper()
+	t.program(prog)
+
+	// Do a second typing pass over the program to ensure we detect the types
+	// of variables assigned after they're used, for example:
+	// BEGIN { while (i<5) { i++; print i } }
+	t.program(prog)
+
+	c := &compiler{
+		typer:   t,
+		writer:  writer,
+		regexen: make(map[string]int),
+	}
+	c.program(prog)
+
+	return nil
+}
+
+type exitError struct {
+	message string
+}
+
+func (e *exitError) Error() string {
+	return e.message
+}
+
+func errorf(format string, args ...interface{}) error {
+	return &exitError{fmt.Sprintf(format, args...)}
+}
+
+type compiler struct {
+	prog    *Program
+	typer   *typer
+	writer  io.Writer
+	regexen map[string]int
+}
+
+func (c *compiler) output(s string) {
+	fmt.Fprint(c.writer, s)
+}
+
+func (c *compiler) outputf(format string, args ...interface{}) {
+	fmt.Fprintf(c.writer, format, args...)
+}
+
+func (c *compiler) program(prog *Program) {
+	c.output(`package main
+
+import (
+	"bufio"
+	"fmt"
+	"math"
+	"math/rand"
+	"os"
+	"os/exec"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode/utf8"
+)
+
+var (
+	_output  *bufio.Writer
+	_scanner *bufio.Scanner
+	_line    string
+	_fields  []string
+	_lineNum int
+	_seed    float64
+	_rand    *rand.Rand
+
+`)
+
+	globals := make([]string, 0, len(c.typer.globals))
+	for name := range c.typer.globals {
+		globals = append(globals, name)
+	}
+	sort.Strings(globals)
+	for _, name := range globals {
+		typ := c.typer.globals[name]
+		c.outputf("%s %s\n", name, c.goType(typ))
+	}
+
+	c.output(`)
+
+func main() {
+	_output = bufio.NewWriter(os.Stdout)
+	defer _output.Flush()
+
+	_scanner = bufio.NewScanner(os.Stdin)
+	_seed = 1.0
+	_rand = rand.New(rand.NewSource(int64(math.Float64bits(_seed))))
+
+	FS = " "
+	OFS = " "
+	ORS = "\n"
+	OFMT = "%.6g"
+	CONVFMT = "%.6g"
+	SUBSEP = "\x1c"
+
+`)
+
+	for i, action := range prog.Actions {
+		if len(action.Pattern) == 2 {
+			// Booleans to keep track of range pattern state
+			c.outputf("_inRange%d := false\n", i)
+		}
+	}
+
+	for _, name := range globals {
+		typ := c.typer.globals[name]
+		switch typ {
+		case typeArrayStr, typeArrayNum:
+			c.outputf("%s = make(%s)\n", name, c.goType(typ))
+		}
+	}
+
+	for _, stmts := range prog.Begin {
+		c.output("\n")
+		c.stmts(stmts)
+	}
+
+	if c.typer.nextUsed {
+		c.output("_nextLine:\n")
+	}
+
+	if len(prog.Actions) > 0 {
+		c.output(`
+	for _scanner.Scan() {
+		_lineNum++
+		_line = _scanner.Text()
+		_fields = _splitHelper(_line, FS)
+`)
+		c.actions(prog.Actions)
+		c.output(`	}
+
+	if _scanner.Err() != nil {
+		fmt.Fprintln(os.Stderr, _scanner.Err())
+		os.Exit(1)
+	}
+`)
+	}
+
+	for _, stmts := range prog.End {
+		c.output("\n")
+		c.stmts(stmts)
+	}
+
+	c.output("}\n")
+
+	type regex struct {
+		pattern string
+		n       int
+	}
+	var regexen []regex
+	for pattern, n := range c.regexen {
+		regexen = append(regexen, regex{pattern, n})
+	}
+	sort.Slice(regexen, func(i, j int) bool {
+		return regexen[i].n < regexen[j].n
+	})
+	if len(regexen) > 0 {
+		c.output("\nvar (\n")
+		for _, r := range regexen {
+			c.outputf("_re%d = regexp.MustCompile(%q)\n", r.n, r.pattern)
+		}
+		c.output(")\n")
+	}
+
+	c.outputHelpers()
+}
+
+func (c *compiler) actions(actions []Action) {
+	for i, action := range actions {
+		c.output("\n")
+		switch len(action.Pattern) {
+		case 0:
+			// No pattern is equivalent to pattern evaluating to true
+		case 1:
+			// Single boolean pattern
+			c.outputf("if %s {\n", c.cond(action.Pattern[0]))
+		case 2:
+			// Range pattern (matches between start and stop lines)
+			c.outputf("if !_inRange%d { _inRange%d = %s }\n", i, i, c.cond(action.Pattern[0]))
+			c.outputf("if _inRange%d {\n", i)
+		}
+
+		if len(action.Stmts) == 0 {
+			// No action is equivalent to { print $0 }
+			c.output("fmt.Fprint(_output, _line, ORS)\n")
+		} else {
+			c.stmts(action.Stmts)
+		}
+
+		switch len(action.Pattern) {
+		case 1:
+			c.output("}\n")
+		case 2:
+			c.outputf("\n_inRange%d = !(%s)\n", i, c.cond(action.Pattern[1]))
+			c.output("}\n")
+		}
+	}
+}
+
+func (c *compiler) stmts(stmts Stmts) {
+	for _, stmt := range stmts {
+		c.stmt(stmt)
+	}
+}
+
+func (c *compiler) assign(left, right Expr) string {
+	switch left := left.(type) {
+	case *VarExpr:
+		if left.Scope == ScopeSpecial {
+			switch left.Index {
+			case V_NF, V_NR, V_FNR:
+				panic(errorf("can't assign to special variable %s", left.Name))
+			}
+		}
+		return fmt.Sprintf("%s = %s", left.Name, c.expr(right))
+	case *IndexExpr:
+		return fmt.Sprintf("%s[%s] = %s", left.Array.Name, c.index(left.Index), c.expr(right))
+	case *FieldExpr:
+		return fmt.Sprintf("_setField(%s, %s)", c.intExpr(left.Index), c.strExpr(right))
+	default:
+		panic(errorf("expected lvalue, not %s", left))
+	}
+}
+
+func (c *compiler) stmtNoNewline(stmt Stmt) {
+	switch s := stmt.(type) {
+	case *ExprStmt:
+		switch e := s.Expr.(type) {
+		case *AssignExpr:
+			c.output(c.assign(e.Left, e.Right))
+
+		case *AugAssignExpr:
+			switch left := e.Left.(type) {
+			case *VarExpr:
+				switch e.Op {
+				case MOD, POW:
+					c.output(left.Name)
+					if e.Op == MOD {
+						c.output(" = math.Mod(")
+					} else {
+						c.output(" = math.Pow(")
+					}
+					c.outputf("%s, %s)", left.Name, c.numExpr(e.Right))
+				default:
+					c.outputf("%s %s= %s", left.Name, e.Op, c.numExpr(e.Right))
+				}
+
+			case *IndexExpr:
+				switch e.Op {
+				case MOD, POW:
+					c.outputf("%s[%s", left.Array.Name, c.index(left.Index))
+					if e.Op == MOD {
+						c.output("] = math.Mod(")
+					} else {
+						c.output("] = math.Pow(")
+					}
+					c.outputf("%s[%s], %s)", left.Array.Name, c.index(left.Index), c.numExpr(e.Right))
+				default:
+					c.outputf("%s[%s] %s= %s", left.Array.Name, c.index(left.Index), e.Op, c.numExpr(e.Right))
+				}
+
+			case *FieldExpr:
+				// We have to be careful not to evaluate left.Index twice, in
+				// case it has side effects, as in: $(y++) += 10
+				switch e.Op {
+				case MOD, POW:
+					c.outputf("func() { _t := %s; _setField(_t, _numToStr(", c.intExpr(left.Index))
+					if e.Op == MOD {
+						c.output("math.Mod(")
+					} else {
+						c.output("math.Pow(")
+					}
+					c.outputf("_strToNum(_getField(_t)), %s))) }()", c.numExpr(e.Right))
+				default:
+					c.outputf("func() { _t := %s; _setField(_t, _numToStr(_strToNum(_getField(_t)) %s %s)) }()",
+						c.intExpr(left.Index), e.Op, c.numExpr(e.Right))
+				}
+			}
+
+		case *IncrExpr:
+			switch left := e.Expr.(type) {
+			case *VarExpr:
+				c.outputf("%s%s", left.Name, e.Op)
+			case *IndexExpr:
+				c.outputf("%s[%s]%s", left.Array.Name, c.index(left.Index), e.Op)
+			case *FieldExpr:
+				// We have to be careful not to evaluate left.Index twice, in
+				// case it has side effects, as in: $(y++)++
+				op := "+"
+				if e.Op == DECR {
+					op = "-"
+				}
+				c.outputf("func() { _t := %s; _setField(_t, _numToStr(_strToNum(_getField(_t)) %s 1)) }()",
+					c.intExpr(left.Index), op)
+			}
+
+		default:
+			c.outputf("_ = %s", c.expr(s.Expr))
+		}
+
+	case *PrintStmt:
+		if s.Dest != nil {
+			panic(errorf("print redirection not yet supported"))
+		}
+		if c.typer.oFSRSChanged {
+			c.output("fmt.Fprint(_output, ")
+		} else {
+			c.output("fmt.Fprintln(_output, ")
+		}
+		if len(s.Args) > 0 {
+			for i, arg := range s.Args {
+				if i > 0 {
+					c.output(", ")
+					if c.typer.oFSRSChanged {
+						c.output("OFS, ")
+					}
+				}
+				str := c.expr(arg)
+				if c.typer.exprs[arg] == typeNum {
+					str = fmt.Sprintf("_formatNum(%s)", str)
+				}
+				c.output(str)
+			}
+		} else {
+			// "print" with no args is equivalent to "print $0"
+			c.output("_line")
+		}
+		if c.typer.oFSRSChanged {
+			c.output(", ORS")
+		}
+		c.output(")")
+
+	case *PrintfStmt:
+		if s.Dest != nil {
+			panic(errorf("printf redirection not yet supported"))
+		}
+		formatExpr, ok := s.Args[0].(*StrExpr)
+		if !ok {
+			panic(errorf("printf currently only supports literal format strings"))
+		}
+		args := c.printfArgs(formatExpr.Value, s.Args[1:])
+		c.outputf("fmt.Fprintf(_output, %q", formatExpr.Value)
+		for _, arg := range args {
+			c.outputf(", %s", arg)
+		}
+		c.output(")")
+
+	case *IfStmt:
+		c.output("if ")
+		switch cond := s.Cond.(type) {
+		case *InExpr:
+			// if _, _ok := a[k]; ok { ... }
+			c.outputf("_, _ok := %s[%s]; _ok ", cond.Array.Name, c.index(cond.Index))
+		default:
+			c.output(c.cond(s.Cond))
+		}
+		c.output(" {\n")
+		c.stmts(s.Body)
+		c.output("}")
+		if len(s.Else) > 0 {
+			if _, isIf := s.Else[0].(*IfStmt); isIf && len(s.Else) == 1 {
+				// Simplify runs if-else if
+				c.output(" else ")
+				c.stmt(s.Else[0])
+			} else {
+				c.output(" else {\n")
+				c.stmts(s.Else)
+				c.output("}")
+			}
+		}
+
+	case *ForStmt:
+		c.output("for ")
+		if s.Pre != nil {
+			_, ok := s.Pre.(*ExprStmt)
+			if !ok {
+				panic(errorf(`only expressions are allowed in "for" initializer`))
+			}
+			c.stmtNoNewline(s.Pre)
+		}
+		c.output("; ")
+		if s.Cond != nil {
+			c.output(c.cond(s.Cond))
+		}
+		c.output("; ")
+		if s.Post != nil {
+			_, ok := s.Post.(*ExprStmt)
+			if !ok {
+				panic(errorf(`only expressions are allowed in "for" post expression`))
+			}
+			c.stmtNoNewline(s.Post)
+		}
+		c.output(" {\n")
+		c.stmts(s.Body)
+		c.output("}")
+
+	case *ForInStmt:
+		c.outputf("for %s = range %s {\n", s.Var.Name, s.Array.Name)
+		c.stmts(s.Body)
+		c.output("}")
+
+	case *ReturnStmt:
+		if s.Value != nil {
+			c.outputf("return %s", c.expr(s.Value))
+		} else {
+			c.output("return")
+		}
+
+	case *WhileStmt:
+		c.outputf("for %s {\n", c.cond(s.Cond))
+		c.stmts(s.Body)
+		c.output("}")
+
+	case *DoWhileStmt:
+		c.output("for {\n")
+		c.stmts(s.Body)
+		c.outputf("if !(%s) {\nbreak\n}\n}", c.cond(s.Cond))
+
+	case *BreakStmt:
+		c.output("break")
+
+	case *ContinueStmt:
+		c.output("continue")
+
+	case *NextStmt:
+		c.output("goto _nextLine")
+
+	case *ExitStmt:
+		if s.Status != nil {
+			c.outputf("os.Exit(%s)", c.intExpr(s.Status))
+		} else {
+			c.output("os.Exit(0)")
+		}
+
+	case *DeleteStmt:
+		if len(s.Index) > 0 {
+			// Delete single key from array
+			c.outputf("delete(%s, %s)", s.Array.Name, c.index(s.Index))
+		} else {
+			// Delete every element in array
+			c.outputf("for _k := range %s {\ndelete(%s, _k)\n}", s.Array.Name, s.Array.Name)
+		}
+
+	case *BlockStmt:
+		c.output("{\n")
+		c.stmts(s.Body)
+		c.output("}")
+
+	default:
+		panic(errorf("%T not yet supported", s))
+	}
+}
+
+func (c *compiler) stmt(stmt Stmt) {
+	c.stmtNoNewline(stmt)
+	c.output("\n")
+}
+
+type valueType int
+
+const (
+	typeUnknown valueType = iota
+	typeStr
+	typeNum
+	typeArrayStr
+	typeArrayNum
+)
+
+func (t valueType) String() string {
+	switch t {
+	case typeStr:
+		return "str"
+	case typeNum:
+		return "num"
+	case typeArrayStr:
+		return "array of str"
+	case typeArrayNum:
+		return "array of num"
+	default:
+		return "unknown"
+	}
+}
+
+func (c *compiler) expr(expr Expr) string {
+	switch e := expr.(type) {
+	case *NumExpr:
+		if e.Value == float64(int(e.Value)) {
+			return fmt.Sprintf("%d.0", int(e.Value))
+		}
+		if math.IsInf(e.Value, 0) {
+			panic(errorf("number literal out of range"))
+		}
+		return fmt.Sprintf("%g", e.Value)
+
+	case *StrExpr:
+		return strconv.Quote(e.Value)
+
+	case *FieldExpr:
+		return "_getField(" + c.intExpr(e.Index) + ")"
+
+	case *VarExpr:
+		switch e.Scope {
+		case ScopeSpecial:
+			return c.special(e.Name, e.Index)
+		case ScopeGlobal:
+			return e.Name
+		default:
+			panic(errorf("unexpected scope %v", e.Scope))
+		}
+
+	case *RegExpr:
+		return fmt.Sprintf("_boolToNum(%s.MatchString(_line))", c.regexLiteral(e.Regex))
+
+	case *BinaryExpr:
+		return c.binaryExpr(e.Op, e.Left, e.Right)
+
+	case *IncrExpr:
+		exprStr := c.expr(e.Expr) // will be an lvalue (VarExpr, IndexExpr, FieldExpr)
+		if e.Pre {
+			// Change ++x expression to:
+			// func() float64 { x++; return x }()
+			return fmt.Sprintf("func() float64 { %s%s; return %s }()",
+				exprStr, e.Op, exprStr)
+		} else {
+			// Change x++ expression to:
+			// func() float64 { _t := x; x++; return _t }()
+			return fmt.Sprintf("func() float64 { _t := %s; %s%s; return _t }()",
+				exprStr, exprStr, e.Op)
+		}
+
+	case *AssignExpr:
+		right := c.expr(e.Right)
+		switch l := e.Left.(type) {
+		case *VarExpr:
+			return fmt.Sprintf("func () %s { %s = %s; return %s }()",
+				c.goType(c.typer.exprs[e.Right]), l.Name, right, l.Name)
+
+		//TODO: case *IndexExpr:
+
+		//TODO: case *FieldExpr:
+
+		default:
+			panic(errorf("unexpected lvalue type: %T", l))
+		}
+
+	//TODO: case *AugAssignExpr:
+
+	case *CondExpr:
+		return fmt.Sprintf("func() %s { if %s { return %s }; return %s }()",
+			c.goType(c.typer.exprs[e]), c.cond(e.Cond), c.expr(e.True), c.expr(e.False))
+
+	case *IndexExpr:
+		switch e.Array.Scope {
+		case ScopeSpecial:
+			panic(errorf("special variable %s not yet supported", e.Array.Name))
+		case ScopeGlobal:
+			return e.Array.Name + "[" + c.index(e.Index) + "]"
+		default:
+			panic(errorf("unexpected scope %v", e.Array.Scope))
+		}
+
+	case *CallExpr:
+		switch e.Func {
+		case F_ATAN2:
+			return "math.Atan2(" + c.numExpr(e.Args[0]) + ", " + c.numExpr(e.Args[1]) + ")"
+
+		case F_COS:
+			return "math.Cos(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_EXP:
+			return "math.Exp(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_FFLUSH:
+			var arg string
+			if len(e.Args) > 0 {
+				switch argExpr := e.Args[0].(type) {
+				case *StrExpr:
+					arg = argExpr.Value
+				default:
+					arg = "not supported"
+				}
+			}
+			if arg != "" {
+				panic(errorf(`fflush() currently only supports no args or "" arg`))
+			}
+			return "_fflush()"
+
+		case F_INDEX:
+			return "float64(strings.Index(" + c.strExpr(e.Args[0]) + ", " + c.strExpr(e.Args[1]) + ") + 1)"
+
+		case F_INT:
+			return "float64(" + c.intExpr(e.Args[0]) + ")"
+
+		case F_LENGTH:
+			switch len(e.Args) {
+			case 0:
+				return "float64(len(_line))"
+			default:
+				return "float64(len(" + c.strExpr(e.Args[0]) + "))"
+			}
+
+		case F_LOG:
+			return "math.Log(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_MATCH:
+			if strExpr, ok := e.Args[1].(*StrExpr); ok {
+				return fmt.Sprintf("_match(%s, %s)", c.strExpr(e.Args[0]), c.regexLiteral(strExpr.Value))
+			}
+			return fmt.Sprintf("_match(%s, _reCompile(%s))", c.strExpr(e.Args[0]), c.strExpr(e.Args[1]))
+
+		case F_RAND:
+			return "_rand.Float64()"
+
+		case F_SIN:
+			return "math.Sin(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_SPLIT:
+			arrayArg := e.Args[1].(*ArrayExpr)
+			str := fmt.Sprintf("_split(%s, %s, ", c.strExpr(e.Args[0]), arrayArg.Name)
+			if len(e.Args) == 3 {
+				str += c.strExpr(e.Args[2])
+			} else {
+				str += "FS"
+			}
+			str += ")"
+			return str
+
+		case F_SPRINTF:
+			formatExpr, ok := e.Args[0].(*StrExpr)
+			if !ok {
+				panic(errorf("sprintf currently only supports literal format strings"))
+			}
+			args := c.printfArgs(formatExpr.Value, e.Args[1:])
+			str := fmt.Sprintf("fmt.Sprintf(%q", formatExpr.Value)
+			for _, arg := range args {
+				str += ", " + arg
+			}
+			str += ")"
+			return str
+
+		case F_SQRT:
+			return "math.Sqrt(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_SRAND:
+			if len(e.Args) == 0 {
+				return "_srandNow()"
+			}
+			return "_srand(" + c.numExpr(e.Args[0]) + ")"
+
+		case F_SUB, F_GSUB:
+			// sub() is actually an assignment to "in" (an lvalue) or $0:
+			// n = sub(re, repl[, in])
+			var reArg string
+			if strExpr, ok := e.Args[0].(*StrExpr); ok {
+				reArg = c.regexLiteral(strExpr.Value)
+			} else {
+				reArg = fmt.Sprintf("_reCompile(%s)", c.strExpr(e.Args[0]))
+			}
+			str := fmt.Sprintf("func() float64 { out, n := _sub(%s, %s, ", reArg, c.strExpr(e.Args[1]))
+			if len(e.Args) == 3 {
+				str += c.expr(e.Args[2])
+			} else {
+				str += "_line"
+			}
+			if e.Func == F_GSUB {
+				str += ", true); "
+			} else {
+				str += ", false); "
+			}
+			if len(e.Args) == 3 {
+				str += c.assign(e.Args[2], &VarExpr{Name: "out", Scope: ScopeGlobal})
+			} else {
+				str += "_setField(0, out)"
+			}
+			str += "; return float64(n) }()"
+			return str
+
+		case F_SUBSTR:
+			if len(e.Args) == 2 {
+				return "_substr(" + c.strExpr(e.Args[0]) + ", " + c.intExpr(e.Args[1]) + ")"
+			}
+			return "_substrLength(" + c.strExpr(e.Args[0]) + ", " + c.intExpr(e.Args[1]) + ", " + c.intExpr(e.Args[2]) + ")"
+
+		case F_SYSTEM:
+			return "_system(" + c.strExpr(e.Args[0]) + ")"
+
+		case F_TOLOWER:
+			return "strings.ToLower(" + c.expr(e.Args[0]) + ")"
+
+		case F_TOUPPER:
+			return "strings.ToUpper(" + c.expr(e.Args[0]) + ")"
+
+		default:
+			panic(errorf("%s() not yet supported", e.Func))
+		}
+
+	case *UnaryExpr:
+		str := c.expr(e.Value)
+		typ := c.typer.exprs[e.Value]
+		switch e.Op {
+		case SUB:
+			if typ == typeStr {
+				str = "_strToNum(" + str + ")"
+			}
+			return "(-" + str + ")"
+
+		case NOT:
+			return "_boolToNum(!(" + c.cond(e.Value) + "))"
+
+		case ADD:
+			if typ == typeStr {
+				str = "_strToNum(" + str + ")"
+			}
+			return "(+" + str + ")"
+
+		default:
+			panic(errorf("unexpected unary operation: %s", e.Op))
+		}
+
+	case *InExpr:
+		return fmt.Sprintf("func() float64 { _, ok := %s[%s]; if ok { return 1 }; return 0 }()",
+			e.Array.Name, c.index(e.Index))
+
+	default:
+		panic(errorf("%T not yet supported", expr))
+	}
+}
+
+func (c *compiler) binaryExpr(op Token, l, r Expr) (str string) {
+	switch op {
+	case ADD, SUB, MUL, DIV:
+		return "(" + c.numExpr(l) + " " + op.String() + " " + c.numExpr(r) + ")"
+	case MOD:
+		return "math.Mod(" + c.numExpr(l) + ", " + c.numExpr(r) + ")"
+	case POW:
+		return "math.Pow(" + c.numExpr(l) + ", " + c.numExpr(r) + ")"
+	case CONCAT:
+		return "(" + c.strExpr(l) + " + " + c.strExpr(r) + ")"
+	default:
+		s, ok := c.boolExpr(op, l, r)
+		if ok {
+			return "_boolToNum(" + s + ")"
+		}
+		panic(errorf("unexpected binary operator %s", op))
+	}
+}
+
+func (c *compiler) boolExpr(op Token, l, r Expr) (string, bool) {
+	switch op {
+	case EQUALS, LESS, LTE, GREATER, GTE, NOT_EQUALS:
+		_, leftIsField := l.(*FieldExpr)
+		_, rightIsField := r.(*FieldExpr)
+		if leftIsField && rightIsField {
+			panic(errorf("can't compare two fields directly (%s %s %s); convert one to string or number", l, op, r))
+		}
+		ls := c.expr(l)
+		rs := c.expr(r)
+		lt := c.typer.exprs[l]
+		rt := c.typer.exprs[r]
+		switch lt {
+		case typeNum:
+			switch rt {
+			case typeNum:
+				return fmt.Sprintf("(%s %s %s)", ls, op, rs), true
+			case typeStr:
+				return fmt.Sprintf("(_numToStr(%s) %s %s)", ls, op, rs), true
+			}
+		case typeStr:
+			switch rt {
+			case typeNum:
+				return fmt.Sprintf("(_strToNum(%s) %s %s)", ls, op, rs), true
+			case typeStr:
+				return fmt.Sprintf("(%s %s %s)", ls, op, rs), true
+			}
+		}
+		panic(errorf("unexpected types in %s (%s) %s %s (%s)", ls, lt, op, rs, rt))
+	case MATCH:
+		if strExpr, ok := r.(*StrExpr); ok {
+			return fmt.Sprintf("%s.MatchString(%s)", c.regexLiteral(strExpr.Value), c.strExpr(l)), true
+		}
+		return fmt.Sprintf("_reCompile(%s).MatchString(%s)", c.strExpr(l), c.strExpr(r)), true
+	case NOT_MATCH:
+		if strExpr, ok := r.(*StrExpr); ok {
+			return fmt.Sprintf("(!%s.MatchString(%s))", c.regexLiteral(strExpr.Value), c.strExpr(l)), true
+		}
+		return fmt.Sprintf("(!_reCompile(%s).MatchString(%s))", c.strExpr(l), c.strExpr(r)), true
+	case AND, OR:
+		return fmt.Sprintf("(%s %s %s)", c.cond(l), op, c.cond(r)), true
+	default:
+		return "", false
+	}
+}
+
+func (c *compiler) cond(expr Expr) string {
+	// If possible, simplify conditional expression to avoid "_boolToNum(b) != 0"
+	switch e := expr.(type) {
+	case *BinaryExpr:
+		str, ok := c.boolExpr(e.Op, e.Left, e.Right)
+		if ok {
+			return str
+		}
+	case *RegExpr:
+		return fmt.Sprintf("%s.MatchString(_line)", c.regexLiteral(e.Regex))
+	case *FieldExpr:
+		return fmt.Sprintf("_isFieldTrue(%s)", c.expr(e))
+	case *UnaryExpr:
+		if e.Op == NOT {
+			return "(!(" + c.cond(e.Value) + "))"
+		}
+	}
+
+	str := c.expr(expr)
+	if c.typer.exprs[expr] == typeStr {
+		str += ` != ""`
+	} else {
+		str += " != 0"
+	}
+	return str
+}
+
+func (c *compiler) numExpr(expr Expr) string {
+	str := c.expr(expr)
+	if c.typer.exprs[expr] == typeStr {
+		str = "_strToNum(" + str + ")"
+	}
+	return str
+}
+
+func (c *compiler) intExpr(expr Expr) string {
+	switch e := expr.(type) {
+	case *NumExpr:
+		return strconv.Itoa(int(e.Value))
+	case *UnaryExpr:
+		ne, ok := e.Value.(*NumExpr)
+		if ok && e.Op == SUB {
+			return "-" + strconv.Itoa(int(ne.Value))
+		}
+	}
+	return "int(" + c.numExpr(expr) + ")"
+}
+
+func (c *compiler) strExpr(expr Expr) string {
+	if fieldExpr, ok := expr.(*FieldExpr); ok {
+		if numExpr, ok := fieldExpr.Index.(*NumExpr); ok && numExpr.Value == 0 {
+			// Optimize _getField(0) to just _line
+			return "_line"
+		}
+	}
+	str := c.expr(expr)
+	if c.typer.exprs[expr] == typeNum {
+		str = "_numToStr(" + str + ")"
+	}
+	return str
+}
+
+func (c *compiler) index(index []Expr) string {
+	indexStr := ""
+	for i, e := range index {
+		if i > 0 {
+			indexStr += ` + SUBSEP + `
+		}
+		str := c.expr(e)
+		if c.typer.exprs[e] == typeNum {
+			str = "_numToStr(" + str + ")"
+		}
+		indexStr += str
+	}
+	return indexStr
+}
+
+func (c *compiler) special(name string, index int) string {
+	switch index {
+	case V_NF:
+		return "float64(len(_fields))"
+	case V_NR, V_FNR:
+		return "float64(_lineNum)"
+	case V_RLENGTH:
+		return "RLENGTH"
+	case V_RSTART:
+		return "RSTART"
+	case V_CONVFMT:
+		return "CONVFMT"
+	case V_FS:
+		return "FS"
+	case V_OFMT:
+		return "OFMT"
+	case V_OFS:
+		return "OFS"
+	case V_ORS:
+		return "ORS"
+	case V_SUBSEP:
+		return "SUBSEP"
+	default:
+		panic(errorf("special variable %s not yet supported", name))
+	}
+}
+
+func (c *compiler) goType(typ valueType) string {
+	switch typ {
+	case typeNum:
+		return "float64"
+	case typeStr:
+		return "string"
+	case typeArrayNum:
+		return "map[string]float64"
+	case typeArrayStr:
+		return "map[string]string"
+	default:
+		panic(errorf("can't convert type %s to Go type", typ))
+	}
+}
+
+func (c *compiler) printfArgs(format string, args []Expr) []string {
+	argIndex := 0
+	nextArg := func() Expr {
+		if argIndex >= len(args) {
+			panic(errorf("not enough arguments (%d) for format string %q", len(args), format))
+		}
+		arg := args[argIndex]
+		argIndex++
+		return arg
+	}
+
+	var argStrs []string
+	for i := 0; i < len(format); i++ {
+		if format[i] == '%' {
+			i++
+			if i >= len(format) {
+				panic(errorf("expected type specifier after %%"))
+			}
+			if format[i] == '%' {
+				continue
+			}
+			for i < len(format) && bytes.IndexByte([]byte(" .-+*#0123456789"), format[i]) >= 0 {
+				if format[i] == '*' {
+					argStrs = append(argStrs, c.intExpr(nextArg()))
+				}
+				i++
+			}
+			if i >= len(format) {
+				panic(errorf("expected type specifier after %%"))
+			}
+			var argStr string
+			switch format[i] {
+			case 's':
+				argStr = c.strExpr(nextArg())
+			case 'd', 'i', 'o', 'x', 'X', 'u':
+				argStr = c.intExpr(nextArg())
+			case 'f', 'e', 'E', 'g', 'G':
+				argStr = "float64(" + c.numExpr(nextArg()) + ")"
+			case 'c':
+				arg := nextArg()
+				if c.typer.exprs[arg] == typeStr {
+					argStr = fmt.Sprintf("_firstRune(%s)", arg)
+				} else {
+					argStr = c.intExpr(arg)
+				}
+			default:
+				panic(errorf("invalid format type %q", format[i]))
+			}
+			argStrs = append(argStrs, argStr)
+		}
+	}
+	return argStrs
+}
+
+func (c *compiler) regexLiteral(pattern string) string {
+	n, ok := c.regexen[pattern]
+	if !ok {
+		n = len(c.regexen) + 1
+		c.regexen[pattern] = n
+	}
+	varName := fmt.Sprintf("_re%d", n)
+	return varName
+}
diff --git a/awkgo/helpers.go b/awkgo/helpers.go
new file mode 100644
index 0000000..83f1b12
--- /dev/null
+++ b/awkgo/helpers.go
@@ -0,0 +1,289 @@
+package main
+
+func (c *compiler) outputHelpers() {
+	c.output(`
+func _getField(i int) string {
+	if i < 0 || i > len(_fields) {
+		return ""
+	}
+	if i == 0 {
+		return _line
+	}
+	return _fields[i-1]
+}
+
+func _setField(i int, s string) {
+	if i == 0 {
+		_line = s
+		_fields = _splitHelper(_line, FS)
+		return
+	}
+	for j := len(_fields); j < i; j++ {
+		_fields = append(_fields, "")
+	}
+	_fields[i-1] = s
+	_line = strings.Join(_fields, OFS)
+}
+
+func _boolToNum(b bool) float64 {
+	if b {
+		return 1
+	}
+	return 0
+}
+
+func _numToStrFormat(format string, n float64) string {
+	switch {
+	case math.IsNaN(n):
+		return "nan"
+	case math.IsInf(n, 0):
+		if n < 0 {
+			return "-inf"
+		} else {
+			return "inf"
+		}
+	case n == float64(int(n)):
+		return strconv.Itoa(int(n))
+	default:
+		return fmt.Sprintf(format, n)
+	}
+}
+
+func _numToStr(n float64) string {
+	return _numToStrFormat(CONVFMT, n)
+}
+
+func _formatNum(n float64) string {
+	return _numToStrFormat(OFMT, n)
+}
+
+var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
+
+// Like strconv.ParseFloat, but parses at the start of string and
+// allows things like "1.5foo".
+func _strToNum(s string) float64 {
+	// Skip whitespace at start
+	i := 0
+	for i < len(s) && asciiSpace[s[i]] != 0 {
+		i++
+	}
+	start := i
+
+	// Parse mantissa: optional sign, initial digit(s), optional '.',
+	// then more digits
+	gotDigit := false
+	if i < len(s) && (s[i] == '+' || s[i] == '-') {
+		i++
+	}
+	for i < len(s) && s[i] >= '0' && s[i] <= '9' {
+		gotDigit = true
+		i++
+	}
+	if i < len(s) && s[i] == '.' {
+		i++
+	}
+	for i < len(s) && s[i] >= '0' && s[i] <= '9' {
+		gotDigit = true
+		i++
+	}
+	if !gotDigit {
+		return 0
+	}
+
+	// Parse exponent ("1e" and similar are allowed, but ParseFloat
+	// rejects them)
+	end := i
+	if i < len(s) && (s[i] == 'e' || s[i] == 'E') {
+		i++
+		if i < len(s) && (s[i] == '+' || s[i] == '-') {
+			i++
+		}
+		for i < len(s) && s[i] >= '0' && s[i] <= '9' {
+			i++
+			end = i
+		}
+	}
+
+	floatStr := s[start:end]
+	f, _ := strconv.ParseFloat(floatStr, 64)
+	return f // Returns infinity in case of "value out of range" error
+}
+
+func _isFieldTrue(s string) bool {
+	if s == "" {
+		return false
+	}
+	f, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
+	if err != nil {
+		return true
+	}
+	return f != 0
+}
+
+var _reCache = make(map[string]*regexp.Regexp)
+
+func _reCompile(pattern string) *regexp.Regexp {
+	if re, ok := _reCache[pattern]; ok {
+		return re
+	}
+	re := regexp.MustCompile(pattern)
+	// Dumb, non-LRU cache: just cache the first N regexes
+	if len(_reCache) < 100 {
+		_reCache[pattern] = re
+	}
+	return re
+}
+
+func _match(str string, re *regexp.Regexp) float64 {
+	loc := re.FindStringIndex(str)
+	if len(loc) > 0 {
+		RSTART = float64(loc[0] + 1)
+		RLENGTH = float64(loc[1] - loc[0])
+	} else {
+		RSTART = 0
+		RLENGTH = -1
+	}
+	return RSTART
+}
+
+func _srand(seed float64) float64 {
+	prev := _seed
+	_seed = seed
+	_rand.Seed(int64(math.Float64bits(seed)))
+	return prev
+}
+
+func _srandNow() float64 {
+	prev := _seed
+	_rand.Seed(time.Now().UnixNano())
+	return prev
+}
+
+func _substrLength(s string, pos, length int) string {
+	if pos > len(s) {
+		pos = len(s) + 1
+	}
+	if pos < 1 {
+		pos = 1
+	}
+	if length < 0 {
+		length = 0
+	}
+	maxLength := len(s) - pos + 1
+	if length > maxLength {
+		length = maxLength
+	}
+	return s[pos-1 : pos-1+length]
+}
+
+func _substr(s string, pos int) string {
+	if pos > len(s) {
+		pos = len(s) + 1
+	}
+	if pos < 1 {
+		pos = 1
+	}
+	length := len(s) - pos + 1
+	return s[pos-1 : pos-1+length]
+}
+
+func _firstRune(s string) int {
+	r, n := utf8.DecodeRuneInString(s)
+	if n == 0 {
+		return 0
+	}
+	return int(r)
+}
+
+func _splitHelper(s, fs string) []string {
+	var parts []string
+	if fs == " " {
+		parts = strings.Fields(s)
+	} else if s == "" {
+		// NF should be 0 on empty line
+	} else if utf8.RuneCountInString(fs) <= 1 {
+		parts = strings.Split(s, fs)
+	} else {
+		parts = _reCompile(fs).Split(s, -1)
+	}
+	return parts
+}
+
+func _split(s string, a map[string]string, fs string) float64 {
+	parts := _splitHelper(s, fs)
+	for k := range a {
+		delete(a, k)
+	}
+	for i, part := range parts {
+		a[strconv.Itoa(i+1)] = part
+	}
+	return float64(len(a))
+}
+
+func _sub(re *regexp.Regexp, repl, in string, global bool) (out string, num int) {
+	count := 0
+	out = re.ReplaceAllStringFunc(in, func(s string) string {
+		// Only do the first replacement for sub(), or all for gsub()
+		if !global && count > 0 {
+			return s
+		}
+		count++
+		// Handle & (ampersand) properly in replacement string
+		r := make([]byte, 0, 64) // Up to 64 byte replacement won't require heap allocation
+		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
+}
+
+func _system(command string) float64 {
+	cmd := exec.Command("/bin/sh", "-c", command)
+	cmd.Stdout = _output
+	cmd.Stderr = os.Stderr
+	err := cmd.Start()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		return -1
+	}
+	err = cmd.Wait()
+	if err != nil {
+		if exitErr, ok := err.(*exec.ExitError); ok {
+			return float64(exitErr.ProcessState.ExitCode())
+		}
+		fmt.Fprintf(os.Stderr, "unexpected error running command %q: %v\n", command, err)
+		return -1
+	}
+	return 0
+}
+
+func _fflush() float64 {
+	err := _output.Flush()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error flushing output: %v\n", err)
+		return -1
+	}
+	return 0
+}
+`)
+}
diff --git a/awkgo/typer.go b/awkgo/typer.go
new file mode 100644
index 0000000..eb3bfa7
--- /dev/null
+++ b/awkgo/typer.go
@@ -0,0 +1,374 @@
+// AWKGo: type
+
+package main
+
+import (
+	. "github.com/benhoyt/goawk/internal/ast"
+	. "github.com/benhoyt/goawk/lexer"
+	. "github.com/benhoyt/goawk/parser"
+)
+
+// typer walks the parse tree and builds a mappings of variables and
+// expressions to their types.
+type typer struct {
+	globals      map[string]valueType
+	scalarRefs   map[string]bool
+	arrayRefs    map[string]bool
+	exprs        map[Expr]valueType
+	funcName     string // function name if inside a func, else ""
+	nextUsed     bool
+	oFSRSChanged bool
+}
+
+func newTyper() *typer {
+	t := &typer{
+		globals:    make(map[string]valueType),
+		scalarRefs: make(map[string]bool),
+		arrayRefs:  make(map[string]bool),
+		exprs:      make(map[Expr]valueType),
+	}
+	t.globals["FS"] = typeStr
+	t.globals["OFS"] = typeStr
+	t.globals["ORS"] = typeStr
+	t.globals["OFMT"] = typeStr
+	t.globals["CONVFMT"] = typeStr
+	t.globals["RSTART"] = typeNum
+	t.globals["RLENGTH"] = typeNum
+	t.globals["SUBSEP"] = typeStr
+	return t
+}
+
+func (t *typer) program(prog *Program) {
+	for _, stmts := range prog.Begin {
+		t.stmts(stmts)
+	}
+	t.actions(prog.Actions)
+	for _, stmts := range prog.End {
+		t.stmts(stmts)
+	}
+	for range prog.Functions {
+		panic(errorf("functions not yet supported"))
+	}
+
+	for name := range t.scalarRefs {
+		if t.globals[name] == typeUnknown {
+			panic(errorf("type of %q not known; need assignment?", name))
+		}
+	}
+	for name := range t.arrayRefs {
+		if t.globals[name] == typeUnknown {
+			panic(errorf("type of array %q not known; need array assignment?", name))
+		}
+	}
+}
+
+func (t *typer) stmts(stmts Stmts) {
+	for _, stmt := range stmts {
+		t.stmt(stmt)
+	}
+}
+
+func (t *typer) actions(actions []Action) {
+	for _, action := range actions {
+		for _, e := range action.Pattern {
+			t.expr(e)
+		}
+		t.stmts(action.Stmts)
+	}
+}
+
+func (t *typer) stmt(stmt Stmt) {
+	switch s := stmt.(type) {
+	case *PrintStmt:
+		for _, arg := range s.Args {
+			t.expr(arg)
+		}
+		if s.Dest != nil {
+			t.expr(s.Dest)
+		}
+
+	case *PrintfStmt:
+		for _, arg := range s.Args {
+			t.expr(arg)
+		}
+		if s.Dest != nil {
+			t.expr(s.Dest)
+		}
+
+	case *ExprStmt:
+		t.expr(s.Expr)
+
+	case *IfStmt:
+		t.expr(s.Cond)
+		t.stmts(s.Body)
+		t.stmts(s.Else)
+
+	case *ForStmt:
+		if s.Pre != nil {
+			t.stmt(s.Pre)
+		}
+		if s.Cond != nil {
+			t.expr(s.Cond)
+		}
+		if s.Post != nil {
+			t.stmt(s.Post)
+		}
+		t.stmts(s.Body)
+
+	case *ForInStmt:
+		t.setType(s.Var.Name, typeStr)
+		t.stmts(s.Body)
+
+	case *WhileStmt:
+		t.expr(s.Cond)
+		t.stmts(s.Body)
+
+	case *DoWhileStmt:
+		t.stmts(s.Body)
+		t.expr(s.Cond)
+
+	case *BreakStmt, *ContinueStmt:
+		return
+
+	case *NextStmt:
+		if t.funcName != "" {
+			panic(errorf(`"next" inside a function not yet supported`))
+		}
+		t.nextUsed = true
+		return
+
+	case *ExitStmt:
+		if s.Status != nil {
+			t.expr(s.Status)
+		}
+
+	case *DeleteStmt:
+		for _, index := range s.Index {
+			t.expr(index)
+		}
+
+	case *ReturnStmt:
+		if s.Value != nil {
+			t.expr(s.Value)
+		}
+
+	case *BlockStmt:
+		t.stmts(s.Body)
+
+	default:
+		panic(errorf("unexpected statement type %T", stmt))
+	}
+}
+
+func (t *typer) setType(name string, typ valueType) {
+	if t.globals[name] == typ {
+		return
+	}
+	if t.globals[name] != typeUnknown {
+		panic(errorf("variable %q already set to %s, can't set to %s",
+			name, t.globals[name], typ))
+	}
+	if typ != typeUnknown {
+		t.globals[name] = typ
+	}
+}
+
+func (t *typer) expr(expr Expr) (typ valueType) {
+	defer func() {
+		if typ != typeUnknown {
+			t.exprs[expr] = typ
+		}
+	}()
+
+	switch e := expr.(type) {
+	case *FieldExpr:
+		t.expr(e.Index)
+		return typeStr
+
+	case *UnaryExpr:
+		t.expr(e.Value)
+		return typeNum
+
+	case *BinaryExpr:
+		t.expr(e.Left)
+		t.expr(e.Right)
+		if e.Op == CONCAT {
+			return typeStr
+		}
+		return typeNum
+
+	case *ArrayExpr:
+		return typeUnknown
+
+	case *InExpr:
+		for _, index := range e.Index {
+			t.expr(index)
+		}
+		t.expr(e.Array)
+		return typeNum
+
+	case *CondExpr:
+		t.expr(e.Cond)
+		trueType := t.expr(e.True)
+		falseType := t.expr(e.False)
+		if trueType != falseType {
+			panic(errorf("both branches of ?: must yield same type (first is %s, second is %s)",
+				trueType, falseType))
+		}
+		return trueType
+
+	case *NumExpr:
+		return typeNum
+
+	case *StrExpr:
+		return typeStr
+
+	case *RegExpr:
+		return typeNum
+
+	case *VarExpr:
+		switch e.Scope {
+		case ScopeSpecial:
+			return t.specialType(e.Name, e.Index)
+		case ScopeGlobal:
+			t.scalarRefs[e.Name] = true
+			return t.globals[e.Name]
+		default:
+			panic(errorf("unexpected scope %v", e.Scope))
+		}
+
+	case *IndexExpr:
+		t.arrayRefs[e.Array.Name] = true
+		t.expr(e.Array)
+		for _, index := range e.Index {
+			t.expr(index)
+		}
+		switch t.globals[e.Array.Name] {
+		case typeArrayStr:
+			return typeStr
+		case typeArrayNum:
+			return typeNum
+		}
+		return typeUnknown
+
+	case *AssignExpr:
+		rightType := t.expr(e.Right)
+		switch left := e.Left.(type) {
+		case *VarExpr:
+			// x = right
+			t.setType(left.Name, rightType)
+			if left.Name == "OFS" || left.Name == "ORS" {
+				t.oFSRSChanged = true
+			}
+		case *IndexExpr:
+			// m[k] = right
+			switch rightType {
+			case typeStr:
+				t.setType(left.Array.Name, typeArrayStr)
+			case typeNum:
+				t.setType(left.Array.Name, typeArrayNum)
+			}
+		case *FieldExpr:
+			// $1 = right
+		}
+		t.expr(e.Left)
+		return rightType
+
+	case *AugAssignExpr:
+		t.expr(e.Right)
+		switch left := e.Left.(type) {
+		case *VarExpr:
+			// x += right
+			t.setType(left.Name, typeNum)
+			if left.Name == "OFS" || left.Name == "ORS" {
+				t.oFSRSChanged = true
+			}
+		case *IndexExpr:
+			// m[k] += right
+			t.setType(left.Array.Name, typeArrayNum)
+		case *FieldExpr:
+			// $1 += right
+		}
+		t.expr(e.Left)
+		return typeNum
+
+	case *IncrExpr:
+		switch left := e.Expr.(type) {
+		case *VarExpr:
+			// x++
+			t.setType(left.Name, typeNum)
+			if left.Name == "OFS" || left.Name == "ORS" {
+				t.oFSRSChanged = true
+			}
+		case *IndexExpr:
+			// m[k]++
+			t.setType(left.Array.Name, typeArrayNum)
+		case *FieldExpr:
+			// $1++
+		}
+		t.expr(e.Expr)
+		return typeNum
+
+	case *CallExpr:
+		switch e.Func {
+		case F_SPLIT:
+			// split's second arg is an array arg
+			t.expr(e.Args[0])
+			arrayExpr := e.Args[1].(*ArrayExpr)
+			if t.globals[arrayExpr.Name] != typeUnknown && t.globals[arrayExpr.Name] != typeArrayStr {
+				panic(errorf("%q already set to %s, can't use as %s in split()",
+					arrayExpr.Name, t.globals[arrayExpr.Name], typeArrayStr))
+			}
+			t.globals[arrayExpr.Name] = typeArrayStr
+			if len(e.Args) == 3 {
+				t.expr(e.Args[2])
+			}
+			return typeNum
+		case F_SUB, F_GSUB:
+			t.expr(e.Args[0])
+			t.expr(e.Args[1])
+			if len(e.Args) == 3 {
+				// sub and gsub's third arg is actually an lvalue
+				switch left := e.Args[2].(type) {
+				case *VarExpr:
+					t.setType(left.Name, typeStr)
+				case *IndexExpr:
+					t.setType(left.Array.Name, typeArrayStr)
+				}
+			}
+			return typeNum
+		}
+		for _, arg := range e.Args {
+			t.expr(arg)
+		}
+		switch e.Func {
+		case F_ATAN2, F_CLOSE, F_COS, F_EXP, F_FFLUSH, F_INDEX, F_INT, F_LENGTH,
+			F_LOG, F_MATCH, F_RAND, F_SIN, F_SQRT, F_SRAND, F_SYSTEM:
+			return typeNum
+		case F_SPRINTF, F_SUBSTR, F_TOLOWER, F_TOUPPER:
+			return typeStr
+		default:
+			panic(errorf("unexpected function %s", e.Func))
+		}
+
+	case *UserCallExpr:
+		panic(errorf("functions not yet supported"))
+
+	case *GetlineExpr:
+		return typeNum
+
+	default:
+		panic(errorf("unexpected expression type %T", expr))
+	}
+}
+
+func (t *typer) specialType(name string, index int) valueType {
+	switch index {
+	case V_NF, V_NR, V_RLENGTH, V_RSTART, V_FNR, V_ARGC:
+		return typeNum
+	case V_CONVFMT, V_FILENAME, V_FS, V_OFMT, V_OFS, V_ORS, V_RS, V_SUBSEP:
+		return typeStr
+	default:
+		panic(errorf("unexpected special variable %s", name))
+	}
+}
diff --git a/interp/value.go b/interp/value.go
index 7c15e22..46c7225 100644
--- a/interp/value.go
+++ b/interp/value.go
@@ -96,7 +96,7 @@ func (v value) str(floatFormat string) string {
 			return fmt.Sprintf(floatFormat, v.n)
 		}
 	}
-	// For typeStr and typeStrNum we already have the string, for
+	// For typeStr and typeNumStr we already have the string, for
 	// typeNull v.s == "".
 	return v.s
 }
@@ -107,7 +107,7 @@ func (v value) num() float64 {
 		// Ensure string starts with a float and convert it
 		return parseFloatPrefix(v.s)
 	}
-	// Handle case for typeNum and typeStrNum. If it's a numeric
+	// Handle case for typeNum and typeNumStr. If it's a numeric
 	// string, we already have the float value from the numStr()
 	// call. For typeNull v.n == 0.
 	return v.n
diff --git a/parser/parser.go b/parser/parser.go
index 2005fb5..675c4c0 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -366,8 +366,8 @@ func (p *parser) stmt() Stmt {
 		p.next()
 		s = &ContinueStmt{}
 	case NEXT:
-		if !p.inAction {
-			panic(p.error("next can't be in BEGIN or END"))
+		if !p.inAction && p.funcName == "" {
+			panic(p.error("next can't be inside BEGIN or END"))
 		}
 		p.next()
 		s = &NextStmt{}


Test patch:
diff --git a/awkgo/awkgo_test.go b/awkgo/awkgo_test.go
new file mode 100644
index 0000000..05cdacb
--- /dev/null
+++ b/awkgo/awkgo_test.go
@@ -0,0 +1,672 @@
+// This is copied and hacked from goawk/interp/interp_test.go
+
+//go:build awkgotest
+// +build awkgotest
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/benhoyt/goawk/parser"
+)
+
+type interpTest struct {
+	src string
+	in  string
+	out string
+	err string
+}
+
+var interpTests = []interpTest{
+	// BEGIN and END work correctly
+	{`BEGIN { print "b" }`, "", "b\n", ""},
+	{`BEGIN { print "b" }`, "foo", "b\n", ""},
+	{`END { print "e" }`, "", "e\n", ""},
+	{`END { print "e" }`, "foo", "e\n", ""},
+	{`BEGIN { print "b"} END { print "e" }`, "", "b\ne\n", ""},
+	{`BEGIN { print "b"} END { print "e" }`, "foo", "b\ne\n", ""},
+	{`BEGIN { print "b"} $0 { print NR } END { print "e" }`, "foo", "b\n1\ne\n", ""},
+	{`BEGIN { printf "x" }; BEGIN { printf "y" }`, "", "xy", ""},
+
+	// Patterns
+	{`$0`, "foo\n\nbar", "foo\nbar\n", ""},
+	{`{ print $0 }`, "foo\n\nbar", "foo\n\nbar\n", ""},
+	{`$1=="foo"`, "foo\n\nbar", "foo\n", ""},
+	{`$1==42`, "foo\n42\nbar", "42\n", ""},
+	{`$1=="42"`, "foo\n42\nbar", "42\n", ""},
+	{`/foo/`, "foo\nx\nfood\nxfooz\nbar", "foo\nfood\nxfooz\n", ""},
+	{`NR==2, NR==4`, "1\n2\n3\n4\n5\n6\n", "2\n3\n4\n", ""},
+	{`
+NR==2, NR==4 { print $0 }
+NR==3, NR==5 { print NR }
+`, "a\nb\nc\nd\ne\nf\ng", "b\nc\n3\nd\n4\n5\n", ""},
+
+	// print and printf statements
+	{`BEGIN { print "x", "y" }`, "", "x y\n", ""},
+	{`BEGIN { print OFS; OFS = ","; print "x", "y" }`, "", " \nx,y\n", ""},
+	{`BEGIN { print ORS; ORS = "."; print "x", "y" }`, "", "\n\nx y.", ""},
+	{`BEGIN { print ORS; ORS = ""; print "x", "y" }`, "", "\n\nx y", ""},
+	{`{ print; print }`, "foo", "foo\nfoo\n", ""},
+	{`BEGIN { print; print }`, "", "\n\n", ""},
+	{`BEGIN { printf "%% %d %x %c %f %s", 42, 42, 42, 42, 42 }`, "", "% 42 2a * 42.000000 42", ""},
+	{`BEGIN { printf "%3d", 42 }`, "", " 42", ""},
+	{`BEGIN { printf "%3s", "x" }`, "", "  x", ""},
+	// {`BEGIN { printf "%.1g", 42 }`, "", "4e+01", ""}, // TODO: comment out for now, for some reason gives "4e+001" on Windows
+	{`BEGIN { printf "%d", 12, 34 }`, "", "12", ""},
+	{`BEGIN { printf "%d" }`, "", "", `not enough arguments (0) for format string "%d"`},
+	// Our %c handling is mostly like awk's, except for multiples
+	// 256, where awk is weird and we're like mawk
+	{`BEGIN { printf "%c", 0 }`, "", "\x00", ""},
+	{`BEGIN { printf "%c", 127 }`, "", "\x7f", ""},
+	{`BEGIN { printf "%c", 128 }  # !gawk`, "", "\u0080", ""},
+	{`BEGIN { printf "%c", 255 }  # !gawk`, "", "\u00ff", ""},
+	{`BEGIN { printf "%c", 256 }  # !awk !gawk`, "", "\u0100", ""},
+	{`BEGIN { printf "%c", "xyz" }`, "", "x", ""},
+	{`BEGIN { printf "%c", "" }  # !awk`, "", "\x00", ""},
+	{`BEGIN { printf }  # !awk - doesn't error on this`, "", "", "parse error at 1:16: expected printf args, got none"},
+	{`BEGIN { printf("%%%dd", 4) }`, "", "%4d", ""},
+
+	// if and loop statements
+	{`BEGIN { if (1) print "t"; }`, "", "t\n", ""},
+	{`BEGIN { if (0) print "t"; }`, "", "", ""},
+	{`BEGIN { if (1) print "t"; else print "f" }`, "", "t\n", ""},
+	{`BEGIN { if (0) print "t"; else print "f" }`, "", "f\n", ""},
+	{`BEGIN { for (;;) { print "x"; break } }`, "", "x\n", ""},
+	{`BEGIN { for (;;) { printf "%d ", i; i++; if (i>2) break; } }`, "", "0 1 2 ", ""},
+	{`BEGIN { for (i=5; ; ) { printf "%d ", i; i++; if (i>8) break; } }`, "", "5 6 7 8 ", ""},
+	{`BEGIN { for (i=5; ; i++) { printf "%d ", i; if (i>8) break; } }`, "", "5 6 7 8 9 ", ""},
+	{`BEGIN { for (i=5; i<8; i++) { printf "%d ", i } }`, "", "5 6 7 ", ""},
+	{`BEGIN { for (i=0; i<10; i++) { if (i < 5) continue; printf "%d ", i } }`, "", "5 6 7 8 9 ", ""},
+	{`BEGIN { a[1]=1; a[2]=1; for (k in a) { s++; break } print s }`, "", "1\n", ""},
+	{`BEGIN { a[1]=1; a[2]=1; a[3]=1; for (k in a) { if (k==2) continue; s++ } print s }`, "", "2\n", ""},
+	{`BEGIN { while (i<3) { i++; s++; break } print s }`, "", "1\n", ""},
+	{`BEGIN { while (i<3) { i++; if (i==2) continue; s++ } print s }`, "", "2\n", ""},
+	{`BEGIN { do { i++; s++; break } while (i<3); print s }`, "", "1\n", ""},
+	{`BEGIN { do { i++; if (i==2) continue; s++ } while (i<3); print s }`, "", "2\n", ""},
+	{`BEGIN { a["x"] = 3; a["y"] = 4; for (k in a) x += a[k]; print x }`, "", "7\n", ""},
+	{`BEGIN { while (i < 5) { print i; i++ } }`, "", "0\n1\n2\n3\n4\n", ""},
+	{`BEGIN { do { print i; i++ } while (i < 5) }`, "", "0\n1\n2\n3\n4\n", ""},
+	{`BEGIN { for (i=0; i<10; i++); printf "x" }`, "", "x", ""},
+	// regression tests for break and continue with nested loops
+	{`
+BEGIN {
+	for (i = 0; i < 1; i++) {
+		for (j = 0; j < 1; j++) {
+			print i, j
+		}
+		break
+	}
+}
+`, "", "0 0\n", ""},
+	{`
+BEGIN {
+	for (i = 0; i < 1; i++) {
+		for (j = 0; j < 1; j++) {
+			print i, j
+		}
+		continue
+	}
+}
+`, "", "0 0\n", ""},
+
+	// next statement
+	{`{ if (NR==2) next; print }`, "a\nb\nc", "a\nc\n", ""},
+	//{`{ if (NR==2) f(); print }  function f() { next }`, "a\nb\nc", "a\nc\n", "functions not yet supported"},
+	{`BEGIN { next }`, "", "", "parse error at 1:9: next can't be inside BEGIN or END"},
+	{`END { next }`, "", "", "parse error at 1:7: next can't be inside BEGIN or END"},
+
+	// Arrays, "in", and delete
+	{`BEGIN { a["x"] = 3; print "x" in a, "y" in a }`, "", "1 0\n", ""},
+	{`BEGIN { a["x"] = 3; a["y"] = 4; delete a["x"]; for (k in a) print k, a[k] }`, "", "y 4\n", ""},
+	{`BEGIN { a["x"] = 3; a["y"] = 4; for (k in a) delete a[k]; for (k in a) print k, a[k] }`, "", "", ""},
+	//{`BEGIN { a["x"]; "y" in a; for (k in a) print k, a[k] }`, "", "x \n", ""},
+	{`BEGIN { a[] }`, "", "", "parse error at 1:11: expected expression instead of ]"},
+	{`BEGIN { delete a[] }`, "", "", "parse error at 1:18: expected expression instead of ]"},
+	{`BEGIN { a["x"] = 3; a["y"] = 4; delete a; for (k in a) print k, a[k] }`, "", "", ""},
+
+	// Unary expressions: ! + -
+	{`BEGIN { print !42, !1, !0, !!42, !!1, !!0 }`, "", "0 0 1 1 1 0\n", ""},
+	{`BEGIN { print !42, !1, !0, !!42, !!1, !!0 }`, "", "0 0 1 1 1 0\n", ""},
+	{`BEGIN { print +4, +"3", +0, +-3, -3, - -4, -"3" }`, "", "4 3 0 -3 -3 4 -3\n", ""},
+	// TODO: {`BEGIN { $0="0"; print !$0 }`, "", "0\n", ""},
+	{`BEGIN { $0="1"; print !$0 }`, "", "0\n", ""},
+	{`{ print !$0 }`, "0\n", "1\n", ""},
+	{`{ print !$0 }`, "1\n", "0\n", ""},
+	{`!seen[$0]++`, "1\n2\n3\n2\n3\n3\n", "1\n2\n3\n", ""},
+	{`!seen[$0]--`, "1\n2\n3\n2\n3\n3\n", "1\n2\n3\n", ""},
+
+	// Comparison expressions: == != < <= > >=
+	{`BEGIN { print (1==1, 1==0, "1"==1, "1"==1.0) }`, "", "1 0 1 1\n", ""},
+	{`{ print ($0=="1", $0==1) }`, "1\n1.0\n+1", "1 1\n0 1\n0 1\n", ""},
+	{`{ print ($1=="1", $1==1) }`, "1\n1.0\n+1", "1 1\n0 1\n0 1\n", ""},
+	{`BEGIN { print (1!=1, 1!=0, "1"!=1, "1"!=1.0) }`, "", "0 1 0 0\n", ""},
+	{`{ print ($0!="1", $0!=1) }`, "1\n1.0\n+1", "0 0\n1 0\n1 0\n", ""},
+	{`{ print ($1!="1", $1!=1) }`, "1\n1.0\n+1", "0 0\n1 0\n1 0\n", ""},
+	{`BEGIN { print (0<1, 1<1, 2<1, "12"<"2") }`, "", "1 0 0 1\n", ""},
+	{`{ print ($1<2) }`, "1\n1.0\n+1", "1\n1\n1\n", ""},
+	{`BEGIN { print (0<=1, 1<=1, 2<=1, "12"<="2") }`, "", "1 1 0 1\n", ""},
+	{`{ print ($1<=2) }`, "1\n1.0\n+1", "1\n1\n1\n", ""},
+	{`BEGIN { print (0>1, 1>1, 2>1, "12">"2") }`, "", "0 0 1 0\n", ""},
+	{`{ print ($1>2) }`, "1\n1.0\n+1", "0\n0\n0\n", ""},
+	{`BEGIN { print (0>=1, 1>=1, 2>=1, "12">="2") }`, "", "0 1 1 0\n", ""},
+	{`{ print ($1>=2) }`, "1\n1.0\n+1", "0\n0\n0\n", ""},
+
+	// Short-circuit && and || operators
+	//	{`
+	//function t() { print "t"; return 1 }
+	//function f() { print "f"; return 0 }
+	//BEGIN {
+	//	print f() && f()
+	//	print f() && t()
+	//	print t() && f()
+	//	print t() && t()
+	//}
+	//`, "", "f\n0\nf\n0\nt\nf\n0\nt\nt\n1\n", ""},
+	//	{`
+	//function t() { print "t"; return 1 }
+	//function f() { print "f"; return 0 }
+	//BEGIN {
+	//	print f() || f()
+	//	print f() || t()
+	//	print t() || f()
+	//	print t() || t()
+	//}
+	//`, "", "f\nf\n0\nf\nt\n1\nt\n1\nt\n1\n", ""},
+
+	// Other binary expressions: + - * ^ / % CONCAT ~ !~
+	{`BEGIN { print 1+2, 1+2+3, 1+-2, -1+2, "1"+"2", 3+.14 }`, "", "3 6 -1 1 3 3.14\n", ""},
+	{`BEGIN { print 1-2, 1-2-3, 1-+2, -1-2, "1"-"2", 3-.14 }`, "", "-1 -4 -1 -3 -1 2.86\n", ""},
+	{`BEGIN { print 2*3, 2*3*4, 2*-3, -2*3, "2"*"3", 3*.14 }`, "", "6 24 -6 -6 6 0.42\n", ""},
+	{`BEGIN { print 2/3, 2/3/4, 2/-3, -2/3, "2"/"3", 3/.14 }`, "", "0.666667 0.166667 -0.666667 -0.666667 0.666667 21.4286\n", ""},
+	{`BEGIN { print 2%3, 2%3%4, 2%-3, -2%3, "2"%"3", 3%.14 }`, "", "2 2 2 -2 2 0.06\n", ""},
+	{`BEGIN { print 2^3, 2^3^3, 2^-3, -2^3, "2"^"3", 3^.14 }`, "", "8 134217728 0.125 -8 8 1.16626\n", ""},
+	{`BEGIN { print 1 2, "x" "yz", 1+2 3+4 }`, "", "12 xyz 37\n", ""},
+	{`BEGIN { print "food"~/oo/, "food"~/[oO]+d/, "food"~"f", "food"~"F", "food"~0 }`, "", "1 1 1 0 0\n", ""},
+	{`BEGIN { print "food"!~/oo/, "food"!~/[oO]+d/, "food"!~"f", "food"!~"F", "food"!~0 }`, "", "0 0 0 1 1\n", ""},
+	{`BEGIN { print 1+2*3/4^5%6 7, (1+2)*3/4^5%6 "7" }`, "", "1.005867 0.008789067\n", ""},
+
+	// Number, string, and regex expressions
+	//{`BEGIN { print 1, 1., .1, 1e0, -1, 1e }`, "", "1 1 0.1 1 -1 1\n", ""},
+	{`BEGIN { print '\"' '\'' 'xy' "z" "'" '\"' }`, "", "\"'xyz'\"\n", ""}, // Check support for single-quoted strings
+	{`{ print /foo/ }`, "food\nfoo\nxfooz\nbar\n", "1\n1\n1\n0\n", ""},
+	{`/[a-/`, "foo", "", "parse error at 1:1: error parsing regexp: missing closing ]: `[a-`"},
+	{`BEGIN { print "-12"+0, "+12"+0, " \t\r\n7foo"+0, ".5"+0, "5."+0, "+."+0 }`, "", "-12 12 7 0.5 5 0\n", ""},
+	{`BEGIN { print "1e3"+0, "1.2e-1"+0, "1e+1"+0, "1e"+0, "1e+"+0 }`, "", "1000 0.12 10 1 1\n", ""},
+	{`BEGIN { print -(11102200000000000000000000000000000000 1040000) }  # !gawk - gawk supports big numbers`,
+		"", "-inf\n", ""},
+	//{`BEGIN { print atan2(0, 8020020000000e20G-0)}`, "", "0\n", ""},
+	//{`BEGIN { print 1e1000, -1e1000 }  # !gawk`, "", "inf -inf\n", ""},
+	{`BEGIN { printf "\x0.\x00.\x0A\x10\xff\xFF\x41" }  # !awk`, "", "\x00.\x00.\n\x10\xff\xffA", ""},
+	{`BEGIN { printf "\x1.\x01.\x0A\x10\xff\xFF\x41" }`, "", "\x01.\x01.\n\x10\xff\xffA", ""},
+	{`BEGIN { printf "\0\78\7\77\777\0 \141 " }  # !awk`, "", "\x00\a8\a?\xff\x00 a ", ""},
+	{`BEGIN { printf "\1\78\7\77\777\1 \141 " }`, "", "\x01\a8\a?\xff\x01 a ", ""},
+
+	// Conditional ?: expression
+	{`{ print /x/?"t":"f" }`, "x\ny\nxx\nz\n", "t\nf\nt\nf\n", ""},
+	{`BEGIN { print 1?2?3:4:5, 1?0?3:4:5, 0?2?3:4:5 }`, "", "3 4 5\n", ""},
+	// TODO: {`BEGIN { $0="0"; print ($0?1:0) }`, "", "1\n", ""},
+	{`{ print $0?1:0 }`, "0\n", "0\n", ""},
+	{`{ print $0?1:0 }`, "1\n", "1\n", ""},
+	{`BEGIN { $0="1"; print ($0?1:0) }`, "", "1\n", ""},
+	{`BEGIN { print 0?1:0, 1?1:0, ""?1:0, "0"?1:0, "1"?1:0 }`, "", "0 1 0 1 1\n", ""},
+
+	// Built-in variables
+	// ARGC is tested in goawk_test.go
+	{`
+BEGIN {
+	print CONVFMT, 1.2345678 ""
+	CONVFMT = "%.3g"
+	print CONVFMT, 1.234567 ""
+}`, "", "%.6g 1.23457\n%.3g 1.23\n", ""},
+	//{`BEGIN { FILENAME = "foo"; print FILENAME }`, "", "foo\n", ""},
+	//{`BEGIN { FILENAME = "123.0"; print (FILENAME==123) }`, "", "0\n", ""},
+	// Other FILENAME behaviour is tested in goawk_test.go
+	//{`BEGIN { FNR = 123; print FNR }`, "", "123\n", ""},
+	{`{ print FNR, $0 }`, "a\nb\nc", "1 a\n2 b\n3 c\n", ""},
+	// Other FNR behaviour is tested in goawk_test.go
+	{`BEGIN { print "|" FS "|"; FS="," } { print $1, $2 }`, "a b\na,b\nx,,y", "| |\na b \na b\nx \n", ""},
+	{`BEGIN { print "|" FS "|"; FS="\\." } { print $1, $2 }`, "a b\na.b\nx..y", "| |\na b \na b\nx \n", ""},
+	{`BEGIN { FS="\\" } { print $1, $2 }`, "a\\b", "a b\n", ""},
+	{`{ print NF }`, "\na\nc d\ne f g", "0\n1\n2\n3\n", ""},
+	//{`BEGIN { NR = 123; print NR }`, "", "123\n", ""},
+	{`{ print NR, $0 }`, "a\nb\nc", "1 a\n2 b\n3 c\n", ""},
+	{`
+BEGIN {
+	print OFMT, 1.2345678
+	OFMT = "%.3g"
+	print OFMT, 1.234567
+}`, "", "%.6g 1.23457\n%.3g 1.23\n", ""},
+	// OFS and ORS are tested above
+	{`BEGIN { print RSTART, RLENGTH; RSTART=5; RLENGTH=42; print RSTART, RLENGTH; } `, "",
+		"0 0\n5 42\n", ""},
+	//{`BEGIN { print RS }`, "", "\n\n", ""},
+	//{`BEGIN { print RS; RS="|"; print RS }  { print }`, "a b|c d|", "\n\n|\na b\nc d\n", ""},
+	//{`BEGIN { RS=""; FS="\n" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) print $i }`,
+	//	"a\n\nb\nc",
+	//	"1 (1):\na\n2 (2):\nb\nc\n", ""},
+	//{`BEGIN { RS=""; FS="\n" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) print $i }`,
+	//	"1\n2\n\na\nb",
+	//	"1 (2):\n1\n2\n2 (2):\na\nb\n", ""},
+	//{`BEGIN { RS=""; FS="\n" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) print $i }`,
+	//	"a b\nc d\n\ne f\n\n\n  \n\n\ng h\n\n\n",
+	//	"1 (2):\na b\nc d\n2 (1):\ne f\n3 (1):\n  \n4 (1):\ng h\n", ""},
+	//{`BEGIN { RS=""; FS="\n" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) print $i }`,
+	//	"\n\na b\n\nc d\n",
+	//	"1 (1):\na b\n2 (1):\nc d\n", ""},
+	//{`BEGIN { RS=""; FS="\n" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) print $i }  # !awk !gawk - they don't handle CR LF with RS==""`,
+	//	"\r\n\r\na b\r\n\r\nc d\r\n",
+	//	"1 (1):\na b\n2 (1):\nc d\n", ""},
+	//{`BEGIN { RS=""; FS="X" }  { printf "%d (%d):\n", NR, NF; for (i=1; i<=NF; i++) printf "%s|", $i }`,
+	//	"aXb\ncXd\n\neXf\n\n\n  \n\n\ngXh\n\n\n",
+	//	"1 (4):\na|b|c|d|2 (2):\ne|f|3 (1):\n  |4 (2):\ng|h|", ""},
+	//{`BEGIN { RS = "" }  { print "got", $0 }`,
+	//	"\n\n\n\n", "", ""},
+	//{`BEGIN { RS="\n" }  { print }`, "a\n\nb\nc", "a\n\nb\nc\n", ""},
+	{`
+BEGIN {
+	print SUBSEP
+	a[1, 2] = "onetwo"
+	print a[1, 2]
+	for (k in a) {
+		print k, a[k]
+	}
+	delete a[1, 2]
+	SUBSEP = "|"
+	print SUBSEP
+	a[1, 2] = "onetwo"
+	print a[1, 2]
+	for (k in a) {
+		print k, a[k]
+	}
+}`, "", "\x1c\nonetwo\n1\x1c2 onetwo\n|\nonetwo\n1|2 onetwo\n", ""},
+
+	// Field expressions and assignment (and interaction with NF)
+	//{`{ print NF; NF=1; $2="two"; print $0, NF }`, "\n", "0\n two 2\n", ""},
+	//{`{ print NF; NF=2; $2="two"; print $0, NF}`, "\n", "0\n two 2\n", ""},
+	//{`{ print NF; NF=3; $2="two"; print $0, NF}`, "a b c\n", "3\na two c 3\n", ""},
+	{`{ print; print $1, $3, $NF }`, "a b c d e", "a b c d e\na c e\n", ""},
+	{`{ print $1,$3; $2="x"; print; print $2 }`, "a b c", "a c\na x c\nx\n", ""},
+	{`{ print; $0="x y z"; print; print $1, $3 }`, "a b c", "a b c\nx y z\nx z\n", ""},
+	{`{ print $1^2 }`, "10", "100\n", ""},
+	//{`{ print $-1 }`, "x", "", "field index negative: -1"},
+	//{`{ NF=-1; }  # !awk - awk allows setting negative NF`,
+	//	"x", "", "NF set to negative value: -1"},
+	//{`{ NF=1234567; }`, "x", "", "NF set too large: 1234567"},
+	{`BEGIN { $1234567=1 }`, "", "", "field index too large: 1234567"},
+	{`0 in FS  # !awk - doesn't flag this as an error`, "x", "",
+		`parse error at 1:6: can't use scalar "FS" as array`},
+	// TODO: I think this is happening because we parse this as ($($0))++ rather than ($($0++))
+	// TODO: {`{ $$0++; print $0 }`, "2 3 4", "3\n", ""},
+	// TODO: {`BEGIN { $0="3 4 5 6 7 8 9"; a=3; print $$a++++; print }`, "", "7\n3 4 6 6 8 8 9\n", ""},
+
+	// Lots of NF tests with different combinations of NF, $, and number
+	// of input fields. Some of these cause segmentation faults on awk
+	// (but work fine on gawk and mawk).
+	//{`{ NF=1; $1="x"; print $0; print NF }`, "a", "x\n1\n", ""},
+	//{`{ NF=1; $1="x"; print $0; print NF }`, "a b", "x\n1\n", ""},
+	//{`{ NF=1; $1="x"; print $0; print NF }`, "a b c", "x\n1\n", ""},
+	//{`{ NF=1; $2="x"; print $0; print NF }`, "a", "a x\n2\n", ""},
+	//{`{ NF=1; $2="x"; print $0; print NF }`, "a b", "a x\n2\n", ""},
+	//{`{ NF=1; $2="x"; print $0; print NF }`, "a b c", "a x\n2\n", ""},
+	//{`{ NF=1; $3="x"; print $0; print NF }`, "a", "a  x\n3\n", ""},
+	//{`{ NF=1; $3="x"; print $0; print NF }  # !awk - awk differs from gawk (but gawk seems right)`,
+	//	"a b", "a  x\n3\n", ""},
+	//{`{ NF=1; $3="x"; print $0; print NF }  # !awk - awk differs from gawk (but gawk seems right)`,
+	//	"a b c", "a  x\n3\n", ""},
+	//{`{ NF=2; $1="x"; print $0; print NF }`, "a", "x \n2\n", ""},
+	//{`{ NF=2; $1="x"; print $0; print NF }`, "a b", "x b\n2\n", ""},
+	//{`{ NF=2; $1="x"; print $0; print NF }`, "a b c", "x b\n2\n", ""},
+	//{`{ NF=2; $2="x"; print $0; print NF }`, "a", "a x\n2\n", ""},
+	//{`{ NF=2; $2="x"; print $0; print NF }`, "a b", "a x\n2\n", ""},
+	//{`{ NF=2; $2="x"; print $0; print NF }`, "a b c", "a x\n2\n", ""},
+	//{`{ NF=2; $3="x"; print $0; print NF }`, "a", "a  x\n3\n", ""},
+	//{`{ NF=2; $3="x"; print $0; print NF }`, "a b", "a b x\n3\n", ""},
+	//{`{ NF=2; $3="x"; print $0; print NF }`, "a b c", "a b x\n3\n", ""},
+	//{`{ NF=3; $1="x"; print $0; print NF }  # !awk - segmentation fault`,
+	//	"a", "x  \n3\n", ""},
+	//{`{ NF=3; $1="x"; print $0; print NF }  # !awk - segmentation fault`,
+	//	"a b", "x b \n3\n", ""},
+	//{`{ NF=3; $1="x"; print $0; print NF }`, "a b c", "x b c\n3\n", ""},
+	//{`{ NF=3; $2="x"; print $0; print NF }  # !awk - segmentation fault`,
+	//	"a", "a x \n3\n", ""},
+	//{`{ NF=3; $2="x"; print $0; print NF }  # !awk - segmentation fault`,
+	//	"a b", "a x \n3\n", ""},
+	//{`{ NF=3; $2="x"; print $0; print NF }`, "a b c", "a x c\n3\n", ""},
+	//{`{ NF=3; $3="x"; print $0; print NF }`, "a", "a  x\n3\n", ""},
+	//{`{ NF=3; $3="x"; print $0; print NF }`, "a b", "a b x\n3\n", ""},
+	//{`{ NF=3; $3="x"; print $0; print NF }`, "a b c", "a b x\n3\n", ""},
+
+	// Assignment expressions and vars
+	{`BEGIN { print x; x = 4; print x; }`, "", "0\n4\n", ""},
+	{`BEGIN { a["foo"]=1; b[2]="x"; k="foo"; print a[k], b["2"] }`, "", "1 x\n", ""},
+	{`BEGIN { s+=5; print s; s-=2; print s; s-=s; print s }`, "", "5\n3\n0\n", ""},
+	{`BEGIN { x=2; x*=x; print x; x*=3; print x }`, "", "4\n12\n", ""},
+	{`BEGIN { x=6; x/=3; print x; x/=x; print x; x/=.6; print x }`, "", "2\n1\n1.66667\n", ""},
+	{`BEGIN { x=12; x%=5; print x }`, "", "2\n", ""},
+	{`BEGIN { x=2; x^=5; print x; x^=0.5; print x }`, "", "32\n5.65685\n", ""},
+	{`{ $2+=10; print; $3/=2; print }`, "1 2 3", "1 12 3\n1 12 1.5\n", ""},
+	{`BEGIN { a[2] += 1; a["2"] *= 3; print a[2] }`, "", "3\n", ""},
+
+	// Incr/decr expressions
+	{`BEGIN { print x++; print x }`, "", "0\n1\n", ""},
+	{`BEGIN { print x; print x++; print ++x; print x }`, "", "0\n0\n2\n2\n", ""},
+	{`BEGIN { print x; print x--; print --x; print x }`, "", "0\n0\n-2\n-2\n", ""},
+	{`BEGIN { s++; s++; print s }`, "", "2\n", ""},
+	{`BEGIN { y=" "; --x[y = y y]; print length(y) }`, "", "2\n", ""},
+	{`BEGIN { x[y++]++; print y }`, "", "1\n", ""},
+	{`BEGIN { x[y++] += 3; print y }`, "", "1\n", ""},
+	{`BEGIN { $(y++)++; print y }`, "", "1\n", ""},
+
+	// Builtin functions
+	{`BEGIN { print sin(0), sin(0.5), sin(1), sin(-1) }`, "", "0 0.479426 0.841471 -0.841471\n", ""},
+	{`BEGIN { print cos(0), cos(0.5), cos(1), cos(-1) }`, "", "1 0.877583 0.540302 0.540302\n", ""},
+	{`BEGIN { print exp(0), exp(0.5), exp(1), exp(-1) }`, "", "1 1.64872 2.71828 0.367879\n", ""},
+	{`BEGIN { print log(0), log(0.5), log(1) }`, "", "-inf -0.693147 0\n", ""},
+	{`BEGIN { print log(-1) }  # !gawk - gawk prints warning for this as well`,
+		"", "nan\n", ""},
+	{`BEGIN { print sqrt(0), sqrt(2), sqrt(4) }`, "", "0 1.41421 2\n", ""},
+	{`BEGIN { print int(3.5), int("1.9"), int(4), int(-3.6), int("x"), int("") }`, "", "3 1 4 -3 0 0\n", ""},
+	{`BEGIN { print match("food", "foo"), RSTART, RLENGTH }`, "", "1 1 3\n", ""},
+	{`BEGIN { print match("x food y", "fo"), RSTART, RLENGTH }`, "", "3 3 2\n", ""},
+	{`BEGIN { print match("x food y", "fox"), RSTART, RLENGTH }`, "", "0 0 -1\n", ""},
+	{`BEGIN { print match("x food y", /[fod]+/), RSTART, RLENGTH }`, "", "3 3 4\n", ""},
+	{`{ print length, length(), length("buzz"), length("") }`, "foo bar", "7 7 4 0\n", ""},
+	{`BEGIN { print index("foo", "f"), index("foo0", 0), index("foo", "o"), index("foo", "x") }`, "", "1 4 2 0\n", ""},
+	{`BEGIN { print atan2(1, 0.5), atan2(-1, 0) }`, "", "1.10715 -1.5708\n", ""},
+	{`BEGIN { print sprintf("%3d", 42) }`, "", " 42\n", ""},
+	{`BEGIN { print sprintf("%d", 12, 34) }`, "", "12\n", ""},
+	{`BEGIN { print sprintf("%d") }`, "", "", `not enough arguments (0) for format string "%d"`},
+	{`BEGIN { print sprintf("%d", 12, 34) }`, "", "12\n", ""},
+	{`BEGIN { print sprintf("% 5d", 42) }`, "", "   42\n", ""},
+	{`BEGIN { print substr("food", 1) }`, "", "food\n", ""},
+	{`BEGIN { print substr("food", 1, 2) }`, "", "fo\n", ""},
+	{`BEGIN { print substr("food", 1, 4) }`, "", "food\n", ""},
+	{`BEGIN { print substr("food", 1, 8) }`, "", "food\n", ""},
+	{`BEGIN { print substr("food", 2) }`, "", "ood\n", ""},
+	{`BEGIN { print substr("food", 2, 2) }`, "", "oo\n", ""},
+	{`BEGIN { print substr("food", 2, 3) }`, "", "ood\n", ""},
+	{`BEGIN { print substr("food", 2, 8) }`, "", "ood\n", ""},
+	{`BEGIN { print substr("food", 0, 8) }`, "", "food\n", ""},
+	{`BEGIN { print substr("food", -1, 8) }`, "", "food\n", ""},
+	{`BEGIN { print substr("food", 5, 8) }`, "", "\n", ""},
+	{`BEGIN { n = split("ab c d ", a); for (i=1; i<=n; i++) print a[i] }`, "", "ab\nc\nd\n", ""},
+	{`BEGIN { n = split("ab,c,d,", a, ","); for (i=1; i<=n; i++) print a[i] }`, "", "ab\nc\nd\n\n", ""},
+	{`BEGIN { n = split("ab,c.d,", a, /[,.]/); for (i=1; i<=n; i++) print a[i] }`, "", "ab\nc\nd\n\n", ""},
+	{`BEGIN { n = split("1 2", a); print (n, a[1], a[2], a[1]==1, a[2]==2) }`, "", "2 1 2 1 1\n", ""},
+	{`BEGIN { x = "1.2.3"; print sub(/\./, ",", x); print x }`, "", "1\n1,2.3\n", ""},
+	{`{ print sub(/\./, ","); print $0 }`, "1.2.3", "1\n1,2.3\n", ""},
+	{`BEGIN { x = "1.2.3"; print gsub(/\./, ",", x); print x }`, "", "2\n1,2,3\n", ""},
+	{`{ print gsub(/\./, ","); print $0 }`, "1.2.3", "2\n1,2,3\n", ""},
+	{`{ print gsub(/[0-9]/, "(&)"); print $0 }`, "0123x. 42y", "6\n(0)(1)(2)(3)x. (4)(2)y\n", ""},
+	{`{ print gsub(/[0-9]+/, "(&)"); print $0 }`, "0123x. 42y", "2\n(0123)x. (42)y\n", ""},
+	{`{ print gsub(/[0-9]/, "\\&"); print $0 }`, "0123x. 42y", "6\n&&&&x. &&y\n", ""},
+	{`{ print gsub(/[0-9]/, "\\z"); print $0 }`, "0123x. 42y", "6\n\\z\\z\\z\\zx. \\z\\zy\n", ""},
+	{`{ print gsub("0", "x\\\\y"); print $0 }  # !awk !gawk -- our behaviour is per POSIX spec (gawk -P and mawk)`,
+		"0", "1\nx\\y\n", ""},
+	//{`sub("", "\\e", FS)  # !awk !gawk`, "foo bar\nbaz buz\n", "",
+	//	"invalid regex \"\\\\e \": error parsing regexp: invalid escape sequence: `\\e`"},
+	{`BEGIN { print tolower("Foo BaR") }`, "", "foo bar\n", ""},
+	{`BEGIN { print toupper("Foo BaR") }`, "", "FOO BAR\n", ""},
+	{`
+BEGIN {
+	srand(1)
+	a = rand(); b = rand(); c = rand()
+	srand(1)
+	x = rand(); y = rand(); z = rand()
+	print (a==b, b==c, x==y, y==z)
+	print (a==x, b==y, c==z)
+}
+`, "", "0 0 0 0\n1 1 1\n", ""},
+	{`
+BEGIN {
+	for (i = 0; i < 1000; i++) {
+		if (rand() < 0.5) n++
+	}
+	print (n>400)
+}
+`, "", "1\n", ""},
+	{`BEGIN { print system("echo foo"); print system("echo bar") }  # !fuzz`,
+		"", "foo\n0\nbar\n0\n", ""},
+	{`BEGIN { print system(">&2 echo error") }  # !fuzz`,
+		"", "error\n0\n", ""},
+
+	// Conditional expressions parse and work correctly
+	{`BEGIN { print 0?"t":"f" }`, "", "f\n", ""},
+	{`BEGIN { print 1?"t":"f" }`, "", "t\n", ""},
+	{`BEGIN { print (1+2)?"t":"f" }`, "", "t\n", ""},
+	{`BEGIN { print (1+2?"t":"f") }`, "", "t\n", ""},
+	{`BEGIN { print(1 ? x="t" : "f"); print x; }`, "", "t\nt\n", ""},
+
+	// Locals vs globals, array params, and recursion
+	//	{`
+	//function f(loc) {
+	//	glob += 1
+	//	loc += 1
+	//	print glob, loc
+	//}
+	//BEGIN {
+	//	glob = 1
+	//	loc = 42
+	//	f(3)
+	//	print loc
+	//	f(4)
+	//	print loc
+	//}
+	//`, "", "2 4\n42\n3 5\n42\n", ""},
+	//	{`
+	//function set(a, x, v) {
+	//	a[x] = v
+	//}
+	//function get(a, x) {
+	//	return a[x]
+	//}
+	//BEGIN {
+	//	a["x"] = 1
+	//	set(b, "y", 2)
+	//	for (k in a) print k, a[k]
+	//	print "---"
+	//	for (k in b) print k, b[k]
+	//	print "---"
+	//	print get(a, "x"), get(b, "y")
+	//}
+	//`, "", "x 1\n---\ny 2\n---\n1 2\n", ""},
+	//	{`
+	//function fib(n) {
+	//	return n < 3 ? 1 : fib(n-2) + fib(n-1)
+	//}
+	//BEGIN {
+	//	for (i = 1; i <= 7; i++) {
+	//		printf "%d ", fib(i)
+	//	}
+	//}
+	//`, "", "1 1 2 3 5 8 13 ", ""},
+	//	{`
+	//function f(a, x) { return a[x] }
+	//function g(b, y) { f(b, y) }
+	//BEGIN { c[1]=2; print f(c, 1); print g(c, 1) }
+	//`, "", "2\n\n", ""},
+	//	{`
+	//function g(b, y) { return f(b, y) }
+	//function f(a, x) { return a[x] }
+	//BEGIN { c[1]=2; print f(c, 1); print g(c, 1) }
+	//`, "", "2\n2\n", ""},
+	//	{`
+	//function h(b, y) { g(b, y) }
+	//function g(b, y) { f(b, y) }
+	//function f(a, x) { return a[x] }
+	//BEGIN { c[1]=2; print f(c, 1); print g(c, 1) }
+	//`, "", "2\n\n", ""},
+	//	{`
+	//function h(b, y) { return g(b, y) }
+	//function g(b, y) { return f(b, y) }
+	//function f(a, x) { return a[x] }
+	//BEGIN { c[1]=2; print f(c, 1); print g(c, 1); print h(c, 1) }
+	//`, "", "2\n2\n2\n", ""},
+	//	{`
+	//function get(a, x) { return a[x] }
+	//BEGIN { a[1]=2; print get(a, x); print get(1, 2); }
+	//# !awk - awk doesn't detect this
+	//`, "", "", `parse error at 3:40: can't pass scalar 1 as array param`},
+	//	{`
+	//function early() {
+	//	print "x"
+	//	return
+	//	print "y"
+	//}
+	//BEGIN { early() }
+	//`, "", "x\n", ""},
+	//	{`BEGIN { return }`, "", "", "parse error at 1:9: return must be inside a function"},
+	//	{`function f() { printf "x" }; BEGIN { f() } `, "", "x", ""},
+	//	{`function f(x) { 0 in _; f(_) }  BEGIN { f() }  # !awk !gawk`, "", "",
+	//		`parse error at 1:25: can't pass array "_" as scalar param`},
+	//	{`BEGIN { for (i=0; i<1001; i++) f(); print x }  function f() { x++ }`, "", "1001\n", ""},
+	//	{`
+	//function bar(y) { return y[1] }
+	//function foo() { return bar(x) }
+	//BEGIN { x[1] = 42; print foo() }
+	//`, "", "42\n", ""},
+	// TODO: failing because f1 doesn't use x, so resolver assumes its type is scalar
+	// 		{`
+	// function f1(x) { }
+	// function f2(x, y) { return x[y] }
+	// BEGIN { a[1]=2; f1(a); print f2(a, 1) }
+	// `, "", "2\n", ""},
+
+	// Type checking / resolver tests
+	{`BEGIN { a[x]; a=42 }`, "", "", `parse error at 1:15: can't use array "a" as scalar`},
+	{`BEGIN { s=42; s[x] }`, "", "", `parse error at 1:15: can't use scalar "s" as array`},
+	//	{`function get(a, k) { return a[k] }  BEGIN { a = 42; print get(a, 1); }  # !awk - doesn't error in awk`,
+	//		"", "", `parse error at 1:59: can't pass scalar "a" as array param`},
+	//	{`function get(a, k) { return a+k } BEGIN { a[42]; print get(a, 1); }`,
+	//		"", "", `parse error at 1:56: can't pass array "a" as scalar param`},
+	//	{`{ f(z) }  function f(x) { print NR }`, "abc", "1\n", ""},
+	//	{`function f() { f() }  BEGIN { f() }  # !awk !gawk`, "", "", `calling "f" exceeded maximum call depth of 1000`},
+	//	{`function f(x) { 0 in x }  BEGIN { f(FS) }  # !awk`, "", "", `parse error at 1:35: can't pass scalar "FS" as array param`},
+	//	{`
+	//function foo(x) { print "foo", x }
+	//function bar(foo) { print "bar", foo }
+	//BEGIN { foo(5); bar(10) }
+	//`, "", "foo 5\nbar 10\n", ""},
+	//	{`
+	//function foo(foo) { print "foo", foo }
+	//function bar(foo) { print "bar", foo }
+	//BEGIN { foo(5); bar(10) }
+	//`, "", "", `parse error at 2:14: can't use function name as parameter name`},
+	//	{`function foo() { print foo }  BEGIN { foo() }`,
+	//		"", "", `parse error at 1:46: global var "foo" can't also be a function`},
+	//	{`function f(x) { print x, x(); }  BEGIN { f() }`, "", "", `parse error at 1:27: can't call local variable "x" as function`},
+
+	// Redirected I/O (we give explicit errors, awk and gawk don't)
+	// TODO: the following two tests sometimes fail under TravisCI with: "write |1: broken pipe"
+	// {`BEGIN { print >"out"; getline <"out" }  # !awk !gawk`, "", "", "can't read from writer stream"},
+	// {`BEGIN { print |"out"; getline <"out" }  # !awk !gawk`, "", "", "can't read from writer stream"},
+	//{`BEGIN { print >"out"; close("out"); getline <"out"; print >"out" }  # !awk !gawk`, "", "", "can't write to reader stream"},
+	//{`BEGIN { print >"out"; close("out"); getline <"out"; print |"out" }  # !awk !gawk`, "", "", "can't write to reader stream"},
+	// TODO: currently we support "getline var" but not "getline lvalue"
+	// TODO: {`BEGIN { getline a[1]; print a[1] }`, "foo", "foo\n", ""},
+	// TODO: {`BEGIN { getline $1; print $1 }`, "foo", "foo\n", ""},
+	//{`BEGIN { print "foo" |"sort"; print "bar" |"sort" }  # !fuzz`, "", "bar\nfoo\n", ""},
+	//{`BEGIN { print "foo" |">&2 echo error" }  # !fuzz`, "", "error\n", ""},
+	//{`BEGIN { "cat" | getline; print }  # !fuzz`, "bar", "bar\n", ""},
+	// TODO: fix test flakiness on Windows (sometimes returns "\nerror\n")
+	// {`BEGIN { ">&2 echo error" | getline; print }`, "", "error\n\n", ""},
+	//{`BEGIN { print getline x < "/no/such/file" }  # !fuzz`, "", "-1\n", ""},
+
+	// Redirecting to or from a filename of "-" means write to stdout or read from stdin
+	//{`BEGIN { print getline x < "-"; print x }`, "a\nb\n", "1\na\n", ""},
+	//{`{ print $0; print getline x <"-"; print x }`, "one\ntwo\n", "one\n0\n\ntwo\n0\n\n", ""},
+	//{`BEGIN { print "x" >"-"; print "y" >"-" }`, "", "x\ny\n", ""},
+
+	// fflush() function - tests parsing and some edge cases, but not
+	// actual flushing behavior (that's partially tested in TestFlushes).
+	{`BEGIN { print fflush(); print fflush("") }`, "", "0\n0\n", ""},
+	{`BEGIN { print "x"; print fflush(); print "y"; print fflush("") }`, "", "x\n0\ny\n0\n", ""},
+	//{`BEGIN { print "x" >"out"; print fflush("out"); print "y"; print fflush("") }  # !fuzz`, "", "0\ny\n0\n", ""},
+	//{`BEGIN { print fflush("x") }  # !gawk`, "", "error flushing \"x\": not an output file or pipe\n-1\n", ""},
+	//{`BEGIN { "cat" | getline; print fflush("cat") }  # !gawk !fuzz`, "", "error flushing \"cat\": not an output file or pipe\n-1\n", ""},
+
+	// Greater than operator requires parentheses in print statement,
+	// otherwise it's a redirection directive
+	//{`BEGIN { print "x" > "out" }  # !fuzz`, "", "", ""},
+	//{`BEGIN { printf "x" > "out" }  # !fuzz`, "", "", ""},
+	//{`BEGIN { print("x" > "out") }`, "", "1\n", ""},
+	//{`BEGIN { printf("x" > "out") }`, "", "1", ""},
+
+	// Grammar should allow blocks wherever statements are allowed
+	{`BEGIN { if (1) printf "x"; else printf "y" }`, "", "x", ""},
+	{`BEGIN { printf "x"; { printf "y"; printf "z" } }`, "", "xyz", ""},
+
+	// Ensure syntax errors result in errors
+	// TODO: {`{ $1 = substr($1, 1, 3) print $1 }`, "", "", "ERROR"},
+	{`BEGIN { f() }`, "", "", `parse error at 1:9: undefined function "f"`},
+	{`function f() {} function f() {} BEGIN { }`, "", "", `parse error at 1:26: function "f" already defined`},
+	{`BEGIN { print (1,2),(3,4) }`, "", "", "parse error at 1:15: unexpected comma-separated expression"},
+	{`BEGIN { print (1,2,(3,4),(5,6)) }`, "", "", "parse error at 1:20: unexpected comma-separated expression"},
+}
+
+func TestAWKGo(t *testing.T) {
+	tempDir := t.TempDir()
+
+	for i, test := range interpTests {
+		testName := test.src
+		if len(testName) > 70 {
+			testName = testName[:70]
+		}
+		t.Run(testName, func(t *testing.T) {
+			output, err := os.Create(filepath.Join(tempDir, fmt.Sprintf("test_%d.go", i)))
+			if err != nil {
+				t.Fatalf("error creating temp file: %v", err)
+			}
+			defer os.Remove(output.Name())
+
+			prog, err := parser.ParseProgram([]byte(test.src), nil)
+			if err != nil {
+				if test.err != "" {
+					if err.Error() == test.err {
+						return
+					}
+					t.Fatalf("expected error %q, got %q", test.err, err.Error())
+				}
+				t.Fatalf("parse error: %v", err)
+			}
+
+			err = compile(prog, output)
+			if err != nil {
+				if test.err != "" {
+					if err.Error() == test.err {
+						return
+					}
+					t.Fatalf("expected error %q, got %q", test.err, err.Error())
+				}
+				t.Fatalf("compile error: %v", err)
+			}
+
+			err = output.Close()
+			if err != nil {
+				t.Fatalf("error closing temp file: %v", err)
+			}
+
+			cmd := exec.Command("go", "run", output.Name())
+			cmd.Stdin = strings.NewReader(test.in)
+			out, err := cmd.CombinedOutput()
+			if err != nil {
+				if test.err != "" {
+					if err.Error() == test.err {
+						return
+					}
+					t.Fatalf("expected error %q, got %q", test.err, err.Error())
+				}
+				t.Fatalf("go run error: %v:\n%s", err, out)
+			}
+			if string(out) != test.out {
+				t.Fatalf("expected %q, got %q", test.out, out)
+			}
+		})
+	}
+}
diff --git a/awkgo/run_tests.sh b/awkgo/run_tests.sh
new file mode 100755
index 0000000..ef4d06b
--- /dev/null
+++ b/awkgo/run_tests.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+go test -tags awkgotest ./awkgo -v -count=1 | awk '{ sub(/TestAWKGo[0-9]+/, "TestAWKGo"); sub(/( \(|\t)[0-9]+\.[0-9]+s\)?/, ""); sub(/awkgo_test.go:[0-9]+/, "awkgo_test.go"); print }' >awkgo/tests.txt
diff --git a/awkgo/tests.txt b/awkgo/tests.txt
new file mode 100644
index 0000000..84454ae
--- /dev/null
+++ b/awkgo/tests.txt
@@ -0,0 +1,428 @@
+=== RUN   TestAWKGo
+=== RUN   TestAWKGo/BEGIN_{_print_"b"_}
+=== RUN   TestAWKGo/BEGIN_{_print_"b"_}#01
+=== RUN   TestAWKGo/END_{_print_"e"_}
+=== RUN   TestAWKGo/END_{_print_"e"_}#01
+=== RUN   TestAWKGo/BEGIN_{_print_"b"}_END_{_print_"e"_}
+=== RUN   TestAWKGo/BEGIN_{_print_"b"}_END_{_print_"e"_}#01
+=== RUN   TestAWKGo/BEGIN_{_print_"b"}_$0_{_print_NR_}_END_{_print_"e"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"x"_};_BEGIN_{_printf_"y"_}
+=== RUN   TestAWKGo/$0
+=== RUN   TestAWKGo/{_print_$0_}
+=== RUN   TestAWKGo/$1=="foo"
+=== RUN   TestAWKGo/$1==42
+=== RUN   TestAWKGo/$1=="42"
+=== RUN   TestAWKGo//foo/
+=== RUN   TestAWKGo/NR==2,_NR==4
+=== RUN   TestAWKGo/_NR==2,_NR==4_{_print_$0_}_NR==3,_NR==5_{_print_NR_}_
+=== RUN   TestAWKGo/BEGIN_{_print_"x",_"y"_}
+=== RUN   TestAWKGo/BEGIN_{_print_OFS;_OFS_=_",";_print_"x",_"y"_}
+=== RUN   TestAWKGo/BEGIN_{_print_ORS;_ORS_=_".";_print_"x",_"y"_}
+=== RUN   TestAWKGo/BEGIN_{_print_ORS;_ORS_=_"";_print_"x",_"y"_}
+=== RUN   TestAWKGo/{_print;_print_}
+=== RUN   TestAWKGo/BEGIN_{_print;_print_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%%_%d_%x_%c_%f_%s",_42,_42,_42,_42,_42_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%3d",_42_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%3s",_"x"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%d",_12,_34_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%d"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_0_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_127_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_128_}__#_!gawk
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_255_}__#_!gawk
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_256_}__#_!awk_!gawk
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_"xyz"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"%c",_""_}__#_!awk
+=== RUN   TestAWKGo/BEGIN_{_printf_}__#_!awk_-_doesn't_error_on_this
+=== RUN   TestAWKGo/BEGIN_{_printf("%%%dd",_4)_}
+=== RUN   TestAWKGo/BEGIN_{_if_(1)_print_"t";_}
+=== RUN   TestAWKGo/BEGIN_{_if_(0)_print_"t";_}
+=== RUN   TestAWKGo/BEGIN_{_if_(1)_print_"t";_else_print_"f"_}
+=== RUN   TestAWKGo/BEGIN_{_if_(0)_print_"t";_else_print_"f"_}
+=== RUN   TestAWKGo/BEGIN_{_for_(;;)_{_print_"x";_break_}_}
+=== RUN   TestAWKGo/BEGIN_{_for_(;;)_{_printf_"%d_",_i;_i++;_if_(i>2)_break;_}_}
+=== RUN   TestAWKGo/BEGIN_{_for_(i=5;_;_)_{_printf_"%d_",_i;_i++;_if_(i>8)_break;_}_}
+=== RUN   TestAWKGo/BEGIN_{_for_(i=5;_;_i++)_{_printf_"%d_",_i;_if_(i>8)_break;_}_}
+=== RUN   TestAWKGo/BEGIN_{_for_(i=5;_i<8;_i++)_{_printf_"%d_",_i_}_}
+=== RUN   TestAWKGo/BEGIN_{_for_(i=0;_i<10;_i++)_{_if_(i_<_5)_continue;_printf_"%d_",_i_}_
+=== RUN   TestAWKGo/BEGIN_{_a[1]=1;_a[2]=1;_for_(k_in_a)_{_s++;_break_}_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_a[1]=1;_a[2]=1;_a[3]=1;_for_(k_in_a)_{_if_(k==2)_continue;_s++
+=== RUN   TestAWKGo/BEGIN_{_while_(i<3)_{_i++;_s++;_break_}_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_while_(i<3)_{_i++;_if_(i==2)_continue;_s++_}_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_do_{_i++;_s++;_break_}_while_(i<3);_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_do_{_i++;_if_(i==2)_continue;_s++_}_while_(i<3);_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_for_(k_in_a)_x_+=_a[k];_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_while_(i_<_5)_{_print_i;_i++_}_}
+=== RUN   TestAWKGo/BEGIN_{_do_{_print_i;_i++_}_while_(i_<_5)_}
+=== RUN   TestAWKGo/BEGIN_{_for_(i=0;_i<10;_i++);_printf_"x"_}
+=== RUN   TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1;_i++)_{___for_(j_=_0;_j_<_1;_j++)_{____pri
+=== RUN   TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1;_i++)_{___for_(j_=_0;_j_<_1;_j++)_{____pri#01
+=== RUN   TestAWKGo/{_if_(NR==2)_next;_print_}
+=== RUN   TestAWKGo/BEGIN_{_next_}
+=== RUN   TestAWKGo/END_{_next_}
+=== RUN   TestAWKGo/BEGIN_{_a["x"]_=_3;_print_"x"_in_a,_"y"_in_a_}
+=== RUN   TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_delete_a["x"];_for_(k_in_a)_print_k,_a
+=== RUN   TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_for_(k_in_a)_delete_a[k];_for_(k_in_a)
+=== RUN   TestAWKGo/BEGIN_{_a[]_}
+=== RUN   TestAWKGo/BEGIN_{_delete_a[]_}
+=== RUN   TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_delete_a;_for_(k_in_a)_print_k,_a[k]_}
+=== RUN   TestAWKGo/BEGIN_{_print_!42,_!1,_!0,_!!42,_!!1,_!!0_}
+=== RUN   TestAWKGo/BEGIN_{_print_!42,_!1,_!0,_!!42,_!!1,_!!0_}#01
+=== RUN   TestAWKGo/BEGIN_{_print_+4,_+"3",_+0,_+-3,_-3,_-_-4,_-"3"_}
+=== RUN   TestAWKGo/BEGIN_{_$0="1";_print_!$0_}
+=== RUN   TestAWKGo/{_print_!$0_}
+=== RUN   TestAWKGo/{_print_!$0_}#01
+=== RUN   TestAWKGo/!seen[$0]++
+=== RUN   TestAWKGo/!seen[$0]--
+=== RUN   TestAWKGo/BEGIN_{_print_(1==1,_1==0,_"1"==1,_"1"==1.0)_}
+=== RUN   TestAWKGo/{_print_($0=="1",_$0==1)_}
+=== RUN   TestAWKGo/{_print_($1=="1",_$1==1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(1!=1,_1!=0,_"1"!=1,_"1"!=1.0)_}
+=== RUN   TestAWKGo/{_print_($0!="1",_$0!=1)_}
+=== RUN   TestAWKGo/{_print_($1!="1",_$1!=1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(0<1,_1<1,_2<1,_"12"<"2")_}
+=== RUN   TestAWKGo/{_print_($1<2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(0<=1,_1<=1,_2<=1,_"12"<="2")_}
+=== RUN   TestAWKGo/{_print_($1<=2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(0>1,_1>1,_2>1,_"12">"2")_}
+=== RUN   TestAWKGo/{_print_($1>2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(0>=1,_1>=1,_2>=1,_"12">="2")_}
+=== RUN   TestAWKGo/{_print_($1>=2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_1+2,_1+2+3,_1+-2,_-1+2,_"1"+"2",_3+.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_1-2,_1-2-3,_1-+2,_-1-2,_"1"-"2",_3-.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_2*3,_2*3*4,_2*-3,_-2*3,_"2"*"3",_3*.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_2/3,_2/3/4,_2/-3,_-2/3,_"2"/"3",_3/.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_2%3,_2%3%4,_2%-3,_-2%3,_"2"%"3",_3%.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_2^3,_2^3^3,_2^-3,_-2^3,_"2"^"3",_3^.14_}
+=== RUN   TestAWKGo/BEGIN_{_print_1_2,_"x"_"yz",_1+2_3+4_}
+=== RUN   TestAWKGo/BEGIN_{_print_"food"~/oo/,_"food"~/[oO]+d/,_"food"~"f",_"food"~"F",_"f
+=== RUN   TestAWKGo/BEGIN_{_print_"food"!~/oo/,_"food"!~/[oO]+d/,_"food"!~"f",_"food"!~"F"
+=== RUN   TestAWKGo/BEGIN_{_print_1+2*3/4^5%6_7,_(1+2)*3/4^5%6_"7"_}
+=== RUN   TestAWKGo/BEGIN_{_print_'\"'_'\''_'xy'_"z"_"'"_'\"'_}
+=== RUN   TestAWKGo/{_print_/foo/_}
+=== RUN   TestAWKGo//[a-/
+=== RUN   TestAWKGo/BEGIN_{_print_"-12"+0,_"+12"+0,_"_\t\r\n7foo"+0,_".5"+0,_"5."+0,_"+."+
+=== RUN   TestAWKGo/BEGIN_{_print_"1e3"+0,_"1.2e-1"+0,_"1e+1"+0,_"1e"+0,_"1e+"+0_}
+=== RUN   TestAWKGo/BEGIN_{_print_-(11102200000000000000000000000000000000_1040000)_}__#_!
+=== RUN   TestAWKGo/BEGIN_{_printf_"\x0.\x00.\x0A\x10\xff\xFF\x41"_}__#_!awk
+=== RUN   TestAWKGo/BEGIN_{_printf_"\x1.\x01.\x0A\x10\xff\xFF\x41"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"\0\78\7\77\777\0_\141_"_}__#_!awk
+=== RUN   TestAWKGo/BEGIN_{_printf_"\1\78\7\77\777\1_\141_"_}
+=== RUN   TestAWKGo/{_print_/x/?"t":"f"_}
+=== RUN   TestAWKGo/BEGIN_{_print_1?2?3:4:5,_1?0?3:4:5,_0?2?3:4:5_}
+=== RUN   TestAWKGo/{_print_$0?1:0_}
+=== RUN   TestAWKGo/{_print_$0?1:0_}#01
+=== RUN   TestAWKGo/BEGIN_{_$0="1";_print_($0?1:0)_}
+=== RUN   TestAWKGo/BEGIN_{_print_0?1:0,_1?1:0,_""?1:0,_"0"?1:0,_"1"?1:0_}
+=== RUN   TestAWKGo/_BEGIN_{__print_CONVFMT,_1.2345678_""__CONVFMT_=_"%.3g"__print_CONVFMT
+=== RUN   TestAWKGo/{_print_FNR,_$0_}
+=== RUN   TestAWKGo/BEGIN_{_print_"|"_FS_"|";_FS=","_}_{_print_$1,_$2_}
+=== RUN   TestAWKGo/BEGIN_{_print_"|"_FS_"|";_FS="\\."_}_{_print_$1,_$2_}
+=== RUN   TestAWKGo/BEGIN_{_FS="\\"_}_{_print_$1,_$2_}
+=== RUN   TestAWKGo/{_print_NF_}
+=== RUN   TestAWKGo/{_print_NR,_$0_}
+=== RUN   TestAWKGo/_BEGIN_{__print_OFMT,_1.2345678__OFMT_=_"%.3g"__print_OFMT,_1.234567_}
+=== RUN   TestAWKGo/BEGIN_{_print_RSTART,_RLENGTH;_RSTART=5;_RLENGTH=42;_print_RSTART,_RLE
+=== RUN   TestAWKGo/_BEGIN_{__print_SUBSEP__a[1,_2]_=_"onetwo"__print_a[1,_2]__for_(k_in_a
+=== RUN   TestAWKGo/{_print;_print_$1,_$3,_$NF_}
+=== RUN   TestAWKGo/{_print_$1,$3;_$2="x";_print;_print_$2_}
+=== RUN   TestAWKGo/{_print;_$0="x_y_z";_print;_print_$1,_$3_}
+=== RUN   TestAWKGo/{_print_$1^2_}
+=== RUN   TestAWKGo/BEGIN_{_$1234567=1_}
+=== RUN   TestAWKGo/0_in_FS__#_!awk_-_doesn't_flag_this_as_an_error
+=== RUN   TestAWKGo/BEGIN_{_print_x;_x_=_4;_print_x;_}
+=== RUN   TestAWKGo/BEGIN_{_a["foo"]=1;_b[2]="x";_k="foo";_print_a[k],_b["2"]_}
+=== RUN   TestAWKGo/BEGIN_{_s+=5;_print_s;_s-=2;_print_s;_s-=s;_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_x=2;_x*=x;_print_x;_x*=3;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_x=6;_x/=3;_print_x;_x/=x;_print_x;_x/=.6;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_x=12;_x%=5;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_x=2;_x^=5;_print_x;_x^=0.5;_print_x_}
+=== RUN   TestAWKGo/{_$2+=10;_print;_$3/=2;_print_}
+=== RUN   TestAWKGo/BEGIN_{_a[2]_+=_1;_a["2"]_*=_3;_print_a[2]_}
+=== RUN   TestAWKGo/BEGIN_{_print_x++;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_print_x;_print_x++;_print_++x;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_print_x;_print_x--;_print_--x;_print_x_}
+=== RUN   TestAWKGo/BEGIN_{_s++;_s++;_print_s_}
+=== RUN   TestAWKGo/BEGIN_{_y="_";_--x[y_=_y_y];_print_length(y)_}
+=== RUN   TestAWKGo/BEGIN_{_x[y++]++;_print_y_}
+=== RUN   TestAWKGo/BEGIN_{_x[y++]_+=_3;_print_y_}
+=== RUN   TestAWKGo/BEGIN_{_$(y++)++;_print_y_}
+=== RUN   TestAWKGo/BEGIN_{_print_sin(0),_sin(0.5),_sin(1),_sin(-1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_cos(0),_cos(0.5),_cos(1),_cos(-1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_exp(0),_exp(0.5),_exp(1),_exp(-1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_log(0),_log(0.5),_log(1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_log(-1)_}__#_!gawk_-_gawk_prints_warning_for_this_as_wel
+=== RUN   TestAWKGo/BEGIN_{_print_sqrt(0),_sqrt(2),_sqrt(4)_}
+=== RUN   TestAWKGo/BEGIN_{_print_int(3.5),_int("1.9"),_int(4),_int(-3.6),_int("x"),_int("
+=== RUN   TestAWKGo/BEGIN_{_print_match("food",_"foo"),_RSTART,_RLENGTH_}
+=== RUN   TestAWKGo/BEGIN_{_print_match("x_food_y",_"fo"),_RSTART,_RLENGTH_}
+=== RUN   TestAWKGo/BEGIN_{_print_match("x_food_y",_"fox"),_RSTART,_RLENGTH_}
+=== RUN   TestAWKGo/BEGIN_{_print_match("x_food_y",_/[fod]+/),_RSTART,_RLENGTH_}
+=== RUN   TestAWKGo/{_print_length,_length(),_length("buzz"),_length("")_}
+=== RUN   TestAWKGo/BEGIN_{_print_index("foo",_"f"),_index("foo0",_0),_index("foo",_"o"),_
+=== RUN   TestAWKGo/BEGIN_{_print_atan2(1,_0.5),_atan2(-1,_0)_}
+=== RUN   TestAWKGo/BEGIN_{_print_sprintf("%3d",_42)_}
+=== RUN   TestAWKGo/BEGIN_{_print_sprintf("%d",_12,_34)_}
+=== RUN   TestAWKGo/BEGIN_{_print_sprintf("%d")_}
+=== RUN   TestAWKGo/BEGIN_{_print_sprintf("%d",_12,_34)_}#01
+=== RUN   TestAWKGo/BEGIN_{_print_sprintf("%_5d",_42)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_1)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_1,_2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_1,_4)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_1,_8)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_2,_2)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_2,_3)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_2,_8)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_0,_8)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_-1,_8)_}
+=== RUN   TestAWKGo/BEGIN_{_print_substr("food",_5,_8)_}
+=== RUN   TestAWKGo/BEGIN_{_n_=_split("ab_c_d_",_a);_for_(i=1;_i<=n;_i++)_print_a[i]_}
+=== RUN   TestAWKGo/BEGIN_{_n_=_split("ab,c,d,",_a,_",");_for_(i=1;_i<=n;_i++)_print_a[i]_
+=== RUN   TestAWKGo/BEGIN_{_n_=_split("ab,c.d,",_a,_/[,.]/);_for_(i=1;_i<=n;_i++)_print_a[
+=== RUN   TestAWKGo/BEGIN_{_n_=_split("1_2",_a);_print_(n,_a[1],_a[2],_a[1]==1,_a[2]==2)_}
+=== RUN   TestAWKGo/BEGIN_{_x_=_"1.2.3";_print_sub(/\./,_",",_x);_print_x_}
+=== RUN   TestAWKGo/{_print_sub(/\./,_",");_print_$0_}
+=== RUN   TestAWKGo/BEGIN_{_x_=_"1.2.3";_print_gsub(/\./,_",",_x);_print_x_}
+=== RUN   TestAWKGo/{_print_gsub(/\./,_",");_print_$0_}
+=== RUN   TestAWKGo/{_print_gsub(/[0-9]/,_"(&)");_print_$0_}
+=== RUN   TestAWKGo/{_print_gsub(/[0-9]+/,_"(&)");_print_$0_}
+=== RUN   TestAWKGo/{_print_gsub(/[0-9]/,_"\\&");_print_$0_}
+=== RUN   TestAWKGo/{_print_gsub(/[0-9]/,_"\\z");_print_$0_}
+=== RUN   TestAWKGo/{_print_gsub("0",_"x\\\\y");_print_$0_}__#_!awk_!gawk_--_our_behaviour
+=== RUN   TestAWKGo/BEGIN_{_print_tolower("Foo_BaR")_}
+=== RUN   TestAWKGo/BEGIN_{_print_toupper("Foo_BaR")_}
+=== RUN   TestAWKGo/_BEGIN_{__srand(1)__a_=_rand();_b_=_rand();_c_=_rand()__srand(1)__x_=_
+=== RUN   TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1000;_i++)_{___if_(rand()_<_0.5)_n++__}__pri
+=== RUN   TestAWKGo/BEGIN_{_print_system("echo_foo");_print_system("echo_bar")_}__#_!fuzz
+=== RUN   TestAWKGo/BEGIN_{_print_system(">&2_echo_error")_}__#_!fuzz
+=== RUN   TestAWKGo/BEGIN_{_print_0?"t":"f"_}
+=== RUN   TestAWKGo/BEGIN_{_print_1?"t":"f"_}
+=== RUN   TestAWKGo/BEGIN_{_print_(1+2)?"t":"f"_}
+=== RUN   TestAWKGo/BEGIN_{_print_(1+2?"t":"f")_}
+=== RUN   TestAWKGo/BEGIN_{_print(1_?_x="t"_:_"f");_print_x;_}
+=== RUN   TestAWKGo/BEGIN_{_a[x];_a=42_}
+=== RUN   TestAWKGo/BEGIN_{_s=42;_s[x]_}
+=== RUN   TestAWKGo/BEGIN_{_print_fflush();_print_fflush("")_}
+=== RUN   TestAWKGo/BEGIN_{_print_"x";_print_fflush();_print_"y";_print_fflush("")_}
+=== RUN   TestAWKGo/BEGIN_{_if_(1)_printf_"x";_else_printf_"y"_}
+=== RUN   TestAWKGo/BEGIN_{_printf_"x";_{_printf_"y";_printf_"z"_}_}
+=== RUN   TestAWKGo/BEGIN_{_f()_}
+=== RUN   TestAWKGo/function_f()_{}_function_f()_{}_BEGIN_{_}
+=== RUN   TestAWKGo/BEGIN_{_print_(1,2),(3,4)_}
+=== RUN   TestAWKGo/BEGIN_{_print_(1,2,(3,4),(5,6))_}
+--- PASS: TestAWKGo
+    --- PASS: TestAWKGo/BEGIN_{_print_"b"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"b"_}#01
+    --- PASS: TestAWKGo/END_{_print_"e"_}
+    --- PASS: TestAWKGo/END_{_print_"e"_}#01
+    --- PASS: TestAWKGo/BEGIN_{_print_"b"}_END_{_print_"e"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"b"}_END_{_print_"e"_}#01
+    --- PASS: TestAWKGo/BEGIN_{_print_"b"}_$0_{_print_NR_}_END_{_print_"e"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"x"_};_BEGIN_{_printf_"y"_}
+    --- PASS: TestAWKGo/$0
+    --- PASS: TestAWKGo/{_print_$0_}
+    --- PASS: TestAWKGo/$1=="foo"
+    --- PASS: TestAWKGo/$1==42
+    --- PASS: TestAWKGo/$1=="42"
+    --- PASS: TestAWKGo//foo/
+    --- PASS: TestAWKGo/NR==2,_NR==4
+    --- PASS: TestAWKGo/_NR==2,_NR==4_{_print_$0_}_NR==3,_NR==5_{_print_NR_}_
+    --- PASS: TestAWKGo/BEGIN_{_print_"x",_"y"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_OFS;_OFS_=_",";_print_"x",_"y"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_ORS;_ORS_=_".";_print_"x",_"y"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_ORS;_ORS_=_"";_print_"x",_"y"_}
+    --- PASS: TestAWKGo/{_print;_print_}
+    --- PASS: TestAWKGo/BEGIN_{_print;_print_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%%_%d_%x_%c_%f_%s",_42,_42,_42,_42,_42_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%3d",_42_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%3s",_"x"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%d",_12,_34_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%d"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_0_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_127_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_128_}__#_!gawk
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_255_}__#_!gawk
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_256_}__#_!awk_!gawk
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_"xyz"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"%c",_""_}__#_!awk
+    --- PASS: TestAWKGo/BEGIN_{_printf_}__#_!awk_-_doesn't_error_on_this
+    --- PASS: TestAWKGo/BEGIN_{_printf("%%%dd",_4)_}
+    --- PASS: TestAWKGo/BEGIN_{_if_(1)_print_"t";_}
+    --- PASS: TestAWKGo/BEGIN_{_if_(0)_print_"t";_}
+    --- PASS: TestAWKGo/BEGIN_{_if_(1)_print_"t";_else_print_"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_if_(0)_print_"t";_else_print_"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(;;)_{_print_"x";_break_}_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(;;)_{_printf_"%d_",_i;_i++;_if_(i>2)_break;_}_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(i=5;_;_)_{_printf_"%d_",_i;_i++;_if_(i>8)_break;_}_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(i=5;_;_i++)_{_printf_"%d_",_i;_if_(i>8)_break;_}_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(i=5;_i<8;_i++)_{_printf_"%d_",_i_}_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(i=0;_i<10;_i++)_{_if_(i_<_5)_continue;_printf_"%d_",_i_}_
+    --- PASS: TestAWKGo/BEGIN_{_a[1]=1;_a[2]=1;_for_(k_in_a)_{_s++;_break_}_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_a[1]=1;_a[2]=1;_a[3]=1;_for_(k_in_a)_{_if_(k==2)_continue;_s++
+    --- PASS: TestAWKGo/BEGIN_{_while_(i<3)_{_i++;_s++;_break_}_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_while_(i<3)_{_i++;_if_(i==2)_continue;_s++_}_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_do_{_i++;_s++;_break_}_while_(i<3);_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_do_{_i++;_if_(i==2)_continue;_s++_}_while_(i<3);_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_for_(k_in_a)_x_+=_a[k];_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_while_(i_<_5)_{_print_i;_i++_}_}
+    --- PASS: TestAWKGo/BEGIN_{_do_{_print_i;_i++_}_while_(i_<_5)_}
+    --- PASS: TestAWKGo/BEGIN_{_for_(i=0;_i<10;_i++);_printf_"x"_}
+    --- PASS: TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1;_i++)_{___for_(j_=_0;_j_<_1;_j++)_{____pri
+    --- PASS: TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1;_i++)_{___for_(j_=_0;_j_<_1;_j++)_{____pri#01
+    --- PASS: TestAWKGo/{_if_(NR==2)_next;_print_}
+    --- PASS: TestAWKGo/BEGIN_{_next_}
+    --- PASS: TestAWKGo/END_{_next_}
+    --- PASS: TestAWKGo/BEGIN_{_a["x"]_=_3;_print_"x"_in_a,_"y"_in_a_}
+    --- PASS: TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_delete_a["x"];_for_(k_in_a)_print_k,_a
+    --- PASS: TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_for_(k_in_a)_delete_a[k];_for_(k_in_a)
+    --- PASS: TestAWKGo/BEGIN_{_a[]_}
+    --- PASS: TestAWKGo/BEGIN_{_delete_a[]_}
+    --- PASS: TestAWKGo/BEGIN_{_a["x"]_=_3;_a["y"]_=_4;_delete_a;_for_(k_in_a)_print_k,_a[k]_}
+    --- PASS: TestAWKGo/BEGIN_{_print_!42,_!1,_!0,_!!42,_!!1,_!!0_}
+    --- PASS: TestAWKGo/BEGIN_{_print_!42,_!1,_!0,_!!42,_!!1,_!!0_}#01
+    --- PASS: TestAWKGo/BEGIN_{_print_+4,_+"3",_+0,_+-3,_-3,_-_-4,_-"3"_}
+    --- PASS: TestAWKGo/BEGIN_{_$0="1";_print_!$0_}
+    --- PASS: TestAWKGo/{_print_!$0_}
+    --- PASS: TestAWKGo/{_print_!$0_}#01
+    --- PASS: TestAWKGo/!seen[$0]++
+    --- PASS: TestAWKGo/!seen[$0]--
+    --- PASS: TestAWKGo/BEGIN_{_print_(1==1,_1==0,_"1"==1,_"1"==1.0)_}
+    --- PASS: TestAWKGo/{_print_($0=="1",_$0==1)_}
+    --- PASS: TestAWKGo/{_print_($1=="1",_$1==1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(1!=1,_1!=0,_"1"!=1,_"1"!=1.0)_}
+    --- PASS: TestAWKGo/{_print_($0!="1",_$0!=1)_}
+    --- PASS: TestAWKGo/{_print_($1!="1",_$1!=1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(0<1,_1<1,_2<1,_"12"<"2")_}
+    --- PASS: TestAWKGo/{_print_($1<2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(0<=1,_1<=1,_2<=1,_"12"<="2")_}
+    --- PASS: TestAWKGo/{_print_($1<=2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(0>1,_1>1,_2>1,_"12">"2")_}
+    --- PASS: TestAWKGo/{_print_($1>2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(0>=1,_1>=1,_2>=1,_"12">="2")_}
+    --- PASS: TestAWKGo/{_print_($1>=2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_1+2,_1+2+3,_1+-2,_-1+2,_"1"+"2",_3+.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_1-2,_1-2-3,_1-+2,_-1-2,_"1"-"2",_3-.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_2*3,_2*3*4,_2*-3,_-2*3,_"2"*"3",_3*.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_2/3,_2/3/4,_2/-3,_-2/3,_"2"/"3",_3/.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_2%3,_2%3%4,_2%-3,_-2%3,_"2"%"3",_3%.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_2^3,_2^3^3,_2^-3,_-2^3,_"2"^"3",_3^.14_}
+    --- PASS: TestAWKGo/BEGIN_{_print_1_2,_"x"_"yz",_1+2_3+4_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"food"~/oo/,_"food"~/[oO]+d/,_"food"~"f",_"food"~"F",_"f
+    --- PASS: TestAWKGo/BEGIN_{_print_"food"!~/oo/,_"food"!~/[oO]+d/,_"food"!~"f",_"food"!~"F"
+    --- PASS: TestAWKGo/BEGIN_{_print_1+2*3/4^5%6_7,_(1+2)*3/4^5%6_"7"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_'\"'_'\''_'xy'_"z"_"'"_'\"'_}
+    --- PASS: TestAWKGo/{_print_/foo/_}
+    --- PASS: TestAWKGo//[a-/
+    --- PASS: TestAWKGo/BEGIN_{_print_"-12"+0,_"+12"+0,_"_\t\r\n7foo"+0,_".5"+0,_"5."+0,_"+."+
+    --- PASS: TestAWKGo/BEGIN_{_print_"1e3"+0,_"1.2e-1"+0,_"1e+1"+0,_"1e"+0,_"1e+"+0_}
+    --- PASS: TestAWKGo/BEGIN_{_print_-(11102200000000000000000000000000000000_1040000)_}__#_!
+    --- PASS: TestAWKGo/BEGIN_{_printf_"\x0.\x00.\x0A\x10\xff\xFF\x41"_}__#_!awk
+    --- PASS: TestAWKGo/BEGIN_{_printf_"\x1.\x01.\x0A\x10\xff\xFF\x41"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"\0\78\7\77\777\0_\141_"_}__#_!awk
+    --- PASS: TestAWKGo/BEGIN_{_printf_"\1\78\7\77\777\1_\141_"_}
+    --- PASS: TestAWKGo/{_print_/x/?"t":"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_1?2?3:4:5,_1?0?3:4:5,_0?2?3:4:5_}
+    --- PASS: TestAWKGo/{_print_$0?1:0_}
+    --- PASS: TestAWKGo/{_print_$0?1:0_}#01
+    --- PASS: TestAWKGo/BEGIN_{_$0="1";_print_($0?1:0)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_0?1:0,_1?1:0,_""?1:0,_"0"?1:0,_"1"?1:0_}
+    --- PASS: TestAWKGo/_BEGIN_{__print_CONVFMT,_1.2345678_""__CONVFMT_=_"%.3g"__print_CONVFMT
+    --- PASS: TestAWKGo/{_print_FNR,_$0_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"|"_FS_"|";_FS=","_}_{_print_$1,_$2_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"|"_FS_"|";_FS="\\."_}_{_print_$1,_$2_}
+    --- PASS: TestAWKGo/BEGIN_{_FS="\\"_}_{_print_$1,_$2_}
+    --- PASS: TestAWKGo/{_print_NF_}
+    --- PASS: TestAWKGo/{_print_NR,_$0_}
+    --- PASS: TestAWKGo/_BEGIN_{__print_OFMT,_1.2345678__OFMT_=_"%.3g"__print_OFMT,_1.234567_}
+    --- PASS: TestAWKGo/BEGIN_{_print_RSTART,_RLENGTH;_RSTART=5;_RLENGTH=42;_print_RSTART,_RLE
+    --- PASS: TestAWKGo/_BEGIN_{__print_SUBSEP__a[1,_2]_=_"onetwo"__print_a[1,_2]__for_(k_in_a
+    --- PASS: TestAWKGo/{_print;_print_$1,_$3,_$NF_}
+    --- PASS: TestAWKGo/{_print_$1,$3;_$2="x";_print;_print_$2_}
+    --- PASS: TestAWKGo/{_print;_$0="x_y_z";_print;_print_$1,_$3_}
+    --- PASS: TestAWKGo/{_print_$1^2_}
+    --- PASS: TestAWKGo/BEGIN_{_$1234567=1_}
+    --- PASS: TestAWKGo/0_in_FS__#_!awk_-_doesn't_flag_this_as_an_error
+    --- PASS: TestAWKGo/BEGIN_{_print_x;_x_=_4;_print_x;_}
+    --- PASS: TestAWKGo/BEGIN_{_a["foo"]=1;_b[2]="x";_k="foo";_print_a[k],_b["2"]_}
+    --- PASS: TestAWKGo/BEGIN_{_s+=5;_print_s;_s-=2;_print_s;_s-=s;_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_x=2;_x*=x;_print_x;_x*=3;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_x=6;_x/=3;_print_x;_x/=x;_print_x;_x/=.6;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_x=12;_x%=5;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_x=2;_x^=5;_print_x;_x^=0.5;_print_x_}
+    --- PASS: TestAWKGo/{_$2+=10;_print;_$3/=2;_print_}
+    --- PASS: TestAWKGo/BEGIN_{_a[2]_+=_1;_a["2"]_*=_3;_print_a[2]_}
+    --- PASS: TestAWKGo/BEGIN_{_print_x++;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_print_x;_print_x++;_print_++x;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_print_x;_print_x--;_print_--x;_print_x_}
+    --- PASS: TestAWKGo/BEGIN_{_s++;_s++;_print_s_}
+    --- PASS: TestAWKGo/BEGIN_{_y="_";_--x[y_=_y_y];_print_length(y)_}
+    --- PASS: TestAWKGo/BEGIN_{_x[y++]++;_print_y_}
+    --- PASS: TestAWKGo/BEGIN_{_x[y++]_+=_3;_print_y_}
+    --- PASS: TestAWKGo/BEGIN_{_$(y++)++;_print_y_}
+    --- PASS: TestAWKGo/BEGIN_{_print_sin(0),_sin(0.5),_sin(1),_sin(-1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_cos(0),_cos(0.5),_cos(1),_cos(-1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_exp(0),_exp(0.5),_exp(1),_exp(-1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_log(0),_log(0.5),_log(1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_log(-1)_}__#_!gawk_-_gawk_prints_warning_for_this_as_wel
+    --- PASS: TestAWKGo/BEGIN_{_print_sqrt(0),_sqrt(2),_sqrt(4)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_int(3.5),_int("1.9"),_int(4),_int(-3.6),_int("x"),_int("
+    --- PASS: TestAWKGo/BEGIN_{_print_match("food",_"foo"),_RSTART,_RLENGTH_}
+    --- PASS: TestAWKGo/BEGIN_{_print_match("x_food_y",_"fo"),_RSTART,_RLENGTH_}
+    --- PASS: TestAWKGo/BEGIN_{_print_match("x_food_y",_"fox"),_RSTART,_RLENGTH_}
+    --- PASS: TestAWKGo/BEGIN_{_print_match("x_food_y",_/[fod]+/),_RSTART,_RLENGTH_}
+    --- PASS: TestAWKGo/{_print_length,_length(),_length("buzz"),_length("")_}
+    --- PASS: TestAWKGo/BEGIN_{_print_index("foo",_"f"),_index("foo0",_0),_index("foo",_"o"),_
+    --- PASS: TestAWKGo/BEGIN_{_print_atan2(1,_0.5),_atan2(-1,_0)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_sprintf("%3d",_42)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_sprintf("%d",_12,_34)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_sprintf("%d")_}
+    --- PASS: TestAWKGo/BEGIN_{_print_sprintf("%d",_12,_34)_}#01
+    --- PASS: TestAWKGo/BEGIN_{_print_sprintf("%_5d",_42)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_1)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_1,_2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_1,_4)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_1,_8)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_2,_2)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_2,_3)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_2,_8)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_0,_8)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_-1,_8)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_substr("food",_5,_8)_}
+    --- PASS: TestAWKGo/BEGIN_{_n_=_split("ab_c_d_",_a);_for_(i=1;_i<=n;_i++)_print_a[i]_}
+    --- PASS: TestAWKGo/BEGIN_{_n_=_split("ab,c,d,",_a,_",");_for_(i=1;_i<=n;_i++)_print_a[i]_
+    --- PASS: TestAWKGo/BEGIN_{_n_=_split("ab,c.d,",_a,_/[,.]/);_for_(i=1;_i<=n;_i++)_print_a[
+    --- PASS: TestAWKGo/BEGIN_{_n_=_split("1_2",_a);_print_(n,_a[1],_a[2],_a[1]==1,_a[2]==2)_}
+    --- PASS: TestAWKGo/BEGIN_{_x_=_"1.2.3";_print_sub(/\./,_",",_x);_print_x_}
+    --- PASS: TestAWKGo/{_print_sub(/\./,_",");_print_$0_}
+    --- PASS: TestAWKGo/BEGIN_{_x_=_"1.2.3";_print_gsub(/\./,_",",_x);_print_x_}
+    --- PASS: TestAWKGo/{_print_gsub(/\./,_",");_print_$0_}
+    --- PASS: TestAWKGo/{_print_gsub(/[0-9]/,_"(&)");_print_$0_}
+    --- PASS: TestAWKGo/{_print_gsub(/[0-9]+/,_"(&)");_print_$0_}
+    --- PASS: TestAWKGo/{_print_gsub(/[0-9]/,_"\\&");_print_$0_}
+    --- PASS: TestAWKGo/{_print_gsub(/[0-9]/,_"\\z");_print_$0_}
+    --- PASS: TestAWKGo/{_print_gsub("0",_"x\\\\y");_print_$0_}__#_!awk_!gawk_--_our_behaviour
+    --- PASS: TestAWKGo/BEGIN_{_print_tolower("Foo_BaR")_}
+    --- PASS: TestAWKGo/BEGIN_{_print_toupper("Foo_BaR")_}
+    --- PASS: TestAWKGo/_BEGIN_{__srand(1)__a_=_rand();_b_=_rand();_c_=_rand()__srand(1)__x_=_
+    --- PASS: TestAWKGo/_BEGIN_{__for_(i_=_0;_i_<_1000;_i++)_{___if_(rand()_<_0.5)_n++__}__pri
+    --- PASS: TestAWKGo/BEGIN_{_print_system("echo_foo");_print_system("echo_bar")_}__#_!fuzz
+    --- PASS: TestAWKGo/BEGIN_{_print_system(">&2_echo_error")_}__#_!fuzz
+    --- PASS: TestAWKGo/BEGIN_{_print_0?"t":"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_1?"t":"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(1+2)?"t":"f"_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(1+2?"t":"f")_}
+    --- PASS: TestAWKGo/BEGIN_{_print(1_?_x="t"_:_"f");_print_x;_}
+    --- PASS: TestAWKGo/BEGIN_{_a[x];_a=42_}
+    --- PASS: TestAWKGo/BEGIN_{_s=42;_s[x]_}
+    --- PASS: TestAWKGo/BEGIN_{_print_fflush();_print_fflush("")_}
+    --- PASS: TestAWKGo/BEGIN_{_print_"x";_print_fflush();_print_"y";_print_fflush("")_}
+    --- PASS: TestAWKGo/BEGIN_{_if_(1)_printf_"x";_else_printf_"y"_}
+    --- PASS: TestAWKGo/BEGIN_{_printf_"x";_{_printf_"y";_printf_"z"_}_}
+    --- PASS: TestAWKGo/BEGIN_{_f()_}
+    --- PASS: TestAWKGo/function_f()_{}_function_f()_{}_BEGIN_{_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(1,2),(3,4)_}
+    --- PASS: TestAWKGo/BEGIN_{_print_(1,2,(3,4),(5,6))_}
+PASS
+ok  	github.com/benhoyt/goawk/awkgo
diff --git a/interp/interp_test.go b/interp/interp_test.go
index a6d088e..ab4a789 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -132,8 +132,9 @@ BEGIN {
 
 	// next statement
 	{`{ if (NR==2) next; print }`, "a\nb\nc", "a\nc\n", "", ""},
-	{`BEGIN { next }`, "", "", "parse error at 1:9: next can't be in BEGIN or END", "BEGIN"},
-	{`END { next }`, "", "", "parse error at 1:7: next can't be in BEGIN or END", "END"},
+	{`{ if (NR==2) f(); print }  function f() { next }`, "a\nb\nc", "a\nc\n", "", ""},
+	{`BEGIN { next }`, "", "", "parse error at 1:9: next can't be inside BEGIN or END", "BEGIN"},
+	{`END { next }`, "", "", "parse error at 1:7: next can't be inside BEGIN or END", "END"},
 
 	// Arrays, "in", and delete
 	{`BEGIN { a["x"] = 3; print "x" in a, "y" in a }`, "", "1 0\n", "", ""},