// Test parser package package parser_test import ( "bytes" "fmt" "strings" "testing" "github.com/benhoyt/goawk/internal/ast" "github.com/benhoyt/goawk/parser" ) // NOTE: apart from TestParseAndString, the parser doesn't have // extensive tests of its own; the idea is to test the parser in the // interp tests. func TestParseAndString(t *testing.T) { // This program should have one of every AST element to ensure // we can parse and String()ify each. source := strings.TrimSpace(` BEGIN { print "begin one" } BEGIN { print "begin two" } { print "empty pattern" } $0 { print "normal pattern" print 1, 2, 3 printf "%.3f", 3.14159 print "x" >"file" print "x" >>"append" print "y" |"prog" delete a delete a[k] if (c) { get(a, k) } if (1 + 2) { get(a, k) } else { set(a, k, v) } for (i = 0; i < 10; i++) { print i continue } for (k in a) { break } while (0) { print "x" } do { print "y" exit status } while (x) next nextfile "cmd" |getline "cmd" |getline x "cmd" |getline a[1] "cmd" |getline $1 getline getline x (getline x + 1) getline $1 getline a[1] getline <"file" getline x <"file" (getline x <"file" "x") getline $1 <"file" getline a[1] <"file" x = 0 y = z = 0 b += 1 c -= 2 d *= 3 e /= 4 g ^= 5 h %= 6 (x ? "t" : "f") ((b && c) || d) (k in a) ((x, y, z) in a) (s ~ "foo") (b < 1) (c <= 2) (d > 3) (e >= 4) (g == 5) (h != 6) ((x y) z) ((b + c) + d) ((b * c) * d) ((b - c) - d) ((b / c) / d) (b ^ (c ^ d)) x++ x-- ++y --y 1234 1.5 "This is a string" if (/a.b/) { print "match" } $1 $(1 + 2) !x +x -x var a[key] a[x, y, z] f() set(a, k, v) sub(/regex/, repl, s) sub("regex", repl, s) sub(regex, repl) sub(regex, repl, s) gsub(regex, repl) gsub(regex, repl, s) split(s, a) split(s, a, regex) match(s, regex) rand() srand() srand(1) length() length($1) sprintf("") sprintf("%.3f", 3.14159) sprintf("%.3f %d", 3.14159, 42) cos(1) sin(1) exp(1) log(1) sqrt(1) int("42") tolower("FOO") toupper("foo") system("ls") close("file") atan2(x, y) index(haystack, needle) { print "block statement" f() } } (NR == 1), (NR == 2) { print "range pattern" } ($1 == "foo") END { print "end one" } END { print "end two" } function f() { } function get(a, k) { return a[k] } function set(a, k, v) { a[k] = v return } `) prog, err := parser.ParseProgram([]byte(source), nil) if err != nil { t.Fatalf("error parsing program: %v", err) } progStr := prog.String() if progStr != source { t.Fatalf("expected first, got second:\n%s\n----------\n%s", source, progStr) } } func TestResolveLargeCallGraph(t *testing.T) { const numCalls = 10000 var buf bytes.Buffer var i int for i = 0; i < numCalls; i++ { fmt.Fprintf(&buf, "function f%d(a) { return f%d(a) }\n", i, i+1) } fmt.Fprintf(&buf, "function f%d(a) { return a }\n", i) fmt.Fprint(&buf, "BEGIN { printf f0(42) }\n") _, err := parser.ParseProgram(buf.Bytes(), nil) if err != nil { t.Fatalf("unexpected error: %v", err) } buf.Reset() fmt.Fprint(&buf, "BEGIN { printf f0(42) }\n") fmt.Fprintf(&buf, "function f%d(a) { return a }\n", numCalls) for i = numCalls - 1; i >= 0; i-- { fmt.Fprintf(&buf, "function f%d(a) { return f%d(a) }\n", i, i+1) } _, err = parser.ParseProgram(buf.Bytes(), nil) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestPositions(t *testing.T) { source := strings.TrimSpace(` function AddNums(n, sum,i) { sum = 0 if (n%2 == 0) { for (i = 0; i < n; i++) { sum += i * 2 } } else if (n%3 == 0) { print "Divides by 3" } else { sum = -1 } while(1) { do { print 123 } while(1) } return sum }`) expectedAst := strings.TrimSpace(` *ast.Program *ast.Function *ast.ExprStmt [2:3-2:10) *ast.AssignExpr *ast.VarExpr *ast.NumExpr *ast.IfStmt [3:3-12:3) *ast.BinaryExpr *ast.BinaryExpr *ast.VarExpr *ast.NumExpr *ast.NumExpr *ast.ForStmt [4:5-6:6) *ast.ExprStmt [4:10-4:15) *ast.AssignExpr *ast.VarExpr *ast.NumExpr *ast.BinaryExpr *ast.VarExpr *ast.VarExpr *ast.ExprStmt [4:24-4:27) *ast.IncrExpr *ast.VarExpr *ast.ExprStmt [5:7-5:19) *ast.AugAssignExpr *ast.VarExpr *ast.BinaryExpr *ast.VarExpr *ast.NumExpr *ast.IfStmt [7:10-11:4) *ast.BinaryExpr *ast.BinaryExpr *ast.VarExpr *ast.NumExpr *ast.NumExpr *ast.PrintStmt [8:5-8:25) *ast.StrExpr *ast.ExprStmt [10:5-10:13) *ast.AssignExpr *ast.VarExpr *ast.UnaryExpr *ast.NumExpr *ast.WhileStmt [12:3-12:41) *ast.NumExpr *ast.DoWhileStmt [12:14-12:40) *ast.PrintStmt [12:19-12:29) *ast.NumExpr *ast.NumExpr *ast.ReturnStmt [13:3-13:13) *ast.VarExpr `) prog, err := parser.ParseProgram([]byte(source), nil) if err != nil { t.Fatalf("error parsing program: %v", err) } code := &code{} ast.Walk(code, &prog.ResolvedProgram.Program) result := strings.TrimSpace(code.buf.String()) if expectedAst != result { t.Fatalf("Wrong AST and/or positions:\n%s", result) } } type code struct { indent int buf strings.Builder } func (c *code) Visit(node ast.Node) ast.Visitor { indent := strings.Repeat(" ", c.indent) if node != nil { c.buf.WriteString(indent) c.buf.WriteString(fmt.Sprintf("%T", node)) if stmt, ok := node.(ast.Stmt); ok { c.buf.WriteString(fmt.Sprintf(" [%s-%s)", stmt.StartPos(), stmt.EndPos())) } c.buf.WriteString("\n") c.indent++ } else { c.indent-- } return c } func Example_valid() { prog, err := parser.ParseProgram([]byte("$0 { print $1 }"), nil) if err != nil { fmt.Println(err) } else { fmt.Println(prog) } // Output: // $0 { // print $1 // } } func Example_error() { prog, err := parser.ParseProgram([]byte("{ for if }"), nil) if err != nil { fmt.Println(err) } else { fmt.Println(prog) } // Output: // parse error at 1:7: expected ( instead of if }