Repository: goplus/gop
Pull request #1036 (2021-12-08 01:49:05)

x/format; gop fmt -mvgo: only process .go files


Patch:
diff --git a/.gitignore b/.gitignore
index ab2048b1..227a0dec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@ coverage.txt
 .gop/
 gop_autogen*.go
 go.json
+format.result
 *.cache
 
 *.exe
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index 11b1f150..ed11d826 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -119,13 +119,13 @@ func walk(path string, d fs.DirEntry, err error) error {
 		}
 	} else {
 		ext := filepath.Ext(path)
-		if _, ok := extGops[ext]; ok {
+		smart := *flagSmart
+		mvgo := smart && *flagMoveGo
+		if _, ok := extGops[ext]; ok && (!mvgo || ext == ".go") {
 			procCnt++
 			if *flagNotExec {
 				fmt.Println("gop fmt", path)
 			} else {
-				smart := *flagSmart
-				mvgo := smart && *flagMoveGo
 				err = gopfmt(path, smart && (mvgo || ext != ".go"), mvgo)
 				if err != nil {
 					report(err)
diff --git a/format/format.go b/format/format.go
index 3ee26f05..cae090ae 100644
--- a/format/format.go
+++ b/format/format.go
@@ -28,7 +28,6 @@ package format
 
 import (
 	"bytes"
-	"fmt"
 	"io"
 
 	"github.com/goplus/gop/ast"
@@ -77,8 +76,10 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
 		}
 		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
 		if err != nil {
+			_, err = dst.Write(buf.Bytes())
+			return err
 			// We should never get here. If we do, provide good diagnostic.
-			return fmt.Errorf("format.Node internal error (%s)", err)
+			// return fmt.Errorf("format.Node internal error (%s)", err)
 		}
 		ast.SortImports(fset, file)
 
diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go
index 50d7f83b..ad78fed7 100644
--- a/x/format/stmt_expr_or_type.go
+++ b/x/format/stmt_expr_or_type.go
@@ -17,6 +17,9 @@
 package format
 
 import (
+	"log"
+	"reflect"
+
 	"github.com/goplus/gop/ast"
 )
 
@@ -24,12 +27,25 @@ import (
 
 func formatType(ctx *formatCtx, typ ast.Expr, ref *ast.Expr) {
 	switch t := typ.(type) {
+	case *ast.Ident, nil:
 	case *ast.SelectorExpr:
 		formatSelectorExpr(ctx, t, ref)
+	case *ast.MapType:
+		formatType(ctx, t.Key, &t.Key)
+		formatType(ctx, t.Value, &t.Value)
 	case *ast.StructType:
-		formatStructType(ctx, t)
+		formatFields(ctx, t.Fields)
+	case *ast.ArrayType:
+		formatExpr(ctx, t.Len, &t.Len)
+		formatType(ctx, t.Elt, &t.Elt)
+	case *ast.ChanType:
+		formatType(ctx, t.Value, &t.Value)
+	case *ast.InterfaceType:
+		formatFields(ctx, t.Methods)
 	case *ast.FuncType:
 		formatFuncType(ctx, t)
+	default:
+		log.Panicln("TODO: format -", reflect.TypeOf(typ))
 	}
 }
 
@@ -38,10 +54,6 @@ func formatFuncType(ctx *formatCtx, t *ast.FuncType) {
 	formatFields(ctx, t.Results)
 }
 
-func formatStructType(ctx *formatCtx, t *ast.StructType) {
-	formatFields(ctx, t.Fields)
-}
-
 func formatFields(ctx *formatCtx, flds *ast.FieldList) {
 	if flds != nil {
 		for _, fld := range flds.List {
@@ -64,13 +76,51 @@ func formatExprs(ctx *formatCtx, exprs []ast.Expr) {
 
 func formatExpr(ctx *formatCtx, expr ast.Expr, ref *ast.Expr) {
 	switch v := expr.(type) {
+	case *ast.Ident, *ast.BasicLit, *ast.BadExpr, nil:
+	case *ast.BinaryExpr:
+		formatExpr(ctx, v.X, &v.X)
+		formatExpr(ctx, v.Y, &v.Y)
+	case *ast.UnaryExpr:
+		formatExpr(ctx, v.X, &v.X)
 	case *ast.CallExpr:
 		formatCallExpr(ctx, v)
 	case *ast.SelectorExpr:
 		formatSelectorExpr(ctx, v, ref)
+	case *ast.SliceExpr:
+		formatSliceExpr(ctx, v)
+	case *ast.IndexExpr:
+		formatExpr(ctx, v.X, &v.X)
+		formatExpr(ctx, v.Index, &v.Index)
+	case *ast.CompositeLit:
+		formatType(ctx, v.Type, &v.Type)
+		formatExprs(ctx, v.Elts)
+	case *ast.StarExpr:
+		formatExpr(ctx, v.X, &v.X)
+	case *ast.KeyValueExpr:
+		formatExpr(ctx, v.Key, &v.Key)
+		formatExpr(ctx, v.Value, &v.Value)
+	case *ast.FuncLit:
+		formatFuncType(ctx, v.Type)
+		formatBlockStmt(ctx, v.Body)
+	case *ast.TypeAssertExpr:
+		formatExpr(ctx, v.X, &v.X)
+		formatType(ctx, v.Type, &v.Type)
+	case *ast.ParenExpr:
+		formatExpr(ctx, v.X, &v.X)
+	case *ast.Ellipsis:
+		formatExpr(ctx, v.Elt, &v.Elt)
+	default:
+		formatType(ctx, expr, ref)
 	}
 }
 
+func formatSliceExpr(ctx *formatCtx, v *ast.SliceExpr) {
+	formatExpr(ctx, v.X, &v.X)
+	formatExpr(ctx, v.Low, &v.Low)
+	formatExpr(ctx, v.High, &v.High)
+	formatExpr(ctx, v.Max, &v.Max)
+}
+
 func formatCallExpr(ctx *formatCtx, v *ast.CallExpr) {
 	formatExpr(ctx, v.Fun, &v.Fun)
 	fncallStartingLowerCase(v)
@@ -111,8 +161,44 @@ func formatStmt(ctx *formatCtx, stmt ast.Stmt) {
 		formatExprStmt(ctx, v)
 	case *ast.AssignStmt:
 		formatAssignStmt(ctx, v)
+	case *ast.IncDecStmt:
+		formatExpr(ctx, v.X, &v.X)
+	case *ast.ForStmt:
+		formatForStmt(ctx, v)
+	case *ast.RangeStmt:
+		formatRangeStmt(ctx, v)
+	case *ast.IfStmt:
+		formatIfStmt(ctx, v)
+	case *ast.CaseClause:
+		formatExprs(ctx, v.List)
+		formatStmts(ctx, v.Body)
+	case *ast.SwitchStmt:
+		formatSwitchStmt(ctx, v)
+	case *ast.TypeSwitchStmt:
+		formatTypeSwitchStmt(ctx, v)
+	case *ast.CommClause:
+		formatStmt(ctx, v.Comm)
+		formatStmts(ctx, v.Body)
+	case *ast.SelectStmt:
+		formatBlockStmt(ctx, v.Body)
 	case *ast.DeclStmt:
 		formatDeclStmt(ctx, v)
+	case *ast.ReturnStmt:
+		formatExprs(ctx, v.Results)
+	case *ast.BlockStmt:
+		formatBlockStmt(ctx, v)
+	case *ast.DeferStmt:
+		formatCallExpr(ctx, v.Call)
+	case *ast.GoStmt:
+		formatCallExpr(ctx, v.Call)
+	case *ast.SendStmt:
+		formatExpr(ctx, v.Chan, &v.Chan)
+		formatExpr(ctx, v.Value, &v.Value)
+	case *ast.LabeledStmt:
+		formatStmt(ctx, v.Stmt)
+	case *ast.BranchStmt, *ast.EmptyStmt, nil, *ast.BadStmt:
+	default:
+		log.Panicln("TODO: formatStmt -", reflect.TypeOf(stmt))
 	}
 }
 
@@ -129,6 +215,38 @@ func formatAssignStmt(ctx *formatCtx, v *ast.AssignStmt) {
 	formatExprs(ctx, v.Rhs)
 }
 
+func formatSwitchStmt(ctx *formatCtx, v *ast.SwitchStmt) {
+	formatStmt(ctx, v.Init)
+	formatExpr(ctx, v.Tag, &v.Tag)
+	formatBlockStmt(ctx, v.Body)
+}
+
+func formatTypeSwitchStmt(ctx *formatCtx, v *ast.TypeSwitchStmt) {
+	formatStmt(ctx, v.Init)
+	formatStmt(ctx, v.Assign)
+	formatBlockStmt(ctx, v.Body)
+}
+
+func formatIfStmt(ctx *formatCtx, v *ast.IfStmt) {
+	formatStmt(ctx, v.Init)
+	formatExpr(ctx, v.Cond, &v.Cond)
+	formatBlockStmt(ctx, v.Body)
+	formatStmt(ctx, v.Else)
+}
+
+func formatRangeStmt(ctx *formatCtx, v *ast.RangeStmt) {
+	formatExpr(ctx, v.Key, &v.Key)
+	formatExpr(ctx, v.Value, &v.Value)
+	formatExpr(ctx, v.X, &v.X)
+	formatBlockStmt(ctx, v.Body)
+}
+
+func formatForStmt(ctx *formatCtx, v *ast.ForStmt) {
+	formatStmt(ctx, v.Init)
+	formatExpr(ctx, v.Cond, &v.Cond)
+	formatBlockStmt(ctx, v.Body)
+}
+
 func formatDeclStmt(ctx *formatCtx, v *ast.DeclStmt) {
 	if decl, ok := v.Decl.(*ast.GenDecl); ok {
 		formatGenDecl(ctx, decl)


Test patch:
diff --git a/format/_format_test.go b/format/_format_test.go
deleted file mode 100644
index 36d5c4fb..00000000
--- a/format/_format_test.go
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package format
-
-import (
-	"bytes"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-	"testing"
-
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/token"
-)
-
-const testfile = "format_test.go"
-
-func diff(t *testing.T, dst, src []byte) {
-	line := 1
-	offs := 0 // line offset
-	for i := 0; i < len(dst) && i < len(src); i++ {
-		d := dst[i]
-		s := src[i]
-		if d != s {
-			t.Errorf("dst:%d: %s\n", line, dst[offs:i+5])
-			t.Errorf("src:%d: %s\n", line, src[offs:i+5])
-			return
-		}
-		if s == '\n' {
-			line++
-			offs = i + 1
-		}
-	}
-	if len(dst) != len(src) {
-		t.Errorf("len(dst) = %d, len(src) = %d\nsrc = %q", len(dst), len(src), src)
-	}
-}
-
-func TestNode(t *testing.T) {
-	src, err := ioutil.ReadFile(testfile)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	fset := token.NewFileSet()
-	file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	var buf bytes.Buffer
-
-	if err = Node(&buf, fset, file); err != nil {
-		t.Fatal("Node failed:", err)
-	}
-
-	diff(t, buf.Bytes(), src)
-}
-
-func TestSource(t *testing.T) {
-	src, err := ioutil.ReadFile(testfile)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-
-	diff(t, res, src)
-}
-
-var (
-	gop_main_pkg = `package main
-
-import (
-	"strconv"
-)
-
-func add(x, y string) (int, error) {
-	return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
-}
-
-func addSafe(x, y string) int {
-	return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
-}
-
-func test() {
-	println(add("100", "23")!)
-
-	sum, err := add("10", "abc")
-	println(sum, err)
-
-	println(addSafe("10", "abc"))
-}
-
-func main() {
-	println("Hello, Go+")
-	println(1r << 129)
-	println(1/3r + 2/7r*2)
-
-	arr := [1, 3, 5, 7, 11, 13, 17, 19]
-	println(arr)
-
-	e := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
-
-	x := {x: i for i, x <- [1, 3, 5, 7, 11]}
-	y := {x: i for i, x <- [1, 3, 5, 7, 11], i%2 == 1}
-	z := {v: k for k, v <- {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"}, k > 3}
-
-	m := {"Hi": 1, "Go+": 2}
-	println(m)
-	println({v: k for k, v <- m})
-	println([k for k, _ <- m])
-	println([v for v <- m])
-
-	sum := 0
-	for x <- [1, 3, 5, 7, 11], x > 3 {
-		sum += x
-	}
-	println("sum(5,7,11):", sum)
-}
-`
-	gop_nopkg = `println("Hello, Go+")
-println(1r << 129)
-println(1/3r + 2/7r*2)
-
-arr := [1, 3, 5, 7, 11, 13, 17, 19]
-println(arr)
-println([x*x for x <- arr, x > 3])
-
-m := {"Hi": 1, "Go+": 2}
-println(m)
-println({v: k for k, v <- m})
-println([k for k, _ <- m])
-println([v for v <- m])
-`
-	gop_nopkg_import = `
-import (
-	"fmt"
-	"strings"
-	"strconv"
-)
-
-func add(x, y string) (int, error) {
-	return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
-}
-
-x := strings.NewReplacer("?", "!").Replace("hello, world???")
-fmt.Println("x:", x)
-sum := 0
-for x <- [1, 3, 5, 7, 11], x > 3 {
-	sum += x
-}
-println("sum(5,7,11):", sum)
-`
-	gop_depth = `
-x := [1, 3.4] // []float64
-println("x:", x)
-
-z := {v+t: k+i for k, v <- {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"}, k > 3 for i, t <- ["a"]}
-println(z)
-
-y := 1 + 2*4/5r
-
-a := [1, 3, 5, 7, 11]
-b := [x*x for x <- a, x > 3]
-println(b) // output: [25 49 121]
-
-mapData := {"Hi": 1, "Hello": 2, "Go+": 3}
-reversedMap := {v: k for k, v <- mapData}
-println(reversedMap) // output: map[1:Hi 2:Hello 3:Go+]
-`
-
-	gop_list_map = `
-a := [x*x for x <- [1, 3, 5, 7, 11]]
-b := [x*x for x <- [1, 3, 5, 7, 11], x > 3]
-c := [i+v for i, v <- [1, 3, 5, 7, 11], i%2 == 1]
-d := [k+","+s for k, s <- {"Hello": "xsw", "Hi": "Go+"}]
-
-arr := [1, 2, 3, 4, 5, 6]
-e := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
-
-x := {x: i for i, x <- [1, 3, 5, 7, 11]}
-y := {x: i for i, x <- [1, 3, 5, 7, 11], i%2 == 1}
-z := {v: k for k, v <- {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"}, k > 3}
-`
-	gop_err_handle = `
-import (
-	"strconv"
-)
-
-func add(x, y string) (int, error) {
-	return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
-}
-
-func addSafe(x, y string) int {
-	return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
-}
-
-println("add", add("100", "23")!)
-
-sum, err := add("10", "abc")
-println("add", sum, err)
-
-println("addSafe", addSafe("10", "abc"))
-`
-)
-
-func TestGopSourceMain(t *testing.T) {
-	src := []byte(gop_main_pkg)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-func TestGopSourceNoMain(t *testing.T) {
-	src := []byte(gop_nopkg)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-func TestGopSourceImportsNoMain(t *testing.T) {
-	src := []byte(gop_nopkg_import)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-func TestGopSourceDepth(t *testing.T) {
-	src := []byte(gop_depth)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-func TestGopSourceListMap(t *testing.T) {
-	src := []byte(gop_list_map)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-func TestGopSourceErrorHandle(t *testing.T) {
-	src := []byte(gop_err_handle)
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-	diff(t, res, src)
-}
-
-// Test cases that are expected to fail are marked by the prefix "ERROR".
-// The formatted result must look the same as the input for successful tests.
-var tests = []string{
-	// declaration lists
-	`import "go/format"`,
-	"var x int",
-	"var x int\n\ntype T struct{}",
-
-	// statement lists
-	"x := 0",
-	"f(a, b, c)\nvar x int = f(1, 2, 3)",
-
-	// indentation, leading and trailing space
-	"\tx := 0\n\tgo f()",
-	"\tx := 0\n\tgo f()\n\n\n",
-	"\n\t\t\n\n\tx := 0\n\tgo f()\n\n\n",
-	"\n\t\t\n\n\t\t\tx := 0\n\t\t\tgo f()\n\n\n",
-	"\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n",     // no indentation added inside raw strings
-	"\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\n\t\tfoo\n`\n\n\n", // no indentation removed inside raw strings
-
-	// comments
-	"/* Comment */",
-	"\t/* Comment */ ",
-	"\n/* Comment */ ",
-	"i := 5 /* Comment */",         // issue #5551
-	"\ta()\n//line :1",             // issue #11276
-	"\t//xxx\n\ta()\n//line :2",    // issue #11276
-	"\ta() //line :1\n\tb()\n",     // issue #11276
-	"x := 0\n//line :1\n//line :2", // issue #11276
-
-	// whitespace
-	"",     // issue #11275
-	" ",    // issue #11275
-	"\t",   // issue #11275
-	"\t\t", // issue #11275
-	"\n",   // issue #11275
-	"\n\n", // issue #11275
-	"\t\n", // issue #11275
-
-	// erroneous programs
-	"ERROR1 + 2 +",
-	"ERRORx :=  0",
-}
-
-func String(s string) (string, error) {
-	res, err := Source([]byte(s))
-	if err != nil {
-		return "", err
-	}
-	return string(res), nil
-}
-
-func TestPartial(t *testing.T) {
-	for _, src := range tests {
-		if strings.HasPrefix(src, "ERROR") {
-			// test expected to fail
-			src = src[5:] // remove ERROR prefix
-			res, err := String(src)
-			if err == nil && res == src {
-				t.Errorf("formatting succeeded but was expected to fail:\n%q", src)
-			}
-		} else {
-			// test expected to succeed
-			res, err := String(src)
-			if err != nil {
-				t.Errorf("formatting failed (%s):\n%q", err, src)
-			} else if strings.TrimSpace(res) != src {
-				t.Errorf("formatting incorrect:\nsource: %q\nresult: %q", src, res)
-			}
-		}
-		break
-	}
-}
-
-func testFrom(t *testing.T, fpath string) {
-	src, err := ioutil.ReadFile(fpath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	res, err := Source(src)
-	if err != nil {
-		t.Fatal("Source failed:", err)
-	}
-
-	diff(t, res, src)
-}
-
-func TestFromTestdata(t *testing.T) {
-	dir, err := os.Getwd()
-	if err != nil {
-		t.Fatal("Getwd failed:", err)
-	}
-	dir = filepath.Join(dir, "../parser/_testdata")
-	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if !info.IsDir() && filepath.Ext(path) == ".gop" {
-			t.Log(path)
-			testFrom(t, path)
-		}
-		return nil
-	})
-}
diff --git a/parser/_testdata/collection/collection.gop b/parser/_testdata/collection/collection.gop
new file mode 100644
index 00000000..730644c5
--- /dev/null
+++ b/parser/_testdata/collection/collection.gop
@@ -0,0 +1,113 @@
+// We often need our programs to perform operations on
+// collections of data, like selecting all items that
+// satisfy a given predicate or mapping all items to a new
+// collection with a custom function.
+
+// In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming)
+// data structures and algorithms. Go does not support
+// generics; in Go it's common to provide collection
+// functions if and when they are specifically needed for
+// your program and data types.
+
+// Here are some example collection functions for slices
+// of `strings`. You can use these examples to build your
+// own functions. Note that in some cases it may be
+// clearest to just inline the collection-manipulating
+// code directly, instead of creating and calling a
+// helper function.
+
+package main
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Index returns the first index of the target string `t`, or
+// -1 if no match is found.
+func Index(vs []string, t string) int {
+	for i, v := range vs {
+		if v == t {
+			return i
+		}
+	}
+	return -1
+}
+
+// Include returns `true` if the target string t is in the
+// slice.
+func Include(vs []string, t string) bool {
+	return Index(vs, t) >= 0
+}
+
+// Any returns `true` if one of the strings in the slice
+// satisfies the predicate `f`.
+func Any(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if f(v) {
+			return true
+		}
+	}
+	return false
+}
+
+// All returns `true` if all of the strings in the slice
+// satisfy the predicate `f`.
+func All(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if !f(v) {
+			return false
+		}
+	}
+	return true
+}
+
+// Filter returns a new slice containing all strings in the
+// slice that satisfy the predicate `f`.
+func Filter(vs []string, f func(string) bool) []string {
+	vsf := make([]string, 0)
+	for _, v := range vs {
+		if f(v) {
+			vsf = append(vsf, v)
+		}
+	}
+	return vsf
+}
+
+// Map returns a new slice containing the results of applying
+// the function `f` to each string in the original slice.
+func Map(vs []string, f func(string) string) []string {
+	vsm := make([]string, len(vs))
+	for i, v := range vs {
+		vsm[i] = f(v)
+	}
+	return vsm
+}
+
+func main() {
+
+	// Here we try out our various collection functions.
+	var strs = []string{"peach", "apple", "pear", "plum"}
+
+	fmt.Println(Index(strs, "pear"))
+
+	fmt.Println(Include(strs, "grape"))
+
+	fmt.Println(Any(strs, func(v string) bool {
+		return strings.HasPrefix(v, "p")
+	}))
+
+	fmt.Println(All(strs, func(v string) bool {
+		return strings.HasPrefix(v, "p")
+	}))
+
+	fmt.Println(Filter(strs, func(v string) bool {
+		return strings.Contains(v, "e")
+	}))
+
+	// The above examples all used anonymous functions,
+	// but you can also use named functions of the correct
+	// type.
+	fmt.Println(Map(strs, strings.ToUpper))
+
+}
diff --git a/parser/_testdata/collection/parser.expect b/parser/_testdata/collection/parser.expect
new file mode 100644
index 00000000..cbe329c5
--- /dev/null
+++ b/parser/_testdata/collection/parser.expect
@@ -0,0 +1,865 @@
+package main
+
+file collection.gop
+ast.GenDecl:
+  Tok: import
+  Specs:
+    ast.ImportSpec:
+      Path:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "fmt"
+    ast.ImportSpec:
+      Path:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "strings"
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // Index returns the first index of the target string `t`, or
+        ast.Comment:
+          Text: // -1 if no match is found.
+  Name:
+    ast.Ident:
+      Name: Index
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: t
+              Type:
+                ast.Ident:
+                  Name: string
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.Ident:
+                  Name: int
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: i
+          Value:
+            ast.Ident:
+              Name: v
+          Tok: :=
+          X:
+            ast.Ident:
+              Name: vs
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IfStmt:
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: v
+                      Op: ==
+                      Y:
+                        ast.Ident:
+                          Name: t
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ReturnStmt:
+                          Results:
+                            ast.Ident:
+                              Name: i
+        ast.ReturnStmt:
+          Results:
+            ast.UnaryExpr:
+              Op: -
+              X:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // Include returns `true` if the target string t is in the
+        ast.Comment:
+          Text: // slice.
+  Name:
+    ast.Ident:
+      Name: Include
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: t
+              Type:
+                ast.Ident:
+                  Name: string
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.Ident:
+                  Name: bool
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ReturnStmt:
+          Results:
+            ast.BinaryExpr:
+              X:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Index
+                  Args:
+                    ast.Ident:
+                      Name: vs
+                    ast.Ident:
+                      Name: t
+              Op: >=
+              Y:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 0
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // Any returns `true` if one of the strings in the slice
+        ast.Comment:
+          Text: // satisfies the predicate `f`.
+  Name:
+    ast.Ident:
+      Name: Any
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: f
+              Type:
+                ast.FuncType:
+                  Params:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: string
+                  Results:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: bool
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.Ident:
+                  Name: bool
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: _
+          Value:
+            ast.Ident:
+              Name: v
+          Tok: :=
+          X:
+            ast.Ident:
+              Name: vs
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IfStmt:
+                  Cond:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: f
+                      Args:
+                        ast.Ident:
+                          Name: v
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ReturnStmt:
+                          Results:
+                            ast.Ident:
+                              Name: true
+        ast.ReturnStmt:
+          Results:
+            ast.Ident:
+              Name: false
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // All returns `true` if all of the strings in the slice
+        ast.Comment:
+          Text: // satisfy the predicate `f`.
+  Name:
+    ast.Ident:
+      Name: All
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: f
+              Type:
+                ast.FuncType:
+                  Params:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: string
+                  Results:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: bool
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.Ident:
+                  Name: bool
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: _
+          Value:
+            ast.Ident:
+              Name: v
+          Tok: :=
+          X:
+            ast.Ident:
+              Name: vs
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IfStmt:
+                  Cond:
+                    ast.UnaryExpr:
+                      Op: !
+                      X:
+                        ast.CallExpr:
+                          Fun:
+                            ast.Ident:
+                              Name: f
+                          Args:
+                            ast.Ident:
+                              Name: v
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ReturnStmt:
+                          Results:
+                            ast.Ident:
+                              Name: false
+        ast.ReturnStmt:
+          Results:
+            ast.Ident:
+              Name: true
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // Filter returns a new slice containing all strings in the
+        ast.Comment:
+          Text: // slice that satisfy the predicate `f`.
+  Name:
+    ast.Ident:
+      Name: Filter
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: f
+              Type:
+                ast.FuncType:
+                  Params:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: string
+                  Results:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: bool
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: vsf
+          Tok: :=
+          Rhs:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: make
+              Args:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 0
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: _
+          Value:
+            ast.Ident:
+              Name: v
+          Tok: :=
+          X:
+            ast.Ident:
+              Name: vs
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IfStmt:
+                  Cond:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: f
+                      Args:
+                        ast.Ident:
+                          Name: v
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.AssignStmt:
+                          Lhs:
+                            ast.Ident:
+                              Name: vsf
+                          Tok: =
+                          Rhs:
+                            ast.CallExpr:
+                              Fun:
+                                ast.Ident:
+                                  Name: append
+                              Args:
+                                ast.Ident:
+                                  Name: vsf
+                                ast.Ident:
+                                  Name: v
+        ast.ReturnStmt:
+          Results:
+            ast.Ident:
+              Name: vsf
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // Map returns a new slice containing the results of applying
+        ast.Comment:
+          Text: // the function `f` to each string in the original slice.
+  Name:
+    ast.Ident:
+      Name: Map
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: vs
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: f
+              Type:
+                ast.FuncType:
+                  Params:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: string
+                  Results:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Type:
+                            ast.Ident:
+                              Name: string
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: vsm
+          Tok: :=
+          Rhs:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: make
+              Args:
+                ast.ArrayType:
+                  Elt:
+                    ast.Ident:
+                      Name: string
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: len
+                  Args:
+                    ast.Ident:
+                      Name: vs
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: i
+          Value:
+            ast.Ident:
+              Name: v
+          Tok: :=
+          X:
+            ast.Ident:
+              Name: vs
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.AssignStmt:
+                  Lhs:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: vsm
+                      Index:
+                        ast.Ident:
+                          Name: i
+                  Tok: =
+                  Rhs:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: f
+                      Args:
+                        ast.Ident:
+                          Name: v
+        ast.ReturnStmt:
+          Results:
+            ast.Ident:
+              Name: vsm
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.DeclStmt:
+          Decl:
+            ast.GenDecl:
+              Doc:
+                ast.CommentGroup:
+                  List:
+                    ast.Comment:
+                      Text: // Here we try out our various collection functions.
+              Tok: var
+              Specs:
+                ast.ValueSpec:
+                  Names:
+                    ast.Ident:
+                      Name: strs
+                  Values:
+                    ast.CompositeLit:
+                      Type:
+                        ast.ArrayType:
+                          Elt:
+                            ast.Ident:
+                              Name: string
+                      Elts:
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "peach"
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "apple"
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "pear"
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "plum"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Index
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.BasicLit:
+                      Kind: STRING
+                      Value: "pear"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Include
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.BasicLit:
+                      Kind: STRING
+                      Value: "grape"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Any
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.FuncLit:
+                      Type:
+                        ast.FuncType:
+                          Params:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Names:
+                                    ast.Ident:
+                                      Name: v
+                                  Type:
+                                    ast.Ident:
+                                      Name: string
+                          Results:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Type:
+                                    ast.Ident:
+                                      Name: bool
+                      Body:
+                        ast.BlockStmt:
+                          List:
+                            ast.ReturnStmt:
+                              Results:
+                                ast.CallExpr:
+                                  Fun:
+                                    ast.SelectorExpr:
+                                      X:
+                                        ast.Ident:
+                                          Name: strings
+                                      Sel:
+                                        ast.Ident:
+                                          Name: HasPrefix
+                                  Args:
+                                    ast.Ident:
+                                      Name: v
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "p"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: All
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.FuncLit:
+                      Type:
+                        ast.FuncType:
+                          Params:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Names:
+                                    ast.Ident:
+                                      Name: v
+                                  Type:
+                                    ast.Ident:
+                                      Name: string
+                          Results:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Type:
+                                    ast.Ident:
+                                      Name: bool
+                      Body:
+                        ast.BlockStmt:
+                          List:
+                            ast.ReturnStmt:
+                              Results:
+                                ast.CallExpr:
+                                  Fun:
+                                    ast.SelectorExpr:
+                                      X:
+                                        ast.Ident:
+                                          Name: strings
+                                      Sel:
+                                        ast.Ident:
+                                          Name: HasPrefix
+                                  Args:
+                                    ast.Ident:
+                                      Name: v
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "p"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Filter
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.FuncLit:
+                      Type:
+                        ast.FuncType:
+                          Params:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Names:
+                                    ast.Ident:
+                                      Name: v
+                                  Type:
+                                    ast.Ident:
+                                      Name: string
+                          Results:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Type:
+                                    ast.Ident:
+                                      Name: bool
+                      Body:
+                        ast.BlockStmt:
+                          List:
+                            ast.ReturnStmt:
+                              Results:
+                                ast.CallExpr:
+                                  Fun:
+                                    ast.SelectorExpr:
+                                      X:
+                                        ast.Ident:
+                                          Name: strings
+                                      Sel:
+                                        ast.Ident:
+                                          Name: Contains
+                                  Args:
+                                    ast.Ident:
+                                      Name: v
+                                    ast.BasicLit:
+                                      Kind: STRING
+                                      Value: "e"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: fmt
+                  Sel:
+                    ast.Ident:
+                      Name: Println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.Ident:
+                      Name: Map
+                  Args:
+                    ast.Ident:
+                      Name: strs
+                    ast.SelectorExpr:
+                      X:
+                        ast.Ident:
+                          Name: strings
+                      Sel:
+                        ast.Ident:
+                          Name: ToUpper
diff --git a/printer/gop_test.go b/printer/gop_test.go
index 25517a3c..cfd198dd 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -21,10 +21,12 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/format"
+	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/printer"
 	"github.com/goplus/gop/token"
 )
@@ -102,21 +104,49 @@ func diffBytes(t *testing.T, dst, src []byte) {
 	}
 }
 
-func testFrom(t *testing.T, fpath string) {
+const (
+	excludeFormatSource = 1
+	excludeFormatNode   = 2
+)
+
+func testFrom(t *testing.T, fpath, sel string, mode int) {
+	if sel != "" && !strings.Contains(fpath, sel) {
+		return
+	}
 	src, err := ioutil.ReadFile(fpath)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	res, err := format.Source(src, fpath)
-	if err != nil {
-		t.Fatal("Source failed:", err)
+	if (mode & excludeFormatSource) == 0 {
+		t.Run("format.Source "+fpath, func(t *testing.T) {
+			res, err := format.Source(src, fpath)
+			if err != nil {
+				t.Fatal("Source failed:", err)
+			}
+			diffBytes(t, res, src)
+		})
 	}
 
-	diffBytes(t, res, src)
+	if (mode & excludeFormatNode) == 0 {
+		t.Run("format.Node "+fpath, func(t *testing.T) {
+			fset := token.NewFileSet()
+			f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
+			if err != nil {
+				t.Fatal(err)
+			}
+			var buf bytes.Buffer
+			err = format.Node(&buf, fset, f)
+			if err != nil {
+				t.Fatal(err)
+			}
+			diffBytes(t, buf.Bytes(), src)
+		})
+	}
 }
 
 func TestFromTestdata(t *testing.T) {
+	sel := ""
 	dir, err := os.Getwd()
 	if err != nil {
 		t.Fatal("Getwd failed:", err)
@@ -127,14 +157,14 @@ func TestFromTestdata(t *testing.T) {
 			return err
 		}
 		if !info.IsDir() && filepath.Ext(path) == ".gop" {
-			t.Log(path)
-			testFrom(t, path)
+			testFrom(t, path, sel, 0)
 		}
 		return nil
 	})
 }
 
 func TestFromParse(t *testing.T) {
+	sel := ""
 	dir, err := os.Getwd()
 	if err != nil {
 		t.Fatal("Getwd failed:", err)
@@ -149,8 +179,7 @@ func TestFromParse(t *testing.T) {
 			return nil
 		}
 		if !info.IsDir() && filepath.Ext(name) == ".gop" {
-			t.Log(path)
-			testFrom(t, path)
+			testFrom(t, path, sel, 0)
 		}
 		return nil
 	})
diff --git a/x/format/_testdata/collection/format.expect b/x/format/_testdata/collection/format.expect
new file mode 100644
index 00000000..d70cf9e2
--- /dev/null
+++ b/x/format/_testdata/collection/format.expect
@@ -0,0 +1,106 @@
+// We often need our programs to perform operations on
+// collections of data, like selecting all items that
+// satisfy a given predicate or mapping all items to a new
+// collection with a custom function.
+
+// In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming)
+// data structures and algorithms. Go does not support
+// generics; in Go it's common to provide collection
+// functions if and when they are specifically needed for
+// your program and data types.
+
+// Here are some example collection functions for slices
+// of `strings`. You can use these examples to build your
+// own functions. Note that in some cases it may be
+// clearest to just inline the collection-manipulating
+// code directly, instead of creating and calling a
+// helper function.
+
+import (
+	"strings"
+)
+
+// Index returns the first index of the target string `t`, or
+// -1 if no match is found.
+func Index(vs []string, t string) int {
+	for i, v := range vs {
+		if v == t {
+			return i
+		}
+	}
+	return -1
+}
+
+// Include returns `true` if the target string t is in the
+// slice.
+func Include(vs []string, t string) bool {
+	return index(vs, t) >= 0
+}
+
+// Any returns `true` if one of the strings in the slice
+// satisfies the predicate `f`.
+func Any(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if f(v) {
+			return true
+		}
+	}
+	return false
+}
+
+// All returns `true` if all of the strings in the slice
+// satisfy the predicate `f`.
+func All(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if !f(v) {
+			return false
+		}
+	}
+	return true
+}
+
+// Filter returns a new slice containing all strings in the
+// slice that satisfy the predicate `f`.
+func Filter(vs []string, f func(string) bool) []string {
+	vsf := make([]string, 0)
+	for _, v := range vs {
+		if f(v) {
+			vsf = append(vsf, v)
+		}
+	}
+	return vsf
+}
+
+// Map returns a new slice containing the results of applying
+// the function `f` to each string in the original slice.
+func Map(vs []string, f func(string) string) []string {
+	vsm := make([]string, len(vs))
+	for i, v := range vs {
+		vsm[i] = f(v)
+	}
+	return vsm
+}
+
+// Here we try out our various collection functions.
+var strs = []string{"peach", "apple", "pear", "plum"}
+
+println index(strs, "pear")
+
+println include(strs, "grape")
+
+println any(strs, func(v string) bool {
+	return strings.hasPrefix(v, "p")
+})
+
+println all(strs, func(v string) bool {
+	return strings.hasPrefix(v, "p")
+})
+
+println filter(strs, func(v string) bool {
+	return strings.contains(v, "e")
+})
+
+// The above examples all used anonymous functions,
+// but you can also use named functions of the correct
+// type.
+println map(strs, strings.ToUpper)
diff --git a/x/format/_testdata/collection/index.gop b/x/format/_testdata/collection/index.gop
new file mode 100644
index 00000000..730644c5
--- /dev/null
+++ b/x/format/_testdata/collection/index.gop
@@ -0,0 +1,113 @@
+// We often need our programs to perform operations on
+// collections of data, like selecting all items that
+// satisfy a given predicate or mapping all items to a new
+// collection with a custom function.
+
+// In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming)
+// data structures and algorithms. Go does not support
+// generics; in Go it's common to provide collection
+// functions if and when they are specifically needed for
+// your program and data types.
+
+// Here are some example collection functions for slices
+// of `strings`. You can use these examples to build your
+// own functions. Note that in some cases it may be
+// clearest to just inline the collection-manipulating
+// code directly, instead of creating and calling a
+// helper function.
+
+package main
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Index returns the first index of the target string `t`, or
+// -1 if no match is found.
+func Index(vs []string, t string) int {
+	for i, v := range vs {
+		if v == t {
+			return i
+		}
+	}
+	return -1
+}
+
+// Include returns `true` if the target string t is in the
+// slice.
+func Include(vs []string, t string) bool {
+	return Index(vs, t) >= 0
+}
+
+// Any returns `true` if one of the strings in the slice
+// satisfies the predicate `f`.
+func Any(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if f(v) {
+			return true
+		}
+	}
+	return false
+}
+
+// All returns `true` if all of the strings in the slice
+// satisfy the predicate `f`.
+func All(vs []string, f func(string) bool) bool {
+	for _, v := range vs {
+		if !f(v) {
+			return false
+		}
+	}
+	return true
+}
+
+// Filter returns a new slice containing all strings in the
+// slice that satisfy the predicate `f`.
+func Filter(vs []string, f func(string) bool) []string {
+	vsf := make([]string, 0)
+	for _, v := range vs {
+		if f(v) {
+			vsf = append(vsf, v)
+		}
+	}
+	return vsf
+}
+
+// Map returns a new slice containing the results of applying
+// the function `f` to each string in the original slice.
+func Map(vs []string, f func(string) string) []string {
+	vsm := make([]string, len(vs))
+	for i, v := range vs {
+		vsm[i] = f(v)
+	}
+	return vsm
+}
+
+func main() {
+
+	// Here we try out our various collection functions.
+	var strs = []string{"peach", "apple", "pear", "plum"}
+
+	fmt.Println(Index(strs, "pear"))
+
+	fmt.Println(Include(strs, "grape"))
+
+	fmt.Println(Any(strs, func(v string) bool {
+		return strings.HasPrefix(v, "p")
+	}))
+
+	fmt.Println(All(strs, func(v string) bool {
+		return strings.HasPrefix(v, "p")
+	}))
+
+	fmt.Println(Filter(strs, func(v string) bool {
+		return strings.Contains(v, "e")
+	}))
+
+	// The above examples all used anonymous functions,
+	// but you can also use named functions of the correct
+	// type.
+	fmt.Println(Map(strs, strings.ToUpper))
+
+}
diff --git a/x/format/_testdata/syntax/format.expect b/x/format/_testdata/syntax/format.expect
new file mode 100644
index 00000000..e7a56d54
--- /dev/null
+++ b/x/format/_testdata/syntax/format.expect
@@ -0,0 +1,25 @@
+a++
+arr := [...]int{1: 3}
+m := make(map[string]chan interface{})
+for range arr[1:] {
+foo:
+	*(p) = 1
+	println f(arr...)
+	m["a"] <- true
+}
+for i := 0; i < 10; i++ {
+	if false {
+	} else {
+	}
+	select {
+	case <-m["a"]:
+	}
+	switch {
+	case i == 10:
+	}
+	switch t.(type) {
+	case int:
+		defer f()
+		go f().println("Hi")
+	}
+}
diff --git a/x/format/_testdata/syntax/index.gop b/x/format/_testdata/syntax/index.gop
new file mode 100644
index 00000000..8ad3c11e
--- /dev/null
+++ b/x/format/_testdata/syntax/index.gop
@@ -0,0 +1,27 @@
+import "fmt"
+
+a++
+arr := [...]int{1: 3}
+m := make(map[string]chan interface{})
+for range arr[1:] {
+foo:
+	*(p) = 1
+	fmt.Println(f(arr...))
+	m["a"] <- true
+}
+for i := 0; i < 10; i++ {
+	if false {
+	} else {
+	}
+	select {
+	case <-m["a"]:
+	}
+	switch {
+	case i == 10:
+	}
+	switch t.(type) {
+	case int:
+		defer f()
+		go f().Println("Hi")
+	}
+}
diff --git a/x/format/gopstyledir_test.go b/x/format/gopstyledir_test.go
new file mode 100644
index 00000000..b87e9c03
--- /dev/null
+++ b/x/format/gopstyledir_test.go
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package format
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"strings"
+	"testing"
+)
+
+// -----------------------------------------------------------------------------
+
+func TestFromTestdata(t *testing.T) {
+	sel := ""
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir = path.Join(dir, "./_testdata")
+	fis, err := ioutil.ReadDir(dir)
+	if err != nil {
+		t.Fatal("ReadDir failed:", err)
+	}
+	for _, fi := range fis {
+		name := fi.Name()
+		if strings.HasPrefix(name, "_") {
+			continue
+		}
+		t.Run(name, func(t *testing.T) {
+			testFrom(t, dir+"/"+name, sel)
+		})
+	}
+}
+
+func testFrom(t *testing.T, pkgDir, sel string) {
+	if sel != "" && !strings.Contains(pkgDir, sel) {
+		return
+	}
+	log.Println("Formatting", pkgDir)
+	file := pkgDir + "/index.gop"
+	src, err := os.ReadFile(file)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ret, err := GopstyleSource(src, file)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expect, err := os.ReadFile(pkgDir + "/format.expect")
+	if err != nil {
+		t.Fatal(err)
+	}
+	diffBytes(t, pkgDir+"/format.result", ret, expect)
+}
+
+func diffBytes(t *testing.T, outfile string, dst, src []byte) {
+	line := 1
+	offs := 0 // line offset
+	for i := 0; i < len(dst) && i < len(src); i++ {
+		d := dst[i]
+		s := src[i]
+		if d != s {
+			os.WriteFile(outfile, dst, 0666)
+			t.Errorf("dst:%d: %s\n", line, dst[offs:])
+			t.Errorf("src:%d: %s\n", line, src[offs:])
+			return
+		}
+		if s == '\n' {
+			line++
+			offs = i + 1
+		}
+	}
+	if len(dst) != len(src) {
+		t.Errorf("len(dst) = %d, len(src) = %d\ndst = %q\nsrc = %q", len(dst), len(src), dst, src)
+	}
+}
+
+// -----------------------------------------------------------------------------