3017 lines
113 KiB
Go
3017 lines
113 KiB
Go
// Tests for GoAWK interpreter.
|
|
package interp_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/benhoyt/goawk/interp"
|
|
"github.com/benhoyt/goawk/parser"
|
|
)
|
|
|
|
var (
|
|
awkExe string
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
flag.StringVar(&awkExe, "awk", "gawk", "awk executable name")
|
|
flag.Parse()
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
type interpTest struct {
|
|
src string // if this includes "!awk" or "!gawk" those interpreters won't be run
|
|
in string
|
|
out string
|
|
err string // error from GoAWK must equal this
|
|
awkErr string // error from awk/gawk must contain this
|
|
}
|
|
|
|
// Note: a lot of these are really parser tests too.
|
|
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", "", ""},
|
|
|
|
// Presence of patterns and actions
|
|
{``, "foo", "", "", ""}, // no pattern, no action
|
|
{`0`, "foo", "", "", ""}, // false pattern, no action
|
|
{`1`, "foo", "foo\n", "", ""}, // true pattern, no action
|
|
{`{}`, "foo", "", "", ""}, // no pattern, empty action (issue #228)
|
|
{`0 {}`, "foo", "", "", ""}, // false pattern, empty action
|
|
{`1 {}`, "foo", "", "", ""}, // true pattern, empty action (issue #261)
|
|
{`{ print $1 }`, "foo", "foo\n", "", ""}, // no pattern, non-empty action
|
|
{`0 { print $1 }`, "foo", "", "", ""}, // false pattern, non-empty action
|
|
{`1 { print $1 }`, "foo", "foo\n", "", ""}, // true pattern, non-empty action
|
|
|
|
// 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", "", ""},
|
|
{`/foo/ { print NR } /foo/`, "foo\nx\nfood\n", "1\nfoo\n3\nfood\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", "", ""},
|
|
|
|
// field separator cannot match empty field
|
|
{`BEGIN {FS = "[^,]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `x,,z`, "1 <>\n2 <,,>\n3 <>\n", "", ""},
|
|
{`BEGIN {FS = "[^a-z]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `xyz`, "1 <xyz>\n", "", ""},
|
|
{`BEGIN {FS = "[0-9]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `xyz`, "1 <xyz>\n", "", ""},
|
|
{`BEGIN {FS = "(abc)?"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `abcdef`, "1 <>\n2 <def>\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 } # !windows-gawk`, "", "4e+01", "", ""}, // for some reason gawk gives "4e+001" on Windows
|
|
{`BEGIN { printf "%d", 12, 34 }`, "", "12", "", ""},
|
|
{`BEGIN { printf "%d" }`, "", "", "format error: got 0 args, expected 1", "not enough arg"},
|
|
// 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`, "", "\x80", "", ""},
|
|
{`BEGIN { printf "%c", 255 } # !gawk`, "", "\xff", "", ""},
|
|
{`BEGIN { printf "%c", 256 } # !gawk`, "", "\x00", "", ""},
|
|
{`BEGIN { printf "%c", "xyz" }`, "", "x", "", ""},
|
|
{`BEGIN { printf "%c", "" } # !awk`, "", "\x00", "", ""},
|
|
{`BEGIN { printf } # !awk !posix - doesn't error on this`, "", "", "parse error at 1:16: expected printf args, got none", "printf: no arguments"},
|
|
{`BEGIN { printf("%%%dd", 4) }`, "", "%4d", "", ""},
|
|
{`BEGIN { printf "%d %i %o %u %x %X", 42, 42, 42, 42, 42, 42 }`, "", "42 42 52 42 2a 2A", "", ""},
|
|
{`BEGIN { printf "%d %i %o %u %x %X", -42, -42, -42, -42, -42, -42 }`, "", "-42 -42 1777777777777777777726 18446744073709551574 ffffffffffffffd6 FFFFFFFFFFFFFFD6", "", ""},
|
|
// Note that Go's fmt %a/%A always has a two-digit exponent with a leading zero, but who cares -- it's rarely used.
|
|
// Also, the output is normalised differently on Gawk under Windows as 0x9.a51eb851eb85p+7, so skip there.
|
|
{`BEGIN { printf "%a %A\n", 1234.56, 1234.56 } # !windows-gawk`, "", "0x1.34a3d70a3d70ap+10 0X1.34A3D70A3D70AP+10\n", "", ""},
|
|
|
|
// 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 { if (1==1) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (1==2) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (1!=1) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (1!=2) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (1>2) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (2>1) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (1>2) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (2>1) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (1>=2) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (2>=1) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (1<2) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (2<1) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if (1<=2) print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if (2<=1) print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("a"=="a") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("a"=="b") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("a"!="a") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("a"!="b") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("a">"b") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("b">"a") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("a">"b") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("b">"a") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("a">="b") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("b">="a") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("a"<"b") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("b"<"a") print "t"; else print "f" }`, "", "f\n", "", ""},
|
|
{`BEGIN { if ("a"<="b") print "t"; else print "f" }`, "", "t\n", "", ""},
|
|
{`BEGIN { if ("b"<="a") 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=3; i>0; i--) { printf "%d ", i } }`, "", "3 2 1 ", "", ""},
|
|
{`BEGIN { for (i=3; i>=0; i--) { printf "%d ", i } }`, "", "3 2 1 0 ", "", ""},
|
|
{`BEGIN { for (i=0; i<10; i++) { if (i < 5) continue; printf "%d ", i } }`, "", "5 6 7 8 9 ", "", ""},
|
|
{`BEGIN { for (i=0; i<100; i++) s+=i; print s }`, "", "4950\n", "", ""},
|
|
{`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", "", ""},
|
|
{`function alen(a, k, n) { n=0; for (k in a) n++; return n } BEGIN { a[1]=1; a[2]=1; print alen(a) }`, "", "2\n", "", ""},
|
|
{`BEGIN { a["x"]=1; for (SUBSEP in a) print SUBSEP, a[SUBSEP] }`, "", "x 1\n", "", ""},
|
|
// TODO: this doesn't work in GoAWK yet
|
|
// {`BEGIN { a["x"]=1; for (NR in a) print NR, a[NR] }`, "", "x 1\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++ } }`, "", "\n1\n2\n3\n4\n", "", ""},
|
|
{`BEGIN { do { print i; i++ } while (i < 5) }`, "", "\n1\n2\n3\n4\n", "", ""},
|
|
{`BEGIN { for (i=0; i<10; i++); printf "x" }`, "", "x", "", ""},
|
|
{`BEGIN { s="x"; while (s=="x") { print s; s="y" } }`, "", "x\n", "", ""},
|
|
{`BEGIN { s="x"; while (s!="") { print s; s="" } }`, "", "x\n", "", ""},
|
|
{`BEGIN { s="x"; while (s) { print s; s="" } }`, "", "x\n", "", ""},
|
|
{"BEGIN { do { print 1 }\nwhile (0) }", "", "1\n", "", ""},
|
|
// 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 and nextfile statements (more tests of nextfile in goawk_test.go)
|
|
{`{ 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", "", ""},
|
|
{`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"},
|
|
{`{ if (NR==2) nextfile; print }`, "a\nb\nc", "a\n", "", ""},
|
|
{`{ if (NR==2) f(); print } function f() { nextfile }`, "a\nb\nc", "a\n", "", ""},
|
|
{`BEGIN { nextfile }`, "", "", "parse error at 1:9: nextfile can't be inside BEGIN or END", "BEGIN"},
|
|
{`END { nextfile }`, "", "", "parse error at 1:7: nextfile 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", "", ""},
|
|
{`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 ]", "syntax error"},
|
|
{`BEGIN { delete a[] }`, "", "", "parse error at 1:18: expected expression instead of ]", "syntax error"},
|
|
{`BEGIN { a["x"] = 3; a["y"] = 4; delete a; for (k in a) print k, a[k] }`, "", "", "", ""},
|
|
{`function f(a) { print "x" in a, "y" in a } BEGIN { b["x"] = 3; f(b) }`, "", "1 0\n", "", ""},
|
|
|
|
// 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", "", ""},
|
|
{`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", "", ""},
|
|
{`{ print($0<2) }`, "10", "0\n", "", ""},
|
|
{`{ print($1<2) }`, "10", "0\n", "", ""},
|
|
{`{ print($1<2) }`, "10x", "1\n", "", ""},
|
|
{`BEGIN { $0="10"; print($0<2) }`, "", "1\n", "", ""},
|
|
{`BEGIN { $1="10"; print($1<2) }`, "", "1\n", "", ""},
|
|
{`BEGIN { $1="10x"; print($1<2) }`, "", "1\n", "", ""},
|
|
|
|
// Short-circuit && and || operators
|
|
{`
|
|
function t() { print "t"; return 2 }
|
|
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 2 }
|
|
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", "", ""},
|
|
{`BEGIN { print 0&&0, 0&&2, 2&&0, 2&&2 }`, "", "0 0 0 1\n", "", ""},
|
|
{`BEGIN { print 0||0, 0||2, 2||0, 2||2 }`, "", "0 1 1 1\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 } # !darwin-gawk`, "", "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 } # !darwin-gawk`, "", "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 2**3, 2**3**3, 2**-3, -2**3, "2"**"3", 3**.14 } # !posix`, "", "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", "", ""},
|
|
{`BEGIN { print 1/0 }`, "", "", "division by zero", "division by zero"},
|
|
{`BEGIN { print 1%0 }`, "", "", "division by zero in mod", "division by zero"},
|
|
{`BEGIN { x /= 0 }`, "", "", "division by zero", "division by zero"},
|
|
{`BEGIN { x %= 0 }`, "", "", "division by zero in mod", "division by zero"},
|
|
|
|
// 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", "", "invalid char"}, // Check support for single-quoted strings
|
|
{`BEGIN { print "0\n1\t2\r3\a4\b5\f6\v7\x408\xf" } # !posix`, "", "0\n1\t2\r3\a4\b5\f6\v7@8\x0f\n", "", ""},
|
|
{`BEGIN { print "The \u201cQUICK\u201d brown fox \u1F602" } # !gawk`, "", "The “QUICK” brown fox 😂\n", "", ""},
|
|
{`{ print /foo/ }`, "food\nfoo\nxfooz\nbar\n", "1\n1\n1\n0\n", "", ""},
|
|
{`/[a-/`, "foo", "", "parse error at 1:1: error parsing regexp: invalid character class range: `a-)`", "terminated"},
|
|
{`/\Q/ # !gawk`, "", "", "parse error at 1:1: error parsing regexp: missing closing ): `(?s:\\Q)`", ""}, // Gawk produces a warning (not an error), so skip
|
|
{`/=foo/`, "=foo", "=foo\n", "", ""},
|
|
{`BEGIN { RS="x" } /^a.*c$/`, "a\nb\nc", "a\nb\nc\n", "", ""},
|
|
{`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 !posix`, "", "\x00.\x00.\n\x10\xff\xffA", "", ""},
|
|
{`BEGIN { printf "\x1.\x01.\x0A\x10\xff\xFF\x41" } # !posix`, "", "\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 ", "", ""},
|
|
|
|
// Unusual number/exponent handling
|
|
{`BEGIN { e="x"; E="X"; print 1e, 1E }`, "", "1x 1X\n", "", ""},
|
|
{`BEGIN { e="x"; E="X"; print 1e1e, 1E1E }`, "", "10x 10X\n", "", ""},
|
|
{`BEGIN { a=2; print 1e+a, 1E+a, 1e+1, 1E+1 }`, "", "12 12 10 10\n", "", ""},
|
|
{`BEGIN { a=2; print 1e-a, 1E-a, 1e-1, 1E-1 }`, "", "1-2 1-2 0.1 0.1\n", "", ""},
|
|
{`BEGIN { print 1e+ }`, "", "", "parse error at 1:19: expected expression instead of }", "syntax error"},
|
|
{`BEGIN { print 1e- }`, "", "", "parse error at 1:19: expected expression instead of }", "syntax error"},
|
|
|
|
// 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", "", ""},
|
|
{`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, x?1:0 }`, "", "0 1 0 1 1 0\n", "", ""},
|
|
|
|
// Built-in variables
|
|
{`BEGIN { print ARGC; ARGC=42; print ARGC } # !gawk`, "", "1\n42\n", "", ""}, // ARGC is properly tested in goawk_test.go
|
|
{`BEGIN { ARGC=1234567 }`, "", "", "ARGC set too large: 1234567", ""},
|
|
{`
|
|
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", "", ""},
|
|
{`{ print NR, FNR } END { print NR, FNR }`, "a\nb\nc\n", "1 1\n2 2\n3 3\n3 3\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", "", ""},
|
|
// ASCII unit and record separator
|
|
{`BEGIN { FS="\x1f"; RS="\x1e"; OFS="," } { print $1, $2, $3 } # !posix`,
|
|
"id\x1fname\x1fage\x1e1\x1fBob \"Billy\" Smith\x1f42\x1e2\x1fJane\nBrown\x1f37",
|
|
"id,name,age\n1,Bob \"Billy\" Smith,42\n2,Jane\nBrown,37\n", "", ""},
|
|
// Unicode unit and record separator (skip on Windows under gawk due to Unicode command line issues)
|
|
{`BEGIN { FS="␟"; RS="␞"; OFS="," } { print $1, $2, $3 } # !windows-gawk !posix`,
|
|
"id␟name␟age␞1␟Bob \"Billy\" Smith␟42␞2␟Jane\nBrown␟37",
|
|
"id,name,age\n1,Bob \"Billy\" Smith,42\n2,Jane\nBrown,37\n", "", ""},
|
|
{`BEGIN { FS="\\" } { print $1, $2 }`, "a\\b", "a b\n", "", ""},
|
|
{`BEGIN { RS="x"; FS=",.*," } { for (i=1; i<=NF; i++) print $i }`, "one,\n,two", "one\ntwo\n", "", ""},
|
|
{`BEGIN { FS="x"; RS=",.*," } { print } # !posix`, "one,\n,two", "one\ntwo\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 { RS="ö" } { print } # !windows-gawk`, "1ötwoöthree", "1\ntwo\nthree\n", "", ""},
|
|
{`BEGIN { RS="\\.+" } { print } # !posix`, "1.two..three...4.", "1\ntwo\nthree\n4\n", "", ""},
|
|
{`BEGIN { RS = "\n|( *[[:upper:]]+ *)" } { print "Record =", $0,"and RT = [" RT "]" } # !posix`, // from https://www.gnu.org/software/gawk/manual/html_node/gawk-split-records.html
|
|
"record 1 AAAA record 2 BBBB record 3\n",
|
|
`Record = record 1 and RT = [ AAAA ]
|
|
Record = record 2 and RT = [ BBBB ]
|
|
Record = record 3 and RT = [
|
|
]
|
|
`, "", ""},
|
|
{`BEGIN { RS = "\n|( *[[:upper:]]+ *)" } { print "Record =", $0,"and RT = [" RT "]" } # !posix`,
|
|
"record 1 AAAA record 2 BBBB record 3",
|
|
`Record = record 1 and RT = [ AAAA ]
|
|
Record = record 2 and RT = [ BBBB ]
|
|
Record = record 3 and RT = []
|
|
`, "", ""},
|
|
{`BEGIN { RS=".." } { print $0 RT } # !posix`, "foo bar bazz", "fo\no \nba\nr \nba\nzz\n", "", ""},
|
|
{`BEGIN { RT="foo"; print RT }`, "", "foo\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", "", ""},
|
|
// Ensure we use the value of FS when the record was read (not when the
|
|
// fields are lazily split).
|
|
{`{ FS=","; print $1; print $2 }`, "a,b c", "a,b\nc\n", "", ""},
|
|
{`{ FS="X+"; print $1; print $2 }`, "aXXb c", "aXXb\nc\n", "", ""},
|
|
{`BEGIN { FS="," } { print $1; print $2 }`, "a,b c", "a\nb c\n", "", ""},
|
|
{`BEGIN { FS="X+" } { print $1; print $2 }`, "aXXb c", "a\nb c\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 }`, "a\nb c\nd e f\n", "a\nc\nf\n", "", "field -1"},
|
|
{`{ print $-2 }`, "a\nb c\nd e f\n", "\nb\ne\n", "", "field -2"},
|
|
{`{ print $-3 }`, "a\nb c\nd e f\n", "\n\nd\n", "", "field -3"},
|
|
{`{ $-1="x"; print }`, "a\nb c\nd e f\n", "x\nb x\nd e x\n", "", "field -1"},
|
|
{`{ $-2="y"; print }`, "a\nb c\nd e f\n", "a\ny c\nd y f\n", "", "field -2"},
|
|
{`{ $-3="z"; print }`, "a\nb c\nd e f\n", "a\nb c\nz e f\n", "", "field -3"},
|
|
{`{ NF=-1; } # !awk - awk allows setting negative NF`,
|
|
"x", "", "NF set to negative value: -1", "negative value"},
|
|
{`{ 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`, "array"},
|
|
{`{ $$0++; print $0 }`, "2 3 4", "3\n", "", ""},
|
|
{`BEGIN { $0="3 4 5 6 7 8 9"; a=3; print $$a++++; print }`, "", "7\n3 4 6 6 8 8 9\n", "", ""},
|
|
{`BEGIN { n = split("12345", a, ""); i = 1; a[a[a[1]++]++]++; for (i=1;i<=n;i++) printf a[i]; print }`, "", "33345\n", "", ""},
|
|
{`
|
|
BEGIN {
|
|
a[1] = "ciao"
|
|
a[2] = "."
|
|
a[3] = ".."
|
|
print gsub("ciao", "hello", a[gsub(".", ",", a[gsub(".", ",", a[f()])])])
|
|
print a[1]
|
|
print a[2]
|
|
print a[3]
|
|
}
|
|
|
|
function f() {
|
|
print "f"
|
|
return 3
|
|
}
|
|
`, "", "f\n1\nhello\n,\n,,\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; }`, "", "\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", "", ""},
|
|
{`BEGIN { x=2; x**=5; print x; x**=0.5; print x } # !posix`, "", "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", "", ""},
|
|
{`function inc(x, n) { x += n; return x } BEGIN { print inc(3, 2) }`, "", "5\n", "", ""},
|
|
{`function inca(a, k, n) { a[k] += n } BEGIN { b["x"]=7; inca(b, "x", 2); print b["x"] }`, "", "9\n", "", ""},
|
|
{`BEGIN { NF += 3; print NF }`, "", "3\n", "", ""},
|
|
{`BEGIN { x=1; x += x+=3; print x }`, "", "8\n", "", ""},
|
|
{`BEGIN { if (1&&x=2) print "t", x }`, "", "t 2\n", "", ""},
|
|
{`BEGIN { if (0||x+=2) print "t", x }`, "", "t 2\n", "", ""},
|
|
{`BEGIN { print(1&&x=2, 1||x=2, 1~x=2, 1!~x=2, 1==x=2, 1!=x=2, 1<x=2, 1<=x=2, 1>x=2, 1>=x=2); print x }`,
|
|
"", "1 1 0 1 0 1 1 1 0 0\n2\n", "", ""},
|
|
{`BEGIN { print a[f()]+=g(); print a["x"] } function f() { print "f"; return "x" } function g() { print "g"; return 1 }`,
|
|
"", "g\nf\n1\n1\n", "", ""},
|
|
{`{ print $f()+=g(); print } function f() { print "f"; return 2 } function g() { print "g"; return 1 }`,
|
|
"1 2 3", "g\nf\n3\n1 3 3\n", "", ""},
|
|
{`BEGIN { x += x++; print x }`, "", "1\n", "", ""},
|
|
{`BEGIN { print(x += x++); print x }`, "", "1\n1\n", "", ""},
|
|
|
|
// Incr/decr expressions
|
|
{`BEGIN { print x++; print x }`, "", "0\n1\n", "", ""},
|
|
{`BEGIN { print x; print x++; print ++x; print x }`, "", "\n0\n2\n2\n", "", ""},
|
|
{`BEGIN { print x; print x--; print --x; print x }`, "", "\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", "", ""},
|
|
{`BEGIN { print "s" ++n; print "s" --n }`, "", "s1\ns0\n", "", ""},
|
|
{`function inc(x) { x++; return x } BEGIN { print inc(3) }`, "", "4\n", "", ""},
|
|
{`function inca(a, k) { a[k]++ } BEGIN { b["x"]=7; inca(b, "x"); print b["x"] }`, "", "8\n", "", ""},
|
|
{`BEGIN { NF++; print NF }`, "", "1\n", "", ""},
|
|
{"{ print $++n; print $--n }", "x y", "x\nx y\n", "", ""},
|
|
{"{ print $++a }", "1 2 3\na b c\nd e f\n", "1\nb\nf\n", "", ""},
|
|
{"BEGIN{ a = 3; b = 7; c = (a)++b; print a, b, c }", "", "3 8 38\n", "", ""},
|
|
{`BEGIN { print ++a[f()]; print a["x"] } function f() { print "f"; return "x" }`,
|
|
"", "f\n1\n1\n", "", ""},
|
|
{`BEGIN { print a[f()]++; print a["x"] } function f() { print "f"; return "x" }`,
|
|
"", "f\n0\n1\n", "", ""},
|
|
{`{ print ++$f(); print } function f() { print "f"; return 2 }`,
|
|
"1 2 3", "f\n3\n1 3 3\n", "", ""},
|
|
{`{ print $f()++; print } function f() { print "f"; return 2 }`,
|
|
"1 2 3", "f\n2\n1 3 3\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", "", ""},
|
|
{`BEGIN { print match("a\nb\nc", /^a.*c$/), RSTART, RLENGTH }`, "", "1 1 5\n", "", ""},
|
|
{`{ print length, length(), length("buzz"), length("") }`, "foo bar", "7 7 4 0\n", "", ""},
|
|
{`{ a[$0]++ } END { print length(a) } # !posix`, "a\nc\nb\na\na\nb", "3\n", "", ""},
|
|
{`BEGIN { 1 in a; print length(a); a[1]; a[2]=2; a[2]=3; print length(a) } # !gawk !posix`, "", "0\n2\n", "", ""},
|
|
// There's a quirk in Gawk where "a" has to be referenced as an array first, so skip Gawk for now:
|
|
{`BEGIN { print length(a); a[1]; a[2]=2; a[2]=3; print length(a) } # !gawk !posix`, "", "0\n2\n", "", ""},
|
|
{`BEGIN { a[1]=1; a[2]=2; a[3]=3; print len(a) } function len(x) { return length(x) } # !posix`, "", "3\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") }`, "", "", "format error: got 0 args, expected 1", "not enough arg"},
|
|
{`BEGIN { print sprintf("%d", 12, 34) }`, "", "12\n", "", ""},
|
|
{`BEGIN { print sprintf("% 5d", 42) }`, "", " 42\n", "", ""},
|
|
{`BEGIN { print sprintf("%*s %.*s", 5, "abc", 5, "abcdefghi") }`, "", " abc abcde\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) }`, "", "\n", "", ""},
|
|
{`BEGIN { print substr("food", -1) }`, "", "food\n", "", ""},
|
|
{`BEGIN { print substr("food", 5, 8) }`, "", "\n", "", ""},
|
|
{`BEGIN { print substr("food", 2, -3), substr("fööd", 2, -3) }`, "", " \n", "", ""},
|
|
{`BEGIN { n = split("", a); for (i=1; i<=n; i++) print a[i] }`, "", "", "", ""},
|
|
{`BEGIN { n = split("", a, "."); for (i=1; i<=n; i++) print a[i] }`, "", "", "", ""},
|
|
{`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", "", ""},
|
|
{`BEGIN { x = "1.2.3"; print sub(/\./, ",\\", x); print x }`, "", "1\n1,\\2.3\n", "", ""},
|
|
{`BEGIN { a["x"] = "1.2.3"; print sub(/\./, ",", a[f()]); print a["x"] } function f() { print "f"; return "x" }`,
|
|
"", "f\n1\n1,2.3\n", "", ""},
|
|
{`{ print sub(/\./, ",", $f()); print } function f() { print "f"; return 2 }`,
|
|
"x 1.2.3 y", "f\n1\nx 1,2.3 y\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`", ""},
|
|
// ensure leftmost-longest matching like other awks
|
|
{`BEGIN { s = "#!a"; sub(/(#|#!)/, "", s); print s }`, "", "a\n", "", ""},
|
|
|
|
{`BEGIN { print tolower("Foo BaR") }`, "", "foo bar\n", "", ""},
|
|
{`BEGIN { print toupper("Foo BaR") }`, "", "FOO BAR\n", "", ""},
|
|
{`
|
|
BEGIN {
|
|
srand()
|
|
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 { s = srand(srand()); print (s > 1700000000) }`, "", "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", "", ""},
|
|
{`BEGIN { print system("exit 42") } # !fuzz !posix`, "", "42\n", "", ""},
|
|
{`BEGIN { system("cat") }`, "foo\nbar", "foo\nbar", "", ""},
|
|
{`BEGIN { print system("exec /bin/kill -9 $$") } # !awk !posix !windows`, "", "265\n", "", ""},
|
|
|
|
// Test bytes/unicode handling (GoAWK currently has char==byte, unlike Gawk).
|
|
{`BEGIN { print match("food", "foo"), RSTART, RLENGTH } # !gawk`, "", "1 1 3\n", "", ""},
|
|
{`BEGIN { print match("x food y", "fo"), RSTART, RLENGTH } # !gawk`, "", "3 3 2\n", "", ""},
|
|
{`BEGIN { print match("x food y", "fox"), RSTART, RLENGTH } # !gawk`, "", "0 0 -1\n", "", ""},
|
|
{`BEGIN { print match("x food y", /[fod]+/), RSTART, RLENGTH } # !gawk`, "", "3 3 4\n", "", ""},
|
|
{`BEGIN { print match("絵 fööd y", /[föd]+/), RSTART, RLENGTH } # !gawk`, "", "5 5 6\n", "", ""},
|
|
{`{ print length, length(), length("buzz"), length("") } # !gawk`, "foo bar", "7 7 4 0\n", "", ""},
|
|
{`BEGIN { print length("a"), length("絵") } # !gawk`, "", "1 3\n", "", ""},
|
|
{`BEGIN { print index("foo", "f"), index("foo0", 0), index("foo", "o"), index("foo", "x") } # !gawk`, "", "1 4 2 0\n", "", ""},
|
|
{`BEGIN { print index("föö", "f"), index("föö0", 0), index("föö", "ö"), index("föö", "x") } # !gawk`, "", "1 6 2 0\n", "", ""},
|
|
{`BEGIN { print substr("food", 1), substr("fööd", 1) } # !gawk`, "", "food fööd\n", "", ""},
|
|
{`BEGIN { print substr("food", 1, 2), substr("fööd", 1, 2) } # !gawk`, "", "fo f\xc3\n", "", ""},
|
|
{`BEGIN { print substr("food", 1, 4), substr("fööd", 1, 4) } # !gawk`, "", "food fö\xc3\n", "", ""},
|
|
{`BEGIN { print substr("food", 1, 8), substr("fööd", 1, 8) } # !gawk`, "", "food fööd\n", "", ""},
|
|
{`BEGIN { print substr("food", 2), substr("fööd", 2) } # !gawk`, "", "ood ööd\n", "", ""},
|
|
{`BEGIN { print substr("food", 2, 2), substr("fööd", 2, 2) } # !gawk`, "", "oo ö\n", "", ""},
|
|
{`BEGIN { print substr("food", 2, 3), substr("fööd", 2, 3) } # !gawk`, "", "ood ö\xc3\n", "", ""},
|
|
{`BEGIN { print substr("food", 2, 8), substr("fööd", 2, 8) } # !gawk`, "", "ood ööd\n", "", ""},
|
|
{`BEGIN { print substr("food", 0, 8), substr("fööd", 0, 8) } # !gawk`, "", "food fööd\n", "", ""},
|
|
{`BEGIN { print substr("food", -1, 8), substr("fööd", -1, 8) } # !gawk`, "", "food fööd\n", "", ""},
|
|
{`BEGIN { print substr("food", 5, 8), substr("fööd", 5, 8) } # !gawk`, "", " \xb6d\n", "", ""},
|
|
{`BEGIN { print substr("food", 2, -3), substr("fööd", 2, -3) } # !gawk`, "", " \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
|
|
loc = loc * 2
|
|
print glob, loc
|
|
}
|
|
BEGIN {
|
|
glob = 1
|
|
loc = 42
|
|
f(3)
|
|
print loc
|
|
f(4)
|
|
print loc
|
|
}
|
|
`, "", "2 8\n42\n3 10\n42\n", "", ""},
|
|
{`
|
|
function set(a, x, v) { a[x] = v }
|
|
function get(a, x) { return a[x] }
|
|
function get2(x, a) { return a[x] }
|
|
function get3(x, a, b) { b[0]; 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")
|
|
print get2("x", a), get2("y", b)
|
|
print get3("x", a), get2("y", b)
|
|
}
|
|
`, "", "x 1\n---\ny 2\n---\n1 2\n1 2\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`, "attempt to use scalar"},
|
|
{`
|
|
function early() {
|
|
print "x"
|
|
return
|
|
print "y"
|
|
}
|
|
BEGIN { early() }
|
|
`, "", "x\n", "", ""},
|
|
{`BEGIN { return }`, "", "", "parse error at 1:9: return must be inside a function", "return"},
|
|
{`function f() { printf "x" }; BEGIN { f() } `, "", "x", "", ""},
|
|
{`BEGIN { arr[0]; f(arr) } function f(a) { printf "x" }`, "", "x", "", ""},
|
|
{`function f(x) { 0 in _; f(_) } BEGIN { f() } # !awk !gawk`, "", "", `calling "f" exceeded maximum call depth of 1000`, ""},
|
|
{`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", "", ""},
|
|
{`
|
|
function f1(x) { }
|
|
function f2(x, y) { return x[y] }
|
|
BEGIN { a[1]=2; f1(a); print f2(a, 1) }
|
|
`, "", "2\n", "", ""},
|
|
{`BEGIN { arr[0]; f(arr) } function f(a) { print "x" }`, "", "x\n", "", ""},
|
|
{`function add(a, b) { return a+b } BEGIN { print add(1, 2), add(1), add() }`, "", "3 1 0\n", "", ""},
|
|
{`function f1(A) {} function f2(x, A) { x[0]; f1(a); f2(a) }`, "", "", "", ""}, // found via fuzzing
|
|
|
|
// Type checking / resolver tests
|
|
{`BEGIN { a[x]; a=42 }`, "", "", `parse error at 1:15: can't use array "a" as scalar`, "array"},
|
|
{`BEGIN { s=42; s[x] }`, "", "", `parse error at 1:15: can't use scalar "s" as array`, "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:63: can't pass scalar "a" as array param`, "attempt to use scalar parameter `a' as an array"},
|
|
{`function get(a, k) { return a+k } BEGIN { a[42]; print get(a, 1); }`,
|
|
"", "", `parse error at 1:60: can't pass array "a" as scalar param`, "array"},
|
|
{`{ 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:37: can't pass scalar "FS" as array param`, "attempt to use scalar parameter `x' as an array"},
|
|
{`
|
|
function foo(x) { print "foo", x }
|
|
function bar(foo) { print "bar", foo }
|
|
BEGIN { foo(5); bar(10) }
|
|
# !posix
|
|
`, "", "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 name"},
|
|
{`function foo() { print foo } BEGIN { foo() }`,
|
|
"", "", `parse error at 1:24: global var "foo" can't also be a function`, "function"},
|
|
{`function f(x) { print x, x(); } BEGIN { f() }`, "", "", `parse error at 1:26: can't call local variable "x" as function`, "function"},
|
|
{`function f(x) { print x } BEGIN { print f(1, 2) } # !gawk`, // only a warning in Gawk
|
|
"", "", `parse error at 1:41: "f" called with more arguments than declared`, ""},
|
|
{`# mutually recursive functions
|
|
function f(n) { if (!n) return; print "f(" n ")"; g(n-1) }
|
|
function g(n) { if (!n) return; print "g(" n ")"; f(n-1) }
|
|
BEGIN { f(4) }
|
|
`, "", "f(4)\ng(3)\nf(2)\ng(1)\n", "", ""},
|
|
{`
|
|
function f1(a) { f2(a) }
|
|
function f2(b) { f3(b) }
|
|
function f3(c) { f4(c) }
|
|
function f4(d) { f5(d) }
|
|
function f5(i) { i[1]=42 }
|
|
BEGIN { x[1]=3; f5(x); print x[1] }
|
|
`, "", "42\n", "", ""},
|
|
{`
|
|
function f1(a) { f2(a) }
|
|
function f2(b) { f3(b) }
|
|
function f5(i) { i[1]++ }
|
|
function f4(d) { f5(d) }
|
|
function f3(c) { f4(c) }
|
|
BEGIN { x[1]=3; f5(x); print x[1] }
|
|
`, "", "4\n", "", ""},
|
|
{`
|
|
function f1(a) { if (0) f5(z1); f2(a) }
|
|
function f2(b) { if (0) f4(z2); f3(b) }
|
|
function f3(c) { if (0) f3(z3); f4(c) }
|
|
function f4(d) { if (0) f2(z4); f5(d) }
|
|
function f5(i) { if (0) f1(z5); i[1]=42 }
|
|
BEGIN { x[1]=3; f5(x); print x[1] }
|
|
`, "", "42\n", "", ""},
|
|
|
|
// Redirected I/O
|
|
{`BEGIN { getline x; print x }`, "foo", "foo\n", "", ""},
|
|
{`function f(x) { getline x; print x } BEGIN { f(); print x }`, "foo", "foo\n\n", "", ""},
|
|
{`BEGIN { getline SUBSEP; print SUBSEP }`, "foo", "foo\n", "", ""},
|
|
{`BEGIN { getline a[1]; print a[1] }`, "foo", "foo\n", "", ""},
|
|
{`BEGIN { getline $1; print $1 }`, "foo", "foo\n", "", ""},
|
|
{`BEGIN { "echo foo" | getline a[1]; print a[1] }`, "", "foo\n", "", ""},
|
|
{`BEGIN { "echo foo" | getline $1; print $1 }`, "", "foo\n", "", ""},
|
|
{`BEGIN { print "foo" |"sort"; print "bar" |"sort" } # !fuzz`, "", "bar\nfoo\n", "", ""},
|
|
{`BEGIN { print "foo" |">&2 echo error" } # !gawk !fuzz`, "", "error\n", "", ""},
|
|
{`BEGIN { "cat" | getline; print } # !fuzz`, "bar", "bar\n", "", ""},
|
|
{`BEGIN { print getline x < "/no/such/file" } # !fuzz`, "", "-1\n", "", ""},
|
|
{`BEGIN { print getline "z"; print $0 }`, "foo", "1z\nfoo\n", "", ""},
|
|
{`BEGIN { print getline x+1; print x }`, "foo", "2\nfoo\n", "", ""},
|
|
{`BEGIN { print getline (x+1); print $0 }`, "foo", "11\nfoo\n", "", ""},
|
|
{`BEGIN { print getline foo(); print $0 } function foo() { print "z" }`, "foo", "z\n1\nfoo\n", "", ""},
|
|
{`BEGIN { print("echo foo" | getline x+1); print x }`, "", "2\nfoo\n", "", ""},
|
|
{`BEGIN { print("echo foo" | getline $0+1); print }`, "", "2\nfoo\n", "", ""},
|
|
{`BEGIN { print("echo foo" | getline ($0+1)); print }`, "", "11\nfoo\n", "", ""},
|
|
{`BEGIN { print("echo foo" | getline foo()); print } function foo() { print "z" }`, "", "z\n1\nfoo\n", "", ""},
|
|
{`BEGIN {
|
|
print "foo" >"out"
|
|
print close("out")
|
|
print "bar" >"out"
|
|
print close("out")
|
|
getline <"out"
|
|
print $0
|
|
print close("out")
|
|
print close("out")
|
|
}`, "", "0\n0\nbar\n0\n-1\n", "", ""},
|
|
{`BEGIN {
|
|
print "foo" >"out"
|
|
print "bar" >"out"
|
|
print close("out")
|
|
getline <"out"
|
|
print $0
|
|
print close("out")
|
|
getline <"out"
|
|
print $0
|
|
print close("out")
|
|
print close("out")
|
|
}`, "", "0\nfoo\n0\nfoo\n0\n-1\n", "", ""},
|
|
{`BEGIN { print close("nothing") }`, "", "-1\n", "", ""},
|
|
{`BEGIN {
|
|
print "foo">"out"
|
|
close("out")
|
|
print "bar">>"out"
|
|
close("out")
|
|
getline <"out"
|
|
print $0
|
|
getline <"out"
|
|
print $0
|
|
}`, "", "foo\nbar\n", "", ""},
|
|
{`BEGIN { print "x" | "cat"; close("cat"); print "y" }`, "", "x\ny\n", "", ""},
|
|
{`BEGIN { print 1 >"/dev/stderr"; print 2 } # !windows-gawk`, "", "1\n2\n", "", ""},
|
|
|
|
// Ensure data returned by getline (in various forms) is treated as numeric string
|
|
{`BEGIN { getline; print($0==0) }`, "0.0", "1\n", "", ""},
|
|
{`BEGIN { getline x; print(x==0) }`, "0.0", "1\n", "", ""},
|
|
{`BEGIN { "echo 0.0" | getline; print($0==0) }`, "", "1\n", "", ""},
|
|
{`BEGIN { "echo 0.0" | getline x; print(x==0) }`, "", "1\n", "", ""},
|
|
|
|
// Redirected I/O errors (we give explicit errors, awk and gawk don't)
|
|
{`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", ""},
|
|
|
|
// The value of close() on a pipe emulates gawk behavior. Results are identical for both
|
|
// input and output. Windows does not do POSIX signals. Windows gawk uses cmd.exe, not sh.exe.
|
|
{`BEGIN { cmd="read FOO; exit 9"; print "" |cmd; print close(cmd) } # !awk !posix !windows-gawk`, "", "9\n", "", ""},
|
|
{`BEGIN { cmd="read FOO; exec /bin/kill -9 $$"; print "" |cmd; print close(cmd) } # !awk !posix !windows`, "", "265\n", "", ""},
|
|
{`BEGIN { cmd="exit 9"; cmd |getline; print close(cmd) } # !awk !posix`, "", "9\n", "", ""},
|
|
{`BEGIN { cmd="exec /bin/kill -9 $$"; cmd |getline; print close(cmd) } # !awk !posix !windows`, "", "265\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", "", ""},
|
|
|
|
// Semicolon is allowed as item terminator
|
|
{"function f(){} ; 0", "", "", "", ""}, // after function
|
|
{"{} ; 0", "", "", "", ""}, // after action
|
|
{"1 ; 0", "", "", "", ""}, // after normal pattern without action
|
|
// also after the last item
|
|
{"function f(){} ; ", "", "", "", ""},
|
|
{"{} ; ", "", "", "", ""},
|
|
{"1 ; ", "", "", "", ""},
|
|
|
|
// Extra semicolons are allowed in blocks
|
|
{`BEGIN {;}`, "", "", "", ""},
|
|
{`BEGIN { while (0) {;;} }`, "", "", "", ""},
|
|
|
|
// 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", "", ""},
|
|
|
|
// Backslash line continuation
|
|
{"BEGIN { print 1,\\\n 2 }", "", "1 2\n", "", ""},
|
|
{"BEGIN { print 1,\\\r\n 2 }", "", "1 2\n", "", ""},
|
|
|
|
// Ensure syntax errors result in errors
|
|
{`{ $1 = substr($1, 1, 3) print $1 }`, "", "", "parse error at 1:25: expected ; or newline between statements", "syntax error"},
|
|
{`BEGIN { 1 + 1 - | }`, "", "", `parse error at 1:17: expected expression instead of |`, "syntax"},
|
|
{`BEGIN { | }`, "", "", `parse error at 1:9: expected expression instead of |`, "syntax"},
|
|
{`BEGIN { f() }`, "", "", `parse error at 1:9: undefined function "f"`, "defined"},
|
|
{`function f() {} function f() {} BEGIN { }`, "", "", `parse error at 1:26: function "f" already defined`, "define"},
|
|
{`BEGIN { print (1,2),(3,4) }`, "", "", "parse error at 1:15: unexpected comma-separated expression", "syntax"},
|
|
{`BEGIN { print (1,2,(3,4),(5,6)) }`, "", "", "parse error at 1:20: unexpected comma-separated expression", "syntax"},
|
|
{"BEGIN { print 1,\\2 }", "", "1 2\n", `parse error at 1:18: expected \n after \ line continuation`, "backslash not last character on line"},
|
|
{`BEGIN { print . }`, "", "", "parse error at 1:16: expected digits", "syntax"},
|
|
{`BEGIN { print "foo }`, "", "", "parse error at 1:21: didn't find end quote in string", "unterminated string"},
|
|
{"BEGIN { print \"foo\n\"}", "", "", "parse error at 1:19: can't have newline in string", "unterminated string"},
|
|
{`/foo`, "", "", "parse error at 1:5: didn't find end slash in regex", "unterminated regexp"},
|
|
{"/foo\n", "", "", "parse error at 1:5: can't have newline in regex", "unterminated regexp"},
|
|
{`BEGIN { print "\x" } # !gawk`, "", "", "parse error at 1:18: 1 or 2 hex digits expected", ""},
|
|
{`BEGIN { print 1&*2 }`, "", "", "parse error at 1:17: unexpected char after '&'", "syntax"},
|
|
{"BEGIN { ` }", "", "", "parse error at 1:9: unexpected char", "invalid char"},
|
|
{"BEGIN { ++3 }", "", "", "parse error at 1:11: expected lvalue after ++", "syntax"},
|
|
{"BEGIN { ($i)++ }", "", "", "parse error at 1:16: expected lvalue after ++", "syntax"},
|
|
{"BEGIN { rand() = 1 }", "", "", "parse error at 1:9: expected lvalue before =", "syntax"},
|
|
{"BEGIN { 1 && rand()=1 }", "", "", "parse error at 1:9: expected lvalue before =", "syntax"},
|
|
|
|
// Hex floating point and other number conversions
|
|
{`{ print $1+0 } # +posix`, `
|
|
0x0
|
|
0X10
|
|
0x1234567890
|
|
0xabcdef
|
|
0xABCDEF
|
|
-0xa
|
|
+0XA
|
|
0xf.f
|
|
0xf.fp10
|
|
0xf.fp-10
|
|
0x.f
|
|
0xf.
|
|
0x.
|
|
`[1:], `
|
|
0
|
|
16
|
|
78187493520
|
|
11259375
|
|
11259375
|
|
-10
|
|
10
|
|
15.9375
|
|
16320
|
|
0.015564
|
|
0.9375
|
|
15
|
|
0
|
|
`[1:], "", ""},
|
|
{`BEGIN { print int("0x22"), int("-0xa"), int("0xffz"), int("022"), int("-022") } # +posix`, "",
|
|
"34 -10 255 22 -22\n", "", ""},
|
|
{`{ print $1, $2+0 } # !gawk`, `
|
|
1 nan
|
|
2 NAN
|
|
3 nanny
|
|
4 +nan
|
|
5 -nan
|
|
6 na
|
|
7 +na
|
|
8 inf
|
|
9 INF
|
|
10 infamous
|
|
11 infinity
|
|
12 +inf
|
|
13 -inf
|
|
14 in
|
|
15 +in
|
|
`[1:], `
|
|
1 nan
|
|
2 nan
|
|
3 nan
|
|
4 nan
|
|
5 nan
|
|
6 0
|
|
7 0
|
|
8 inf
|
|
9 inf
|
|
10 inf
|
|
11 inf
|
|
12 inf
|
|
13 -inf
|
|
14 0
|
|
15 0
|
|
`[1:], "", ""},
|
|
{`{ printf "%s < %s == %d\n", $1, $2, $1<$2 } # +posix`, `
|
|
10 2
|
|
0x10 0x2
|
|
+nan +nan
|
|
-0x10 +0x2
|
|
-0x10.0p0 +0x2.0p0
|
|
`[1:], `
|
|
10 < 2 == 0
|
|
0x10 < 0x2 == 0
|
|
+nan < +nan == 0
|
|
-0x10 < +0x2 == 1
|
|
-0x10.0p0 < +0x2.0p0 == 1
|
|
`[1:], "", ""},
|
|
{`{ print !$1 } # +posix`, "0x0\n0x0.0p0\n0x1\n0x0.01\n", "1\n1\n0\n0\n", "", ""},
|
|
{`{ print $1<$2 }`, "1_0 2", "1\n", "", ""},
|
|
}
|
|
|
|
// Flags parses the "skip flags" in it.src's first comment and returns them as
|
|
// a map. An enable flag ("# +flag") appears as {"flag": true} in the map, and
|
|
// a disable flag ("# !flag") appears as {"flag": false} in the map.
|
|
func (it interpTest) Flags() map[string]bool {
|
|
flags := make(map[string]bool)
|
|
i := strings.Index(it.src, "#")
|
|
if i < 0 {
|
|
return flags
|
|
}
|
|
for _, field := range strings.Fields(it.src[i+1:]) {
|
|
switch {
|
|
case len(field) <= 1:
|
|
continue
|
|
case field[0] == '!':
|
|
flags[field[1:]] = false
|
|
case field[0] == '+':
|
|
flags[field[1:]] = true
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func TestInterp(t *testing.T) {
|
|
// Ensure very long lines work (> 64KB)
|
|
longLine := strings.Repeat("x", 70000)
|
|
tests := append(interpTests,
|
|
interpTest{`{ print length() }`, longLine, fmt.Sprintf("%d\n", len(longLine)), "", ""},
|
|
)
|
|
|
|
for _, test := range tests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
flags := test.Flags()
|
|
var skipWindows bool
|
|
if f, ok := flags["windows"]; !f && ok && runtime.GOOS == "windows" {
|
|
skipWindows = true
|
|
}
|
|
|
|
// Run it through external awk program first
|
|
if awkExe != "" {
|
|
runAWK := func(t *testing.T, posix bool) {
|
|
if skipWindows {
|
|
t.Skip("skipping windows")
|
|
}
|
|
|
|
var f, ok bool
|
|
if f, ok = flags[awkExe]; !f && ok {
|
|
t.Skipf("skipping under %s", awkExe)
|
|
}
|
|
if f, ok = flags[runtime.GOOS+"-"+awkExe]; !f && ok {
|
|
t.Skipf("skipping on %s under %s", runtime.GOOS, awkExe)
|
|
}
|
|
if f, ok = flags["posix"]; posix && !f && ok {
|
|
t.Skipf("skipping in --posix mode")
|
|
}
|
|
if !posix && f {
|
|
t.Skip("skipping in non-posix mode")
|
|
}
|
|
|
|
var args []string
|
|
if posix {
|
|
args = append(args, "--posix")
|
|
}
|
|
args = append(args, test.src, "-")
|
|
cmd := exec.Command(awkExe, args...)
|
|
if runtime.GOOS == "darwin" {
|
|
cmd.Env = []string{"LC_ALL=en_US.UTF-8"}
|
|
} else if runtime.GOOS != "windows" {
|
|
cmd.Env = []string{"LC_ALL=C.UTF-8"}
|
|
}
|
|
if test.in != "" {
|
|
cmd.Stdin = strings.NewReader(test.in)
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
if test.awkErr != "" {
|
|
if strings.Contains(string(out), test.awkErr) {
|
|
return
|
|
}
|
|
t.Fatalf("expected error %q, got:\n%s", test.awkErr, out)
|
|
} else {
|
|
t.Fatalf("error running %s: %v:\n%s", awkExe, err, out)
|
|
}
|
|
}
|
|
if test.awkErr != "" {
|
|
t.Fatalf(`expected error %q, got ""`, test.awkErr)
|
|
}
|
|
normalized := normalizeNewlines(string(out))
|
|
if normalized != test.out {
|
|
t.Fatalf("expected/got:\n%q\n%q", test.out, normalized)
|
|
}
|
|
}
|
|
t.Run("awk_"+testName, func(t *testing.T) {
|
|
runAWK(t, false)
|
|
})
|
|
if strings.Contains(awkExe, "gawk") {
|
|
t.Run("awkposix_"+testName, func(t *testing.T) {
|
|
runAWK(t, true)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Then test it in GoAWK
|
|
t.Run(testName, func(t *testing.T) {
|
|
if skipWindows {
|
|
t.Skip("skipping windows")
|
|
}
|
|
testGoAWK(t, test.src, test.in, test.out, test.err, nil, nil)
|
|
})
|
|
}
|
|
_ = os.Remove("out")
|
|
}
|
|
|
|
// Version of bytes.Buffer that's safe for concurrent writes. This
|
|
// makes certain tests that write to Output and Error at once (due
|
|
// to os/exec) work correctly.
|
|
type concurrentBuffer struct {
|
|
buffer bytes.Buffer
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
func (b *concurrentBuffer) Write(data []byte) (int, error) {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
return b.buffer.Write(data)
|
|
}
|
|
|
|
func (b *concurrentBuffer) String() string {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
return b.buffer.String()
|
|
}
|
|
|
|
func testGoAWKBase(
|
|
t *testing.T, src, in, out, errStr string,
|
|
funcs map[string]any,
|
|
configure func(config *interp.Config),
|
|
transformFn func(string) string,
|
|
) {
|
|
parserConfig := &parser.ParserConfig{
|
|
Funcs: funcs,
|
|
}
|
|
prog, err := parser.ParseProgram([]byte(src), parserConfig)
|
|
if err != nil {
|
|
if errStr != "" {
|
|
if err.Error() == errStr {
|
|
return
|
|
}
|
|
t.Fatalf("expected error %q, got %q", errStr, err.Error())
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Test that disassembler at least doesn't panic or return an error.
|
|
err = prog.Disassemble(io.Discard)
|
|
if err != nil {
|
|
t.Fatalf("disassembler returned an error: %v", err)
|
|
}
|
|
|
|
outBuf := &concurrentBuffer{}
|
|
config := &interp.Config{
|
|
Stdin: strings.NewReader(in),
|
|
Output: outBuf,
|
|
Error: outBuf,
|
|
Vars: []string{"_var", "42"},
|
|
Funcs: funcs,
|
|
}
|
|
if configure != nil {
|
|
configure(config)
|
|
}
|
|
status, err := interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
if errStr != "" {
|
|
if err.Error() == errStr {
|
|
return
|
|
}
|
|
t.Fatalf("expected error %q, got %q", errStr, err.Error())
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
if errStr != "" {
|
|
t.Fatalf(`expected error %q, got ""`, errStr)
|
|
}
|
|
normalized := transformFn(outBuf.String())
|
|
if normalized != out {
|
|
t.Fatalf("expected/got:\n%q\n%q", out, normalized)
|
|
}
|
|
if status != 0 {
|
|
t.Fatalf("expected status 0, got %d", status)
|
|
}
|
|
}
|
|
|
|
func testGoAWK(
|
|
t *testing.T, src, in, out, errStr string,
|
|
funcs map[string]any, configure func(config *interp.Config),
|
|
) {
|
|
testGoAWKBase(t, src, in, out, errStr, funcs, configure, normalizeNewlines)
|
|
}
|
|
|
|
func testGoAWKRaw(
|
|
t *testing.T, src, in, out, errStr string,
|
|
funcs map[string]any, configure func(config *interp.Config),
|
|
) {
|
|
rawdata := func(s string) string { return s }
|
|
testGoAWKBase(t, src, in, out, errStr, funcs, configure, rawdata)
|
|
}
|
|
|
|
func TestNative(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
in string
|
|
out string
|
|
err string
|
|
funcs map[string]any
|
|
}{
|
|
{`BEGIN { print foo() }`, "", "", `parse error at 1:15: undefined function "foo"`,
|
|
nil},
|
|
{`BEGIN { print foo() }`, "", "\n", "",
|
|
map[string]any{
|
|
"foo": func() {},
|
|
}},
|
|
{`BEGIN { print foo() }`, "", "FOO\n", "",
|
|
map[string]any{
|
|
"foo": func() string { return "FOO" },
|
|
}},
|
|
{`BEGIN { print foo() }`, "", "BYTES\n", "",
|
|
map[string]any{
|
|
"foo": func() []byte { return []byte("BYTES") },
|
|
}},
|
|
{`BEGIN { print repeat("xy", 5) }`, "", "xyxyxyxyxy\n", "",
|
|
map[string]any{
|
|
"repeat": strings.Repeat,
|
|
}},
|
|
{`BEGIN { print repeat("xy", 5) }`, "", "xyxyxyxyxy\n", "",
|
|
map[string]any{
|
|
"repeat": strings.Repeat,
|
|
}},
|
|
{`
|
|
BEGIN {
|
|
print r0()
|
|
print r1(), r1(5)
|
|
print r2(), r2(5)
|
|
}`, "", "\n0 25\n0 25\n", "",
|
|
map[string]any{
|
|
"r0": func() {},
|
|
"r1": func(n int) int { return n * n },
|
|
"r2": func(n int) (int, error) {
|
|
return n * n, nil
|
|
},
|
|
}},
|
|
{`
|
|
BEGIN {
|
|
print r2()
|
|
}`, "", "", "NATIVE ERROR",
|
|
map[string]any{
|
|
"r2": func(n int) (int, error) {
|
|
return n * n, fmt.Errorf("NATIVE ERROR")
|
|
},
|
|
}},
|
|
{`
|
|
BEGIN {
|
|
print
|
|
print bool(), bool(0), bool(1), bool(""), bool("0"), bool("x")
|
|
print i(), i(42), i(-5), i(3.75), i(-3.75)
|
|
print i8(), i8(42), i8(-5.6), i8(127), i8(-128)
|
|
print i16(), i16(42), i16(-5.6), i16(32767), i16(-32768)
|
|
print i32(), i32(42), i32(-5.6), i32(2147483647), i32(-2147483648)
|
|
print i64(), i64(42), i64(-5.6), i64(2147483647000), i64(-2147483647000)
|
|
print u(), u(42), u(0), u(1)
|
|
print u8(), u8(42), u8(-5.6), u8(127), u8(128), u8(255)
|
|
print u16(), u16(42), u16(-1), u16(65535)
|
|
print u32(), u32(42), u32(-1), u32(4294967295)
|
|
print u64(), u64(42), u64(1), u64(4294967296), u64(2147483647000)
|
|
print s() "." s("") "." s("Foo bar") "." s(1234)
|
|
print b() "." b("") "." b("Foo bar") "." b(1234)
|
|
}`, "", `
|
|
0 0 1 0 1 1
|
|
0 42 -5 3 -3
|
|
0 42 -5 127 -128
|
|
0 42 -5 32767 -32768
|
|
0 42 -5 2147483647 -2147483648
|
|
0 42 -5 2147483647000 -2147483647000
|
|
0 42 0 1
|
|
0 42 251 127 128 255
|
|
0 42 65535 65535
|
|
0 42 4294967295 4294967295
|
|
0 42 1 4294967296 2147483647000
|
|
..Foo bar.1234
|
|
..Foo bar.1234
|
|
`, "",
|
|
map[string]any{
|
|
"bool": func(b bool) bool { return b },
|
|
"i": func(n int) int { return n },
|
|
"i8": func(n int8) int8 { return n },
|
|
"i16": func(n int16) int16 { return n },
|
|
"i32": func(n int32) int32 { return n },
|
|
"i64": func(n int64) int64 { return n },
|
|
"u": func(n uint) uint { return n },
|
|
"u8": func(n uint8) uint8 { return n },
|
|
"u16": func(n uint16) uint16 { return n },
|
|
"u32": func(n uint32) uint32 { return n },
|
|
"u64": func(n uint64) uint64 { return n },
|
|
"b": func(b []byte) []byte { return b },
|
|
"s": func(s string) string { return s },
|
|
}},
|
|
{`
|
|
BEGIN {
|
|
print
|
|
print sum(), sum(1), sum(2, 3), sum(4, 5, 6, 7, 8)
|
|
print fmt_ints()
|
|
print fmt_ints("%5d")
|
|
print fmt_ints("%5d", 123)
|
|
print fmt_ints("%d %d", 123, 456)
|
|
print fmt_ints("%d %d %d", 123, 456, 789)
|
|
}`, "", `
|
|
0 1 5 30
|
|
|
|
%!d(MISSING)
|
|
123
|
|
123 456
|
|
123 456 789
|
|
`, "",
|
|
map[string]any{
|
|
"sum": func(args ...int) int {
|
|
sum := 0
|
|
for _, a := range args {
|
|
sum += a
|
|
}
|
|
return sum
|
|
},
|
|
"fmt_ints": func(s string, args ...int) string {
|
|
fmtArgs := make([]any, len(args))
|
|
for i, a := range args {
|
|
fmtArgs[i] = a
|
|
}
|
|
return fmt.Sprintf(s, fmtArgs...)
|
|
},
|
|
}},
|
|
{`BEGIN { 0 }`, "", "", `native function "f" is not a function`,
|
|
map[string]any{
|
|
"f": 0,
|
|
}},
|
|
{`BEGIN { 1 }`, "", "", `native function "g" param 0 is not int or string`,
|
|
map[string]any{
|
|
"g": func(s complex64) {},
|
|
}},
|
|
{`BEGIN { 2 }`, "", "", `native function "g" param 2 is not int or string`,
|
|
map[string]any{
|
|
"g": func(x, y int, s []int, t string) {},
|
|
}},
|
|
{`BEGIN { 3 }`, "", "", `native function "h" param 0 is not int or string`,
|
|
map[string]any{
|
|
"h": func(a ...map[string]int) {},
|
|
}},
|
|
{`BEGIN { 4 }`, "", "", `native function "h" param 1 is not int or string`,
|
|
map[string]any{
|
|
"h": func(x int, a ...complex64) {},
|
|
}},
|
|
{`BEGIN { 5 }`, "", "", `native function "r" return value is not int or string`,
|
|
map[string]any{
|
|
"r": func() map[string]int { return nil },
|
|
}},
|
|
{`BEGIN { 6 }`, "", "", `native function "r" first return value is not int or string`,
|
|
map[string]any{
|
|
"r": func() (map[string]int, error) { return nil, nil },
|
|
}},
|
|
{`BEGIN { 7 }`, "", "", `native function "r" second return value is not an error`,
|
|
map[string]any{
|
|
"r": func() (int, int) { return 0, 0 },
|
|
}},
|
|
{`BEGIN { 8 }`, "", "", `native function "r" returns more than two values`,
|
|
map[string]any{
|
|
"r": func() (int, int, int) { return 0, 0, 0 },
|
|
}},
|
|
{`BEGIN { print f(), f(1, 2) }`, "", "", `parse error at 1:20: "f" called with more arguments than declared`,
|
|
map[string]any{
|
|
"f": func(n int) {},
|
|
}},
|
|
{`BEGIN { print split("x y", a) }`, "", "", `can't use keyword "split" as native function name`,
|
|
map[string]any{
|
|
"split": func() {},
|
|
}},
|
|
{`
|
|
function foo(n) { return n * 2 }
|
|
BEGIN { print foo(42) }
|
|
`, "", "84\n", "", map[string]any{
|
|
"foo": func(n int) int { return n / 2 },
|
|
}},
|
|
{`BEGIN { x=3; print foo(x) }`, "", "9\n", ``,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`
|
|
function bar(n) { return foo(n) }
|
|
BEGIN { x=4; y=5; print foo(x), bar(y) }
|
|
`, "", "16 25\n", ``,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`BEGIN { a["x"]=1; print foo(a) }`, "", "",
|
|
`parse error at 1:29: can't use array "a" as scalar`,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`BEGIN { print foo(a); a["x"]=1 }`, "", "",
|
|
`parse error at 1:23: can't use scalar "a" as array`,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`BEGIN { x["x"]=1; print f(x) } function f(a) { return foo(a) }`, "", "",
|
|
`parse error at 1:27: can't pass array "x" as scalar param`,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`function f(a) { return foo(a) } BEGIN { x["x"]=1; print f(x) }`, "", "",
|
|
`parse error at 1:60: can't pass array "x" as scalar param`,
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`BEGIN { x["x"]=1; print f(x["x"]) } function f(a) { return foo(a) }`, "", "1\n", "",
|
|
map[string]any{
|
|
"foo": func(n int) int { return n * n },
|
|
}},
|
|
{`BEGIN { print add(1, add(2, 3)) }`, "", "6\n", "",
|
|
map[string]any{
|
|
"add": func(a, b float64) float64 { return a + b },
|
|
}},
|
|
{`BEGIN { print add(1, add(2, 3)) }`, "", "6\n", "",
|
|
map[string]any{
|
|
"add": func(a, b float32) float32 { return a + b },
|
|
}},
|
|
{`BEGIN { print foo(x) }`, "", "0\n", "",
|
|
map[string]any{
|
|
"foo": func(i int) int { return i },
|
|
}},
|
|
{`BEGIN { print foo(_var) }`, "", "42\n", "",
|
|
map[string]any{
|
|
"foo": func(i int) int { return i },
|
|
}},
|
|
{`function foo(y) { return y/2 } BEGIN { print foo(_var) }`, "", "21\n", "",
|
|
map[string]any{
|
|
"foo": func(i int) int { return i },
|
|
}},
|
|
}
|
|
for _, test := range tests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
testGoAWK(t, test.src, test.in, test.out, test.err, test.funcs, nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafeMode(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
in string
|
|
out string
|
|
err string
|
|
args []string
|
|
}{
|
|
{`BEGIN { print "hi" >"out" }`, "", "", "can't write to file due to NoFileWrites", nil},
|
|
{`BEGIN { print "hi" >>"out" }`, "", "", "can't write to file due to NoFileWrites", nil},
|
|
{`BEGIN { print "hi" |"sort" }`, "", "", "can't write to pipe due to NoExec", nil},
|
|
{`BEGIN { getline <"in" }`, "", "", "can't read from file due to NoFileReads", nil},
|
|
{`$0 # no files`, "1\n2\n", "1\n2\n", "", nil},
|
|
{`$0 # files`, "1\n2\n", "1\n2\n", "can't read from file due to NoFileReads", []string{"f1"}},
|
|
{`BEGIN { "echo foo" |getline }`, "", "", "can't read from pipe due to NoExec", nil},
|
|
{`BEGIN { system("echo foo") }`, "", "", "can't call system() due to NoExec", nil},
|
|
}
|
|
for _, test := range tests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
testGoAWK(t, test.src, test.in, test.out, test.err, nil, func(config *interp.Config) {
|
|
config.Args = test.args
|
|
config.NoExec = true
|
|
config.NoFileWrites = true
|
|
config.NoFileReads = true
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfigVarsCorrect(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte(`BEGIN { print x }`), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing: %v", err)
|
|
}
|
|
config := &interp.Config{
|
|
Stdin: strings.NewReader(""),
|
|
Output: &bytes.Buffer{},
|
|
Error: io.Discard,
|
|
Vars: []string{"FS"},
|
|
}
|
|
_, err = interp.ExecProgram(prog, config)
|
|
expected := "length of config.Vars must be a multiple of 2, not 1"
|
|
if err == nil || err.Error() != expected {
|
|
t.Fatalf("expected error %q, got: %v", expected, err)
|
|
}
|
|
}
|
|
|
|
func TestCharsMode(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
in string
|
|
out string
|
|
}{
|
|
// printf %c
|
|
{`BEGIN { printf "%c", 128 }`, "", "\u0080"},
|
|
{`BEGIN { printf "%c", 255 }`, "", "ÿ"},
|
|
{`BEGIN { printf "%c", 256 }`, "", "Ā"},
|
|
{`BEGIN { printf "%c", 4660 }`, "", "\u1234"},
|
|
{`BEGIN { printf "%c %c %c", "Ā", "ĀĀĀ", "Āx" }`, "", "Ā Ā Ā"},
|
|
|
|
// index()
|
|
{`BEGIN { print index("föö", "f"), index("föö0", 0), index("föö", "ö"), index("föö", "x") }`, "", "1 4 2 0\n"},
|
|
|
|
// length()
|
|
{`BEGIN { print length("a"), length("絵") }`, "", "1 1\n"},
|
|
{`BEGIN { $0="a"; print length(); $0 = "絵"; print length() }`, "", "1\n1\n"},
|
|
|
|
// match()
|
|
{`BEGIN { print match("絵 fööd y", /[föd]+/), RSTART, RLENGTH }`, "", "3 3 4\n"},
|
|
|
|
// substr()
|
|
{`BEGIN { print substr("food", 1), substr("fööd", 1) }`, "", "food fööd\n"},
|
|
{`BEGIN { print substr("food", 1, 2), substr("fööd", 1, 2) }`, "", "fo fö\n"},
|
|
{`BEGIN { print substr("food", 1, 4), substr("fööd", 1, 4) }`, "", "food fööd\n"},
|
|
{`BEGIN { print substr("food", 1, 8), substr("fööd", 1, 8) }`, "", "food fööd\n"},
|
|
{`BEGIN { print substr("food", 2), substr("fööd", 2) }`, "", "ood ööd\n"},
|
|
{`BEGIN { print substr("food", 2, 2), substr("fööd", 2, 2) }`, "", "oo öö\n"},
|
|
{`BEGIN { print substr("food", 2, 3), substr("fööd", 2, 3) }`, "", "ood ööd\n"},
|
|
{`BEGIN { print substr("food", 2, 8), substr("fööd", 2, 8) }`, "", "ood ööd\n"},
|
|
{`BEGIN { print substr("food", 0, 8), substr("fööd", 0, 8) }`, "", "food fööd\n"},
|
|
{`BEGIN { print substr("food", -1, 8), substr("fööd", -1, 8) }`, "", "food fööd\n"},
|
|
{`BEGIN { print substr("food", 5, 8), substr("fööd", 5, 8) }`, "", " \n"},
|
|
{`BEGIN { print substr("food", 2, -3), substr("fööd", 2, -3) }`, "", " \n"},
|
|
}
|
|
for _, test := range tests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
testGoAWK(t, test.src, test.in, test.out, "", nil, func(config *interp.Config) {
|
|
config.Chars = true
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShellCommand(t *testing.T) {
|
|
testGoAWK(t, `BEGIN { system("echo hello world") }`, "", "hello world\n", "", nil, nil)
|
|
|
|
if runtime.GOOS == "windows" {
|
|
testGoAWK(t, `BEGIN { system("echo hello world") }`, "", "hello world\n", "", nil,
|
|
func(config *interp.Config) {
|
|
config.ShellCommand = []string{"cmd.exe", "/c"}
|
|
})
|
|
} else {
|
|
testGoAWK(t, `BEGIN { system("world") }`, "", "hello world\n", "", nil,
|
|
func(config *interp.Config) {
|
|
config.ShellCommand = []string{"/bin/echo", "hello"}
|
|
})
|
|
testGoAWK(t, `BEGIN { "world" | getline; print }`, "", "hello world\n", "", nil,
|
|
func(config *interp.Config) {
|
|
config.ShellCommand = []string{"/bin/echo", "hello"}
|
|
})
|
|
testGoAWK(t, `BEGIN { print "hello world" | "-" }`, "", "hello world\n", "", nil,
|
|
func(config *interp.Config) {
|
|
config.ShellCommand = []string{"/bin/cat"}
|
|
})
|
|
testGoAWK(t, `BEGIN { print system("echo hi") }`, "", "exec: \"foobar3982\": executable file not found in $PATH\n-1\n", "", nil,
|
|
func(config *interp.Config) {
|
|
config.ShellCommand = []string{"foobar3982"}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevStdfile(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte(`
|
|
BEGIN {
|
|
print "Output." >"/dev/stdout"
|
|
print "Error!" >"/dev/stderr"
|
|
}`), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing: %v", err)
|
|
}
|
|
var outBuf bytes.Buffer
|
|
var errBuf bytes.Buffer
|
|
config := &interp.Config{
|
|
Output: &outBuf,
|
|
Error: &errBuf,
|
|
}
|
|
_, err = interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
gotOutput := normalizeNewlines(outBuf.String())
|
|
expectedOutput := "Output.\n"
|
|
if gotOutput != expectedOutput {
|
|
t.Fatalf("expected stdout %q, got %q", expectedOutput, outBuf.String())
|
|
}
|
|
|
|
gotError := normalizeNewlines(errBuf.String())
|
|
expectedError := "Error!\n"
|
|
if gotError != expectedError {
|
|
t.Fatalf("expected stderr %q, got %q", expectedError, gotError)
|
|
}
|
|
}
|
|
|
|
func TestSystemCommandNotFound(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte(`BEGIN { print system("foobar3982") }`), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing: %v", err)
|
|
}
|
|
outBuf := &concurrentBuffer{}
|
|
config := &interp.Config{
|
|
Output: outBuf,
|
|
Error: outBuf,
|
|
}
|
|
_, err = interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
got := outBuf.buffer.String()
|
|
if !strings.Contains(got, "foobar3982") || !strings.Contains(got, "not found") {
|
|
t.Fatalf(`expected output to contain "foobar3982" and "not found", got %q`, got)
|
|
}
|
|
}
|
|
|
|
type mockFlusher struct {
|
|
bytes.Buffer
|
|
flushes []string
|
|
}
|
|
|
|
func (f *mockFlusher) Flush() error {
|
|
f.flushes = append(f.flushes, normalizeNewlines(f.String()))
|
|
return nil
|
|
}
|
|
|
|
func TestFlushes(t *testing.T) {
|
|
src := `
|
|
BEGIN {
|
|
print fflush()
|
|
print "x"
|
|
print "y"
|
|
print fflush()
|
|
print "z"
|
|
print fflush("")
|
|
}`
|
|
f := &mockFlusher{}
|
|
testGoAWK(t, src, "", "", "", nil, func(config *interp.Config) {
|
|
config.Output = f
|
|
})
|
|
// The last one is from GoAWK itself flushing output after finishing
|
|
expected := []string{"", "0\nx\ny\n", "0\nx\ny\n0\nz\n", "0\nx\ny\n0\nz\n0\n"}
|
|
if !reflect.DeepEqual(f.flushes, expected) {
|
|
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
|
|
}
|
|
|
|
// Ensure output is flushed before getline reads from stdin
|
|
src = `
|
|
BEGIN {
|
|
printf "Prompt: "
|
|
getline x
|
|
print x
|
|
}`
|
|
f = &mockFlusher{}
|
|
testGoAWK(t, src, "42\n", "", "", nil, func(config *interp.Config) {
|
|
config.Output = f
|
|
})
|
|
expected = []string{"Prompt: ", "Prompt: 42\n"}
|
|
if !reflect.DeepEqual(f.flushes, expected) {
|
|
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
|
|
}
|
|
|
|
// Ensure output is flushed before system()
|
|
src = `
|
|
BEGIN {
|
|
print "one"
|
|
system("echo .")
|
|
print "two"
|
|
}`
|
|
f = &mockFlusher{}
|
|
testGoAWK(t, src, "", "", "", nil, func(config *interp.Config) {
|
|
config.Output = f
|
|
})
|
|
expected = []string{"one\n", "one\n.\ntwo\n"}
|
|
if !reflect.DeepEqual(f.flushes, expected) {
|
|
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
|
|
}
|
|
}
|
|
|
|
type errorFlusher struct {
|
|
bytes.Buffer
|
|
}
|
|
|
|
func (f *errorFlusher) Flush() error {
|
|
return errors.New("that's not good, hackers")
|
|
}
|
|
|
|
func TestFlushError(t *testing.T) {
|
|
f := &errorFlusher{}
|
|
testGoAWK(t, `BEGIN { fflush() }`, "", "", "", nil, func(config *interp.Config) {
|
|
config.Output = f
|
|
config.Error = f
|
|
})
|
|
expected := "error flushing \"stdout\": that's not good, hackers\n"
|
|
if f.String() != expected {
|
|
t.Fatalf("expected/got:\n%q\n%q", expected, f.String())
|
|
}
|
|
}
|
|
|
|
func TestEnviron(t *testing.T) {
|
|
os.Setenv("GOAWK_TEN", "10") // to test that ENVIRON[x] is numeric string
|
|
src := `
|
|
BEGIN {
|
|
n = 0
|
|
for (k in ENVIRON)
|
|
n++
|
|
print(n, ENVIRON["USER"], ENVIRON["GOAWK_TEN"] < 2)
|
|
}`
|
|
expected := fmt.Sprintf("%d %s 0\n", len(os.Environ()), os.Getenv("USER"))
|
|
testGoAWK(t, src, "", expected, "", nil, nil)
|
|
|
|
expected = "2 bob 0\n"
|
|
testGoAWK(t, src, "", expected, "", nil, func(config *interp.Config) {
|
|
config.Environ = []string{"USER", "bob", "GOAWK_TEN", "10"}
|
|
})
|
|
|
|
expected = "0 1\n"
|
|
testGoAWK(t, src, "", expected, "", nil, func(config *interp.Config) {
|
|
config.Environ = []string{}
|
|
})
|
|
|
|
testGoAWK(t, src, "", "", "length of config.Environ must be a multiple of 2, not 3", nil, func(config *interp.Config) {
|
|
config.Environ = []string{"b", "a", "d"}
|
|
})
|
|
}
|
|
|
|
func TestExit(t *testing.T) {
|
|
tests := []struct {
|
|
src string
|
|
out string
|
|
status int
|
|
}{
|
|
{`BEGIN { print "x"; exit; print "y" } { print "a" } END { print "z" }`, "x\nz\n", 0},
|
|
{`BEGIN { print "x"; exit 1+2; print "y" } { print "a" } END { print "z" }`, "x\nz\n", 3},
|
|
{`{ print "x"; exit; print "y" } END { print "z" }`, "x\nz\n", 0},
|
|
{`{ print "x"; exit 1+2; print "y" } END { print "z" }`, "x\nz\n", 3},
|
|
{`END { print "x"; exit; print "y" }`, "x\n", 0},
|
|
{`END { print "x"; exit 1+2; print "y" }`, "x\n", 3},
|
|
{`BEGIN { print "x"; exit 42 } END { print "z"; exit }`, "x\nz\n", 42},
|
|
{`BEGIN { print "x"; exit } END { print "z"; exit 43 }`, "x\nz\n", 43},
|
|
{`BEGIN { print "x"; exit 44 } { print "a"; exit }`, "x\n", 44},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.src, func(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte(test.src), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing: %v", err)
|
|
}
|
|
outBuf := &bytes.Buffer{}
|
|
config := &interp.Config{
|
|
Stdin: strings.NewReader("line\n"),
|
|
Output: outBuf,
|
|
}
|
|
status, err := interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
t.Fatalf("error interpreting: %v", err)
|
|
}
|
|
normalized := normalizeNewlines(outBuf.String())
|
|
if normalized != test.out {
|
|
t.Fatalf("expected/got:\n%q\n%q", test.out, normalized)
|
|
}
|
|
if status != test.status {
|
|
t.Fatalf("expected status %d, got %d", test.status, status)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A bare 'END {}' should consume the input (issue #228).
|
|
func TestENDConsumesInput(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte("END {}"), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing: %v", err)
|
|
}
|
|
input := strings.NewReader("1\n2\n3\n")
|
|
outBuf := &bytes.Buffer{}
|
|
config := &interp.Config{
|
|
Stdin: input,
|
|
Output: outBuf,
|
|
}
|
|
_, err = interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
t.Fatalf("error interpreting: %v", err)
|
|
}
|
|
if outBuf.String() != "" {
|
|
t.Fatalf("output expected/got:\n%q\n%q", "", outBuf.String())
|
|
}
|
|
// Ensure there's no input remaining to be read.
|
|
b, err := io.ReadAll(input)
|
|
if err != nil {
|
|
t.Fatalf("error reading input: %v", err)
|
|
}
|
|
if string(b) != "" {
|
|
t.Fatalf("input expected/got:\n%q\n%q", "", string(b))
|
|
}
|
|
}
|
|
|
|
type newlineTest struct {
|
|
src string
|
|
in string
|
|
out string
|
|
err string
|
|
configure func(config *interp.Config)
|
|
}
|
|
|
|
var nl = func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "\r\n"
|
|
}
|
|
return "\n"
|
|
}()
|
|
|
|
var newlineTests = []newlineTest{{
|
|
`BEGIN { printf 'smart\nnewline\n' }`, "", "smart" + nl + "newline" + nl, "",
|
|
func(config *interp.Config) { config.NewlineOutput = interp.SmartNewlineMode },
|
|
}, {
|
|
`BEGIN { printf 'raw\nnewline\n' }`, "", "raw\nnewline\n", "",
|
|
func(config *interp.Config) { config.NewlineOutput = interp.RawNewlineMode },
|
|
}, {
|
|
`BEGIN { printf 'crlf\nnewline\n' }`, "", "crlf\r\nnewline\r\n", "",
|
|
func(config *interp.Config) { config.NewlineOutput = interp.CRLFNewlineMode },
|
|
}, {
|
|
`BEGIN { print 1, " smart " }`, "", "1,\" smart \"" + nl, "",
|
|
func(config *interp.Config) {
|
|
config.OutputMode = interp.CSVMode
|
|
config.NewlineOutput = interp.SmartNewlineMode
|
|
},
|
|
}, {
|
|
`BEGIN { print 2, " raw " }`, "", "2,\" raw \"\n", "",
|
|
func(config *interp.Config) {
|
|
config.OutputMode = interp.CSVMode
|
|
config.NewlineOutput = interp.RawNewlineMode
|
|
},
|
|
}, {
|
|
`BEGIN { print 3, " crlf " }`, "", "3,\" crlf \"\r\n", "",
|
|
func(config *interp.Config) {
|
|
config.OutputMode = interp.CSVMode
|
|
config.NewlineOutput = interp.CRLFNewlineMode
|
|
},
|
|
}}
|
|
|
|
func TestNewline(t *testing.T) {
|
|
for _, test := range newlineTests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
testGoAWKRaw(t, test.src, test.in, test.out, test.err, nil, test.configure)
|
|
})
|
|
}
|
|
}
|
|
|
|
type csvTest struct {
|
|
src string
|
|
in string
|
|
out string
|
|
err string
|
|
configure func(config *interp.Config)
|
|
}
|
|
|
|
var csvTests = []csvTest{
|
|
// INPUTMODE combinations
|
|
{`BEGIN { INPUTMODE="" } { print $1, $3 }`, "name,email\nBob C. Smith,bob@smith.com\nJane X. Brown,jane@brown.com", "name,email \nBob Smith,bob@smith.com\nJane Brown,jane@brown.com\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } { print $1, $3 }`, "name,email,age\nBob\tSmith,bob@smith.com,42\n\nJane,jane@brown.com,37\n# not a comment", "Bob\tSmith 42\nJane 37\n# not a comment \n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv separator=|" } { print $1, $3 }`, "Bob,Smith|bob@smith.com|42\nJane|jane@brown.com|37", "Bob,Smith 42\nJane 37\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv comment=#" } { print $1, $3 }`, "# this is a comment\nBob\tSmith,bob@smith.com,42\nJane,jane@brown.com,37", "Bob\tSmith 42\nJane 37\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { print $1, $3 }`, "name,email,age\nBob,bob@smith.com,42\nJane,jane@brown.com,37", "name age\nBob 42\nJane 37\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } { print @"age", @"name" }`, "name,email,age\nBob,bob@smith.com,42\nJane,jane@brown.com,37", "42 Bob\n37 Jane\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } { x="name"; print @"age", @x }`, "name,age\nBob,42", "42 Bob\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { print @"age", @"name" }`, "name,email,age\nBob,bob@smith.com,42\nJane,jane@brown.com,37", "", `no field names for @; use -H or add "header" to INPUTMODE, and use "getline" first if in BEGIN`, nil},
|
|
{`BEGIN { INPUTMODE="csv header"; print @"age", @"name" }`, "name,email,age\nBob,bob@smith.com,42\nJane,jane@brown.com,37", "", `no field names for @; use -H or add "header" to INPUTMODE, and use "getline" first if in BEGIN`, nil},
|
|
{`BEGIN { INPUTMODE="csv header"; getline; print @"age", @"name" }`, "name,email,age\nBob,bob@smith.com,42\nJane,jane@brown.com,37", "42 Bob\n", "", nil},
|
|
{`BEGIN { INPUTMODE="tsv header" } { print $1, $3 }`, "name\temail\tage\nBob,Smith\tbob@smith.com\t42\nJane\tjane@brown.com\t37", "Bob,Smith 42\nJane 37\n", "", nil},
|
|
|
|
// OUTPUTMODE combinations
|
|
{`BEGIN { OUTPUTMODE="csv" } { print $2, $1 }`, "a\"b c\nd e", "c,\"a\"\"b\"\ne,d\n", "", nil},
|
|
{`BEGIN { OUTPUTMODE="tsv" } { print $2, $1 }`, "a\"b c\nd e", "c\t\"a\"\"b\"\ne\td\n", "", nil},
|
|
{`BEGIN { OUTPUTMODE="csv separator=|" } { print $2, $1 }`, "a\"b c\nd e", "c|\"a\"\"b\"\ne|d\n", "", nil},
|
|
|
|
// Both input and output in CSV (or TSV) mode
|
|
{`BEGIN { INPUTMODE="csv header"; OUTPUTMODE="csv"; print "age", "name" } { print $2, $1 }`, "name,age\nBob,42\n\"J B\",37\n\"A\"\"B\",7", "age,name\n42,Bob\n37,J B\n7,\"A\"\"B\"\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv"; OUTPUTMODE="tsv"; } { $1=$1; print }`, "name,age\nBob,42\n\"J B\",37\n\"A\"\"B\",7", "name\tage\nBob\t42\nJ B\t37\n\"A\"\"B\"\t7\n", "", nil},
|
|
|
|
// Configure via interp.Config struct
|
|
{`{ print $2, $1 }`, "name,age\nBob,42", "age name\n42 Bob\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.CSVMode
|
|
}},
|
|
{`{ print $2, $1 }`, "name\tage\nBob\t42", "age name\n42 Bob\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.TSVMode
|
|
}},
|
|
{`{ print $2, $1 }`, "# comment\nBob;42", "42 Bob\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.CSVMode
|
|
config.CSVInput.Separator = ';'
|
|
config.CSVInput.Comment = '#'
|
|
}},
|
|
{`{ print $1, $2 }`, "", "", "input mode configuration not valid in default input mode", func(config *interp.Config) {
|
|
config.CSVInput.Separator = ';'
|
|
}},
|
|
{`{ print $2, $1 }`, "Bob,42\nJane,37", "42\tBob\n37\tJane\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.CSVMode
|
|
config.OutputMode = interp.TSVMode
|
|
}},
|
|
{`BEGIN { INPUTMODE="tsv header"; OUTPUTMODE="csv" } { print @"age", @"name" }`, "name\tage\nBob\t42", "42,Bob\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.CSVMode // will be overridden by BEGIN
|
|
config.OutputMode = interp.TSVMode
|
|
}},
|
|
{`{ print @"age", @"name" }`, "name\tage\nBob\t42", "42,Bob\n", "", func(config *interp.Config) {
|
|
config.InputMode = interp.CSVMode // will be overridden by Vars
|
|
config.OutputMode = interp.TSVMode
|
|
config.Vars = []string{"INPUTMODE", "tsv header", "OUTPUTMODE", "csv"}
|
|
}},
|
|
{`{ print $2, $1 }`, "Bob 42", "42,Bob\n", "", func(config *interp.Config) {
|
|
config.OutputMode = interp.CSVMode
|
|
}},
|
|
{`{ print $2, $1 }`, "Bob 42", "42\tBob\n", "", func(config *interp.Config) {
|
|
config.OutputMode = interp.TSVMode
|
|
}},
|
|
{`{ print $2, $1 }`, "Bob 42", "42;Bob\n", "", func(config *interp.Config) {
|
|
config.OutputMode = interp.CSVMode
|
|
config.CSVOutput.Separator = ';'
|
|
}},
|
|
{`{ print $1, $2 }`, "", "", "output mode configuration not valid in default output mode", func(config *interp.Config) {
|
|
config.CSVOutput.Separator = ';'
|
|
}},
|
|
|
|
// $0 still works as expected in CSV mode
|
|
{`BEGIN { INPUTMODE="csv header" } { print }`, "name,age\nBob,42\nJane,37", "Bob,42\nJane,37\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } { print $0 }`, "name,age\nBob,42\nJane,37", "Bob,42\nJane,37\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } { print $0; $0=NR; print $0 }`, "name,age\nBob,42\nJane,37", "Bob,42\n1\nJane,37\n2\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header comment=#" } { print $0 } END { for (i=1; i in FIELDS; i++) print i, FIELDS[i] }`,
|
|
"# comment\n\nname,age\n# comment\n\nBob,42\n# comment\nJane,37\n\nFoo,5",
|
|
"Bob,42\nJane,37\nFoo,5\n1 name\n2 age\n", "", nil},
|
|
|
|
// CSV filters
|
|
{`BEGIN { INPUTMODE="csv header" } /foo/ { print $2 }`, "id,type\n1,food\n2,bar\n3,foo\n", "food\nfoo\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header" } $1==2 { print $2 }`, "id,type\n1,food\n2,bar\n3,foo\n", "bar\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { s += $-1 } END { print s }`, "a,1\nb,2\nc,3\n", "6\n", "", nil},
|
|
|
|
// Updating fields
|
|
{`BEGIN { INPUTMODE="csv" } { $1 = $1 $1; print $1, $2 }`, "a,1\nb,2", "aa 1\nbb 2\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { $1 = $1 $1; print }`, "a,1\nb,2", "aa 1\nbb 2\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { $0 = "X,3"; print $1, $2 }`, "a,1\nb,2", "X 3\nX 3\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } { $0 = "X,3"; print }`, "a,1\nb,2", "X,3\nX,3\n", "", nil},
|
|
{`BEGIN { INPUTMODE=OUTPUTMODE="csv" } { $1 = $1 $1; print $1, $2 }`, "a,1\nb,2", "aa,1\nbb,2\n", "", nil},
|
|
{`BEGIN { INPUTMODE=OUTPUTMODE="csv" } { $1 = $1 $1; print }`, "a,1\nb,2", "aa,1\nbb,2\n", "", nil},
|
|
{`BEGIN { INPUTMODE=OUTPUTMODE="csv" } { $0 = "X,3"; print $1, $2 }`, "a,1\nb,2", "X,3\nX,3\n", "", nil},
|
|
{`BEGIN { INPUTMODE=OUTPUTMODE="csv" } { $0 = "X,3"; print }`, "a,1\nb,2", "X,3\nX,3\n", "", nil},
|
|
{`BEGIN { OUTPUTMODE="csv"; $0 = "a b c"; printf "%s|%s %s %s\n", $0, $1, $2, $3; NF=2; printf "%s|%s %s\n", $0, $1, $2 }`, "", "a b c|a b c\na,b|a b\n", "", nil},
|
|
{`BEGIN { OUTPUTMODE="csv"; $0 = "a b c"; printf "%s|%s %s %s\n", $0, $1, $2, $3; NF=4; printf "%s|%s %s %s %s\n", $0, $1, $2, $3, $4 }`, "", "a b c|a b c\na,b,c,|a b c \n", "", nil},
|
|
|
|
// FIELDS array
|
|
{`BEGIN { INPUTMODE="csv header" } NR==1 { for (i=1; i in FIELDS; i++) print i, FIELDS[i] }`, "name,email,age\na,b,c", "1 name\n2 email\n3 age\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv" } NR==1 { for (i=1; i in FIELDS; i++) print FIELDS[i] }`, "name,email,age\na,b,c", "", "", nil},
|
|
|
|
// Parsing and formatting of INPUTMODE and OUTPUTMODE special variables
|
|
{`BEGIN { INPUTMODE="csv separator=,"; print INPUTMODE }`, "", "csv\n", "", nil},
|
|
{`BEGIN { INPUTMODE="csv header=true comment=# separator=|"; print INPUTMODE }`, "", "csv separator=| comment=# header\n", "", nil},
|
|
{`BEGIN { OUTPUTMODE="csv separator=,"; printf "%s", OUTPUTMODE }`, "", "csv", "", nil},
|
|
{`BEGIN { OUTPUTMODE="csv separator=|"; printf "%s", OUTPUTMODE }`, "", "csv separator=|", "", nil},
|
|
|
|
// Ignores UTF-8 byte order mark (BOM) at start of CSV file
|
|
{`BEGIN { INPUTMODE="csv" } { print $1=="foo" }`, "\ufefffoo,bar\n\ufefffoo,bar", "1\n0\n", "", nil},
|
|
|
|
// Two-argument split() parses in CSV mode if input mode is CSV
|
|
{`
|
|
BEGIN {
|
|
INPUTMODE = "csv"
|
|
split("foo,\"bar baz\",,x", a)
|
|
for (i=1; i in a; i++) print i ": " a[i]
|
|
split("a,b c,d", a)
|
|
for (i=1; i in a; i++) print i ": " a[i]
|
|
}`, "", "1: foo\n2: bar baz\n3: \n4: x\n1: a\n2: b c\n3: d\n", "", nil},
|
|
{`
|
|
BEGIN {
|
|
INPUTMODE = "tsv"
|
|
split("foo\tbar baz\t\tx", a)
|
|
for (i=1; i in a; i++) print i ": " a[i]
|
|
split("a\tb c\td", a)
|
|
for (i=1; i in a; i++) print i ": " a[i]
|
|
}`, "", "1: foo\n2: bar baz\n3: \n4: x\n1: a\n2: b c\n3: d\n", "", nil},
|
|
// Three-argument split() does not parse in CSV mode
|
|
{`
|
|
BEGIN {
|
|
INPUTMODE = "csv"
|
|
split("foo,\"bar baz\",,x", a, " ")
|
|
for (i=1; i in a; i++) print i ": " a[i]
|
|
}`, "", "1: foo,\"bar\n2: baz\",,x\n", "", nil},
|
|
|
|
// Error handling when parsing INPUTMODE and OUTPUTMODE
|
|
{`BEGIN { INPUTMODE="xyz" }`, "", "", `invalid input mode "xyz"`, nil},
|
|
{`BEGIN { INPUTMODE="csv separator=foo" }`, "", "", `invalid CSV/TSV separator "foo"`, nil},
|
|
{`BEGIN { INPUTMODE="csv comment=bar" }`, "", "", `invalid CSV/TSV comment character "bar"`, nil},
|
|
{`BEGIN { INPUTMODE="csv header=x" }`, "", "", `invalid header value "x"`, nil},
|
|
{`BEGIN { INPUTMODE="csv foo=bar" }`, "", "", `invalid input mode key "foo"`, nil},
|
|
{`BEGIN { OUTPUTMODE="xyz" }`, "", "", `invalid output mode "xyz"`, nil},
|
|
{`BEGIN { OUTPUTMODE="csv separator=foo" }`, "", "", `invalid CSV/TSV separator "foo"`, nil},
|
|
{`BEGIN { OUTPUTMODE="csv foo=bar" }`, "", "", `invalid output mode key "foo"`, nil},
|
|
|
|
// Other errors
|
|
{`BEGIN { @"x" = "y" }`, "", "", "parse error at 1:14: assigning @ expression not supported", nil},
|
|
{`BEGIN { x="a"; @x = "y" }`, "", "", "parse error at 1:19: assigning @ expression not supported", nil},
|
|
{`BEGIN { @"x" += "y" }`, "", "", "parse error at 1:14: assigning @ expression not supported", nil},
|
|
{`BEGIN { x="a"; @x += "y" }`, "", "", "parse error at 1:19: assigning @ expression not supported", nil},
|
|
}
|
|
|
|
func TestCSV(t *testing.T) {
|
|
for _, test := range csvTests {
|
|
testName := test.src
|
|
if len(testName) > 70 {
|
|
testName = testName[:70]
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
testGoAWK(t, test.src, test.in, test.out, test.err, nil, test.configure)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCSVMultiRead(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
src string
|
|
reads []string
|
|
out string
|
|
}{{
|
|
name: "UnquotedHeader",
|
|
src: `BEGIN { INPUTMODE="csv header"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"name,age\n", "Bob", ",42\n", "", "Jill,", "37", ""},
|
|
out: "Bob,42|Bob|42\nJill,37|Jill|37\n",
|
|
}, {
|
|
name: "QuotedHeader",
|
|
src: `BEGIN { INPUTMODE="csv header"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"name,age\n", "\"Bo", "b\"", ",42\n", "\"Ji\n", "ll\",", "37"},
|
|
out: "\"Bob\",42|Bob|42\n\"Ji\nll\",37|Ji\nll|37\n",
|
|
}, {
|
|
name: "UnquotedNewline",
|
|
src: `BEGIN { INPUTMODE="csv header"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"name,age\n", "Bob", ",42\n", "Jill,", "37", "\n"},
|
|
out: "Bob,42|Bob|42\nJill,37|Jill|37\n",
|
|
}, {
|
|
name: "QuotedNewline",
|
|
src: `BEGIN { INPUTMODE="csv header"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"name,age\n", "\"Bo", "b\"", ",42\n", "\"Ji\n", "ll\",", "37\n"},
|
|
out: "\"Bob\",42|Bob|42\n\"Ji\nll\",37|Ji\nll|37\n",
|
|
}, {
|
|
name: "UnquotedNoHeader",
|
|
src: `BEGIN { INPUTMODE="csv"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"Bob", ",42\n", "", "Jill,", "37", ""},
|
|
out: "Bob,42|Bob|42\nJill,37|Jill|37\n",
|
|
}, {
|
|
name: "QuotedNoHeader",
|
|
src: `BEGIN { INPUTMODE="csv"; OFS="|" } { print $0, $1, $2 }`,
|
|
reads: []string{"\"Bo", "b\"", ",42\n", "\"Ji\n", "ll\",", "37\n"},
|
|
out: "\"Bob\",42|Bob|42\n\"Ji\nll\",37|Ji\nll|37\n",
|
|
}, {
|
|
name: "QuotedCRLF",
|
|
src: `BEGIN { INPUTMODE="csv" } { printf "%s|%s|%s", $0, $1, $2 }`,
|
|
reads: []string{"\"Ji\r\n", "ll\",", "37"},
|
|
out: "\"Ji\nll\",37|Ji\nll|37",
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
prog, err := parser.ParseProgram([]byte(test.src), nil)
|
|
if err != nil {
|
|
t.Fatalf("error parsing program: %v", err)
|
|
}
|
|
outBuf := &concurrentBuffer{}
|
|
config := &interp.Config{
|
|
Stdin: &sliceReader{reads: test.reads},
|
|
Output: outBuf,
|
|
Error: outBuf,
|
|
}
|
|
status, err := interp.ExecProgram(prog, config)
|
|
if err != nil {
|
|
t.Fatalf("error executing program: %v", err)
|
|
}
|
|
out := outBuf.String()
|
|
if runtime.GOOS == "windows" {
|
|
out = normalizeNewlines(out)
|
|
}
|
|
if out != test.out {
|
|
t.Fatalf("expected/got:\n%q\n%q", test.out, out)
|
|
}
|
|
if status != 0 {
|
|
t.Fatalf("expected status 0, got %d", status)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type sliceReader struct {
|
|
reads []string
|
|
}
|
|
|
|
func (r *sliceReader) Read(buf []byte) (int, error) {
|
|
if len(r.reads) == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(buf, r.reads[0])
|
|
if n < len(r.reads[0]) {
|
|
r.reads[0] = r.reads[0][:len(buf)]
|
|
} else {
|
|
r.reads = r.reads[1:]
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func benchmarkProgram(b *testing.B, funcs map[string]any,
|
|
input, expected, srcFormat string, args ...any,
|
|
) {
|
|
b.StopTimer()
|
|
src := fmt.Sprintf(srcFormat, args...)
|
|
parserConfig := &parser.ParserConfig{
|
|
Funcs: funcs,
|
|
}
|
|
prog, err := parser.ParseProgram([]byte(src), parserConfig)
|
|
if err != nil {
|
|
b.Fatalf("error parsing %s: %v", b.Name(), err)
|
|
}
|
|
outBuf := &bytes.Buffer{}
|
|
config := &interp.Config{
|
|
Stdin: strings.NewReader(input),
|
|
Output: outBuf,
|
|
Error: io.Discard,
|
|
Funcs: funcs,
|
|
}
|
|
b.StartTimer()
|
|
_, err = interp.ExecProgram(prog, config)
|
|
b.StopTimer()
|
|
if err != nil {
|
|
b.Fatalf("error interpreting %s: %v", b.Name(), err)
|
|
}
|
|
if expected != "" {
|
|
expected += "\n"
|
|
}
|
|
outStr := strings.ReplaceAll(outBuf.String(), "\r\n", "\n")
|
|
if outStr != expected {
|
|
b.Fatalf("expected/got:\n%q\n%q", expected, outStr)
|
|
}
|
|
}
|
|
|
|
func BenchmarkGlobalVars(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "a 1", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
x = 1; y = "a"; t = x; x = y; y = t
|
|
x = 1; y = "a"; t = x; x = y; y = t
|
|
x = 1; y = "a"; t = x; x = y; y = t
|
|
x = 1; y = "a"; t = x; x = y; y = t
|
|
x = 1; y = "a"; t = x; x = y; y = t
|
|
}
|
|
print x, y
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkLocalVars(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "b 2", `
|
|
function f(i, x, y, t) {
|
|
for (i = 0; i < %d; i++) {
|
|
x = 2; y = "b"; t = x; x = y; y = t
|
|
x = 2; y = "b"; t = x; x = y; y = t
|
|
x = 2; y = "b"; t = x; x = y; y = t
|
|
x = 2; y = "b"; t = x; x = y; y = t
|
|
x = 2; y = "b"; t = x; x = y; y = t
|
|
}
|
|
print x, y
|
|
}
|
|
|
|
BEGIN {
|
|
f()
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkIncrDecr(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "0 10", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
x++; x++; x++; x++; x++; x++; x++; x++; x++; x++
|
|
y = x
|
|
x--; x--; x--; x--; x--; x--; x--; x--; x--; x--
|
|
}
|
|
print x, y
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkSimpleBuiltins(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
sin(0); cos(0); exp(0); log(1); sqrt(2); int("x");
|
|
sin(0); cos(0); exp(0); log(1); sqrt(2); int("x");
|
|
sin(0); cos(0); exp(0); log(1); sqrt(2); int("x");
|
|
sin(0); cos(0); exp(0); log(1); sqrt(2); int("x");
|
|
sin(0); cos(0); exp(0); log(1); sqrt(2); int("x");
|
|
}
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinMatch(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "21", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog"
|
|
for (i = 0; i < %d; i++) {
|
|
match(s, /j[a-z]+p/); match(s, /j[a-z]+p/)
|
|
match(s, /j[a-z]+p/); match(s, /j[a-z]+p/)
|
|
match(s, /j[a-z]+p/); match(s, /j[a-z]+p/)
|
|
match(s, /j[a-z]+p/); match(s, /j[a-z]+p/)
|
|
match(s, /j[a-z]+p/); x = match(s, /j[a-z]+p/)
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinLength(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "134", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
|
|
for (i = 0; i < %d; i++) {
|
|
length(s); length(s); length(s); length(s); length(s);
|
|
length(s); length(s); length(s); length(s); length(s);
|
|
length(s); length(s); length(s); length(s); length(s);
|
|
length(s); length(s); length(s); length(s); length(s);
|
|
length(s); length(s); length(s); length(s); x = length(s);
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinIndex(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "134", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog!?!"
|
|
for (i = 0; i < %d; i++) {
|
|
index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!")
|
|
index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!")
|
|
index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!")
|
|
index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!")
|
|
index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); index(s, "!?!"); x = index(s, "!?!")
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSubstr(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", " brown fox", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog!?!"
|
|
for (i = 0; i < %d; i++) {
|
|
substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10)
|
|
substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10)
|
|
substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10)
|
|
substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10)
|
|
substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); substr(s, 100, 10); x = substr(s, 100, 10)
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSplitSpace(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "27", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog!?!"
|
|
for (i = 0; i < %d; i++) {
|
|
split(s, a, " "); split(s, a, " "); split(s, a, " ")
|
|
split(s, a, " "); split(s, a, " "); split(s, a, " ")
|
|
split(s, a, " "); split(s, a, " "); split(s, a, " ")
|
|
split(s, a, " "); split(s, a, " "); split(s, a, " ")
|
|
split(s, a, " "); split(s, a, " "); split(s, a, " ")
|
|
}
|
|
for (k in a) n++
|
|
print n
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSplitRegex(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "22", `
|
|
BEGIN {
|
|
s = "a fox ab fax abc fix a fox ab fax abc fix a fox ab fax abc fix a fox ab fax abc fix a fox ab fax abc fix a fox ab fax abc fix a fox ab fax abc fix"
|
|
for (i = 0; i < %d; i++) {
|
|
split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x")
|
|
split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x")
|
|
split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x")
|
|
split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x")
|
|
split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x"); split(s, a, "f[a-z]x")
|
|
}
|
|
for (k in a) n++
|
|
print n
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSub(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "1 164", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
|
|
sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s)
|
|
sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s)
|
|
sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s)
|
|
sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s)
|
|
sub(/f[a-z]x/, "foxes", s); sub(/f[a-z]x/, "foxes", s); x = sub(/f[a-z]x/, "foxes", s)
|
|
}
|
|
print x, length(s)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSubAmpersand(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "1 164", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
|
|
sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s)
|
|
sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s)
|
|
sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s)
|
|
sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s)
|
|
sub(/f[a-z]x/, "&es", s); sub(/f[a-z]x/, "&es", s); x = sub(/f[a-z]x/, "&es", s)
|
|
}
|
|
print x, length(s)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinGsub(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "3 224", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
|
|
gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s)
|
|
gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s)
|
|
gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s)
|
|
gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s)
|
|
gsub(/f[a-z]x/, "foxes", s); gsub(/f[a-z]x/, "foxes", s); x = gsub(/f[a-z]x/, "foxes", s)
|
|
}
|
|
print x, length(s)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinGsubAmpersand(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "3 224", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
s = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
|
|
gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s)
|
|
gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s)
|
|
gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s)
|
|
gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s)
|
|
gsub(/f[a-z]x/, "&es", s); gsub(/f[a-z]x/, "&es", s); x = gsub(/f[a-z]x/, "&es", s)
|
|
}
|
|
print x, length(s)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBuiltinSprintf(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "A 123 foo 3.14", `
|
|
BEGIN {
|
|
x = "foo"
|
|
y = 3.14159
|
|
for (i = 0; i < %d; i++) {
|
|
sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y)
|
|
sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y)
|
|
sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y)
|
|
sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y)
|
|
sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y); s = sprintf("%%c %%d %%5s %%.3g", 65, 123, x, y)
|
|
}
|
|
print s
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkRecursiveFunc(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "55", `
|
|
function fib(n) {
|
|
if (n <= 2) {
|
|
return 1
|
|
}
|
|
return fib(n-1) + fib(n-2)
|
|
}
|
|
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
res = fib(10)
|
|
}
|
|
print res
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkFuncCall(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "75", `
|
|
function add(a, b) {
|
|
return a + b
|
|
}
|
|
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
sum = add(0, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
}
|
|
print sum
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkNativeFunc(b *testing.B) {
|
|
funcs := map[string]any{
|
|
"add": func(a, b float64) float64 { return a + b },
|
|
}
|
|
benchmarkProgram(b, funcs, "", "75", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
sum = add(0, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
sum = add(sum, add(1, add(2, add(3, add(4, 5)))))
|
|
}
|
|
print sum
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkForLoop(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
for (j = 0; j < 100; j++);
|
|
}
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkForInLoop(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "", `
|
|
BEGIN {
|
|
for (j = 0; j < 100; j++) {
|
|
a[j] = j
|
|
}
|
|
for (i = 0; i < %d; i++) {
|
|
for (k in a);
|
|
}
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkIfStatement(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "0", `
|
|
BEGIN {
|
|
c = 1
|
|
d = 0
|
|
for (i = 0; i < %d; i++) {
|
|
if (c) { x = 1 } else { x = 0 }
|
|
if (c) { x = 1 } else { x = 0 }
|
|
if (c) { x = 1 } else { x = 0 }
|
|
if (d) { x = 1 } else { x = 0 }
|
|
if (d) { x = 1 } else { x = 0 }
|
|
if (d) { x = 1 } else { x = 0 }
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkCondExpr(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "0", `
|
|
BEGIN {
|
|
c = 1
|
|
d = 0
|
|
for (i = 0; i < %d; i++) {
|
|
x = c ? 1 : 0
|
|
x = c ? 1 : 0
|
|
x = c ? 1 : 0
|
|
x = d ? 1 : 0
|
|
x = d ? 1 : 0
|
|
x = d ? 1 : 0
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkSimplePattern(b *testing.B) {
|
|
b.StopTimer()
|
|
inputLines := []string{}
|
|
expectedLines := []string{}
|
|
for i := 0; i < b.N; i++ {
|
|
if i != 0 && i%2 == 0 {
|
|
line := fmt.Sprintf("%d", i)
|
|
inputLines = append(inputLines, line)
|
|
expectedLines = append(expectedLines, line)
|
|
} else {
|
|
inputLines = append(inputLines, "")
|
|
}
|
|
}
|
|
input := strings.Join(inputLines, "\n")
|
|
expected := strings.Join(expectedLines, "\n")
|
|
benchmarkProgram(b, nil, input, expected, "$0")
|
|
}
|
|
|
|
func BenchmarkGetField(b *testing.B) {
|
|
b.StopTimer()
|
|
inputLines := []string{}
|
|
expectedLines := []string{}
|
|
for i := 1; i < b.N+1; i++ {
|
|
inputLines = append(inputLines, fmt.Sprintf("%d %d %d", i, i*2, i*3))
|
|
expectedLines = append(expectedLines, fmt.Sprintf("%d %d", i, i*3))
|
|
}
|
|
input := strings.Join(inputLines, "\n")
|
|
expected := strings.Join(expectedLines, "\n")
|
|
benchmarkProgram(b, nil, input, expected, "{ print $1, $3 }")
|
|
}
|
|
|
|
func BenchmarkSetField(b *testing.B) {
|
|
benchmarkProgram(b, nil, "1 2 3", "one 2 three", `
|
|
{
|
|
for (i = 0; i < %d; i++) {
|
|
$1 = "one"; $3 = "three"
|
|
$1 = "one"; $3 = "three"
|
|
$1 = "one"; $3 = "three"
|
|
$1 = "one"; $3 = "three"
|
|
$1 = "one"; $3 = "three"
|
|
}
|
|
}
|
|
END {
|
|
print $0
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkRegexMatch(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "1", `
|
|
BEGIN {
|
|
s = "The quick brown fox jumps over the lazy dog"
|
|
for (i = 0; i < %d; i++) {
|
|
x = s ~ /j[a-z]+p/
|
|
x = s ~ /j[a-z]+p/
|
|
x = s ~ /j[a-z]+p/
|
|
x = s ~ /j[a-z]+p/
|
|
x = s ~ /j[a-z]+p/
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkBinaryOperators(b *testing.B) {
|
|
benchmarkProgram(b, nil, "", "5.0293", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
res = (1+2*3/4^5) + (1+2*3/4^5) + (1+2*3/4^5) + (1+2*3/4^5) + (1+2*3/4^5)
|
|
}
|
|
print res
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkConcatTwo(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "20", `
|
|
BEGIN {
|
|
x = "0123456789"
|
|
for (i = 0; i < %d; i++) {
|
|
y = x x
|
|
}
|
|
print length(y)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkConcatSmall(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "100", `
|
|
BEGIN {
|
|
x = "0123456789"
|
|
for (i = 0; i < %d; i++) {
|
|
y = x x x x x x x x x x
|
|
}
|
|
print length(y)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkConcatLarge(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "1000000", `
|
|
BEGIN {
|
|
x = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
|
for (i = 0; i < %d; i++) {
|
|
y = x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x \
|
|
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
|
|
z = y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y \
|
|
y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y
|
|
}
|
|
print length(z)
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkComparisons(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "1", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
x = ((((((1 < 2) <= 3) > 4) >= 5) == 6) != 7)
|
|
x = ((((((1 < 2) <= 3) > 4) >= 5) == 6) != 7)
|
|
x = ((((((1 < 2) <= 3) > 4) >= 5) == 6) != 7)
|
|
}
|
|
print x
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkArrayOperations(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "243", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
a[0] = 1
|
|
a[0] = a[0] + a[0] + a[0]
|
|
a[0] = a[0] + a[0] + a[0]
|
|
a[0] = a[0] + a[0] + a[0]
|
|
a[0] = a[0] + a[0] + a[0]
|
|
a[0] = a[0] + a[0] + a[0]
|
|
}
|
|
print a[0]
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkAssign(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "0 1 2 3 4", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
v=0; w=1; x=2; y=3; z=4
|
|
v=0; w=1; x=2; y=3; z=4
|
|
v=0; w=1; x=2; y=3; z=4
|
|
v=0; w=1; x=2; y=3; z=4
|
|
v=0; w=1; x=2; y=3; z=4
|
|
}
|
|
print v, w, x, y, z
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkAugAssign(b *testing.B) {
|
|
b.StopTimer()
|
|
benchmarkProgram(b, nil, "", "5 -9 729 32 3.0536 2", `
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
a = 0; b = 1; c = 3; d = 1024; e = 2; f = 14
|
|
a += 1; b -= 2; c *= 3; d /= 2; e ^= 1.1; f %%= 6
|
|
a += 1; b -= 2; c *= 3; d /= 2; e ^= 1.1; f %%= 6
|
|
a += 1; b -= 2; c *= 3; d /= 2; e ^= 1.1; f %%= 6
|
|
a += 1; b -= 2; c *= 3; d /= 2; e ^= 1.1; f %%= 6
|
|
a += 1; b -= 2; c *= 3; d /= 2; e ^= 1.1; f %%= 6
|
|
}
|
|
print a, b, c, d, e, f
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkPrint(b *testing.B) {
|
|
b.StopTimer()
|
|
src := fmt.Sprintf(`
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
print i, "foo", i, "bar"
|
|
}
|
|
}
|
|
`, b.N)
|
|
|
|
prog, err := parser.ParseProgram([]byte(src), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
b.StartTimer()
|
|
_, err = interp.ExecProgram(prog, &interp.Config{
|
|
Output: io.Discard,
|
|
Environ: []string{},
|
|
})
|
|
b.StopTimer()
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
}
|
|
|
|
func BenchmarkPrintf(b *testing.B) {
|
|
b.StopTimer()
|
|
src := fmt.Sprintf(`
|
|
BEGIN {
|
|
for (i = 0; i < %d; i++) {
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
printf "%%d %%s %%d %%s\n", i, "foo", i, "bar"
|
|
}
|
|
}
|
|
`, b.N)
|
|
|
|
prog, err := parser.ParseProgram([]byte(src), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
b.StartTimer()
|
|
_, err = interp.ExecProgram(prog, &interp.Config{
|
|
Output: io.Discard,
|
|
Environ: []string{},
|
|
})
|
|
b.StopTimer()
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRepeatExecProgram(b *testing.B) {
|
|
prog, err := parser.ParseProgram([]byte(`BEGIN {}`), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
config := interp.Config{
|
|
Output: io.Discard,
|
|
Environ: []string{},
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := interp.ExecProgram(prog, &config)
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkRepeatNew(b *testing.B) {
|
|
prog, err := parser.ParseProgram([]byte(`BEGIN {}`), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
p, err := interp.New(prog)
|
|
if err != nil {
|
|
b.Fatalf("interp.New error: %v", err)
|
|
}
|
|
config := interp.Config{
|
|
Output: io.Discard,
|
|
Environ: []string{},
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := p.Execute(&config)
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkRepeatIOExecProgram(b *testing.B) {
|
|
prog, err := parser.ParseProgram([]byte(`{ for (i=1; i<=NF; i++) print $i }`), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
inputStr := "foo bar\nbazz\n"
|
|
input := strings.NewReader(inputStr)
|
|
var output bytes.Buffer
|
|
config := interp.Config{
|
|
Stdin: input,
|
|
Output: &output,
|
|
Environ: []string{},
|
|
}
|
|
expected := "foo\nbar\nbazz\n"
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
input.Reset(inputStr)
|
|
output.Reset()
|
|
_, err := interp.ExecProgram(prog, &config)
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
if output.String() != expected {
|
|
b.Fatalf("expected/got:\n%q\n%q", expected, output.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkRepeatIONew(b *testing.B) {
|
|
prog, err := parser.ParseProgram([]byte(`{ for (i=1; i<=NF; i++) print $i }`), nil)
|
|
if err != nil {
|
|
b.Fatalf("parse error: %v", err)
|
|
}
|
|
p, err := interp.New(prog)
|
|
if err != nil {
|
|
b.Fatalf("interp.New error: %v", err)
|
|
}
|
|
inputStr := "foo bar\nbazz\n"
|
|
input := strings.NewReader(inputStr)
|
|
var output bytes.Buffer
|
|
config := interp.Config{
|
|
Stdin: input,
|
|
Output: &output,
|
|
Environ: []string{},
|
|
}
|
|
expected := "foo\nbar\nbazz\n"
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
input.Reset(inputStr)
|
|
output.Reset()
|
|
_, err := p.Execute(&config)
|
|
if err != nil {
|
|
b.Fatalf("execute error: %v", err)
|
|
}
|
|
if output.String() != expected {
|
|
b.Fatalf("expected/got:\n%q\n%q", expected, output.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkCSVInputGoAWK(b *testing.B) {
|
|
b.StopTimer()
|
|
s := 0
|
|
var inputLines []string
|
|
for i := 0; i < b.N; i++ {
|
|
s += i
|
|
inputLines = append(inputLines, fmt.Sprintf(`%d,foo,Bob Smith,"foo,bar,baz",email@example.com`, i))
|
|
}
|
|
input := strings.Join(inputLines, "\n")
|
|
expected := fmt.Sprintf("%d", s)
|
|
src := `BEGIN { INPUTMODE="csv" } { s += $1 } END { print s }`
|
|
benchmarkProgram(b, nil, input, expected, src)
|
|
}
|
|
|
|
func BenchmarkCSVInputReader(b *testing.B) {
|
|
b.StopTimer()
|
|
s := 0
|
|
var inputLines []string
|
|
for i := 0; i < b.N; i++ {
|
|
s += i
|
|
inputLines = append(inputLines, fmt.Sprintf(`%d,foo,Bob Smith,"foo,bar,baz",email@example.com`, i))
|
|
}
|
|
input := strings.Join(inputLines, "\n")
|
|
reader := csv.NewReader(strings.NewReader(input))
|
|
total := 0
|
|
b.StartTimer()
|
|
for {
|
|
record, err := reader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
b.Fatalf("read error: %v", err)
|
|
}
|
|
v, _ := strconv.Atoi(record[0])
|
|
total += v
|
|
}
|
|
if s != total {
|
|
b.Fatalf("expected %d, got %d", s, total)
|
|
}
|
|
}
|
|
|
|
func BenchmarkCSVOutputGoAWK(b *testing.B) {
|
|
b.StopTimer()
|
|
var expectedLines []string
|
|
for i := 0; i < b.N; i++ {
|
|
expectedLines = append(expectedLines, fmt.Sprintf(`%d,foo,Bob Smith,"foo,bar,baz",email@example.com`, i))
|
|
}
|
|
expected := strings.Join(expectedLines, "\n")
|
|
benchmarkProgram(b, nil, "", expected, `
|
|
BEGIN {
|
|
OUTPUTMODE = "csv";
|
|
for (i=0; i<%d; i++)
|
|
print i, "foo", "Bob Smith", "foo,bar,baz", "email@example.com"
|
|
}
|
|
`, b.N)
|
|
}
|
|
|
|
func BenchmarkCSVOutputWriter(b *testing.B) {
|
|
b.StopTimer()
|
|
var expectedLines []string
|
|
for i := 0; i < b.N; i++ {
|
|
expectedLines = append(expectedLines, fmt.Sprintf(`%d,foo,Bob Smith,"foo,bar,baz",email@example.com`, i))
|
|
}
|
|
expected := strings.Join(expectedLines, "\n") + "\n"
|
|
var buf bytes.Buffer
|
|
writer := csv.NewWriter(&buf)
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
err := writer.Write([]string{strconv.Itoa(i), "foo", "Bob Smith", "foo,bar,baz", "email@example.com"})
|
|
if err != nil {
|
|
b.Fatalf("write error: %v", err)
|
|
}
|
|
}
|
|
writer.Flush()
|
|
b.StopTimer()
|
|
output := buf.String()
|
|
if output != expected {
|
|
b.Fatalf("expected/got:\n%q\n%q\n", expected, output)
|
|
}
|
|
}
|
|
|
|
func normalizeNewlines(s string) string {
|
|
return strings.ReplaceAll(s, "\r\n", "\n")
|
|
}
|