Repository: goplus/gop
Pull request #2110 (2025-02-16 13:51:35)

v1.3.0


Patch:
diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000..0752ec08
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,31 @@
+coverage:
+  ignore:
+    - "builtin"
+    - "ast"
+    - "test"
+    - "scanner"
+    - "format"
+    - "demo"
+    - "demo/unit-test"
+    - "cmd"
+    - "cl/cltest"
+    - "cl/outline"
+    - "cl/internal/spx"
+    - "cl/internal/spx2"
+    - "cl/internal/spx3"
+    - "cl/internal/test"
+    - "cl/internal/overload"
+    - "cl/internal/llgo-hello"
+    - "parser/fsx"
+    - "parser/parsertest"
+    - "x/jsonrpc2"
+    - "x/jsonrpc2/jsonrpc2test"
+    - "x/watcher"
+    - "x/langserver"
+    - "x/fsnotify"
+    - "x/fakenet"
+    - "x/typesutil"
+    - "x/gocmd"
+    - "x/gopenv"
+    - "tool"
+  
\ No newline at end of file
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ab059676..eeb1a1c0 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -27,8 +27,8 @@ jobs:
         - macos-latest
         go:
         - 1.19.x
-        - 1.20.x
         - 1.21.x
+        - 1.22.x
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v4
@@ -48,7 +48,6 @@ jobs:
       run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./...
 
     - name: Codecov
-      uses: codecov/codecov-action@v4
+      uses: codecov/codecov-action@v5
       with:
-        token: ${{ secrets.CODECOV_TOKEN }}
         slug: goplus/gop
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 6f10496b..a90318a7 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -19,7 +19,7 @@ jobs:
         python .github/workflows/check_goreleaser_config.py
 
     - name: Install Snapcraft
-      uses: samuelmeuli/action-snapcraft@v2
+      uses: samuelmeuli/action-snapcraft@v3
 
     - name: Checkout tag
       run: |
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index d96b74a3..fda1cbbb 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -51,6 +51,7 @@ dockers:
     build_flag_templates:
       - --platform=linux/386
       - --build-arg=USE_GORELEASER_ARTIFACTS=1
+      - --build-arg=GORELEASER_ARTIFACTS_TARBALL=./.dist/{{.ProjectName}}{{.Version}}.linux-386.tar.gz
     extra_files:
       - ./
   - goarch: amd64
@@ -60,6 +61,7 @@ dockers:
     build_flag_templates:
       - --platform=linux/amd64
       - --build-arg=USE_GORELEASER_ARTIFACTS=1
+      - --build-arg=GORELEASER_ARTIFACTS_TARBALL=./.dist/{{.ProjectName}}{{.Version}}.linux-amd64.tar.gz
     extra_files:
       - ./
   - goarch: arm64
@@ -69,6 +71,7 @@ dockers:
     build_flag_templates:
       - --platform=linux/arm64
       - --build-arg=USE_GORELEASER_ARTIFACTS=1
+      - --build-arg=GORELEASER_ARTIFACTS_TARBALL=./.dist/{{.ProjectName}}{{.Version}}.linux-arm64.tar.gz
     extra_files:
       - ./
 
@@ -182,16 +185,14 @@ nfpms:
         dst: "/usr/lib/{{ .ProjectName }}/Makefile"
       - src: "README.md"
         dst: "/usr/lib/{{ .ProjectName }}/README.md"
-      - src: "_gendeps.go"
-        dst: "/usr/lib/{{ .ProjectName }}/_gendeps.go"
       - src: "all.bash"
         dst: "/usr/lib/{{ .ProjectName }}/all.bash"
       - src: "all.bat"
         dst: "/usr/lib/{{ .ProjectName }}/all.bat"
       - src: "ast"
         dst: "/usr/lib/{{ .ProjectName }}/ast"
-      - src: "build_install_run.go"
-        dst: "/usr/lib/{{ .ProjectName }}/build_install_run.go"
+      - src: "tool"
+        dst: "/usr/lib/{{ .ProjectName }}/tool"
       - src: "builtin"
         dst: "/usr/lib/{{ .ProjectName }}/builtin"
       - src: "cl"
@@ -204,22 +205,14 @@ nfpms:
         dst: "/usr/lib/{{ .ProjectName }}/env"
       - src: "format"
         dst: "/usr/lib/{{ .ProjectName }}/format"
-      - src: "gengo.go"
-        dst: "/usr/lib/{{ .ProjectName }}/gengo.go"
       - src: "go.mod"
         dst: "/usr/lib/{{ .ProjectName }}/go.mod"
       - src: "go.sum"
         dst: "/usr/lib/{{ .ProjectName }}/go.sum"
-      - src: "imp.go"
-        dst: "/usr/lib/{{ .ProjectName }}/imp.go"
-      - src: "load.go"
-        dst: "/usr/lib/{{ .ProjectName }}/load.go"
       - src: "make.bash"
         dst: "/usr/lib/{{ .ProjectName }}/make.bash"
       - src: "make.bat"
         dst: "/usr/lib/{{ .ProjectName }}/make.bat"
-      - src: "outline.go"
-        dst: "/usr/lib/{{ .ProjectName }}/outline.go"
       - src: "parser"
         dst: "/usr/lib/{{ .ProjectName }}/parser"
       - src: "printer"
@@ -230,8 +223,6 @@ nfpms:
         dst: "/usr/lib/{{ .ProjectName }}/test"
       - src: "demo"
         dst: "/usr/lib/{{ .ProjectName }}/demo"
-      - src: "tidy.go"
-        dst: "/usr/lib/{{ .ProjectName }}/tidy.go"
       - src: "token"
         dst: "/usr/lib/{{ .ProjectName }}/token"
       - src: "x"
@@ -268,16 +259,14 @@ snapcrafts:
         destination: "Makefile"
       - source: "README.md"
         destination: "README.md"
-      - source: "_gendeps.go"
-        destination: "_gendeps.go"
       - source: "all.bash"
         destination: "all.bash"
       - source: "all.bat"
         destination: "all.bat"
       - source: "ast"
         destination: "ast"
-      - source: "build_install_run.go"
-        destination: "build_install_run.go"
+      - source: "tool"
+        destination: "tool"
       - source: "builtin"
         destination: "builtin"
       - source: "cl"
@@ -290,22 +279,14 @@ snapcrafts:
         destination: "env"
       - source: "format"
         destination: "format"
-      - source: "gengo.go"
-        destination: "gengo.go"
       - source: "go.mod"
         destination: "go.mod"
       - source: "go.sum"
         destination: "go.sum"
-      - source: "imp.go"
-        destination: "imp.go"
-      - source: "load.go"
-        destination: "load.go"
       - source: "make.bash"
         destination: "make.bash"
       - source: "make.bat"
         destination: "make.bat"
-      - source: "outline.go"
-        destination: "outline.go"
       - source: "parser"
         destination: "parser"
       - source: "printer"
@@ -316,8 +297,6 @@ snapcrafts:
         destination: "test"
       - source: "demo"
         destination: "demo"
-      - source: "tidy.go"
-        destination: "tidy.go"
       - source: "token"
         destination: "token"
       - source: "x"
diff --git a/Dockerfile b/Dockerfile
index bbca70c3..8b214ce7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,21 +2,18 @@ ARG BASE_IMAGE=golang:1.22-bookworm
 
 FROM $BASE_IMAGE AS build
 ARG USE_GORELEASER_ARTIFACTS=0
+ARG GORELEASER_ARTIFACTS_TARBALL
 WORKDIR /usr/local/src/gop
 COPY . .
 ENV GOPROOT=/usr/local/gop
 RUN set -eux; \
-	mkdir -p $GOPROOT/bin; \
-	git ls-tree --full-tree --name-only -r HEAD | grep -vE "^\." | xargs -I {} cp --parents {} $GOPROOT/; \
+	mkdir $GOPROOT; \
 	if [ $USE_GORELEASER_ARTIFACTS -eq 1 ]; then \
-		GOARCH=$(go env GOARCH); \
-		BIN_DIR_SUFFIX=linux_$GOARCH; \
-		[ $GOARCH = "amd64" ] && BIN_DIR_SUFFIX=${BIN_DIR_SUFFIX}_v1; \
-		[ $GOARCH = "arm" ] && BIN_DIR_SUFFIX=${BIN_DIR_SUFFIX}_$(go env GOARM | cut -d , -f 1); \
-		cp .dist/gop_$BIN_DIR_SUFFIX/bin/gop $GOPROOT/bin/; \
+		tar -xzf "${GORELEASER_ARTIFACTS_TARBALL}" -C $GOPROOT; \
 	else \
+		git ls-tree --full-tree --name-only -r HEAD | grep -vE "^\." | xargs -I {} cp --parents {} $GOPROOT/; \
 		./all.bash; \
-		cp bin/gop $GOPROOT/bin/; \
+		mv bin $GOPROOT/; \
 	fi
 
 FROM $BASE_IMAGE
diff --git a/README.md b/README.md
index b63b778e..09712e05 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,9 @@ For more details, see [Quick Start](doc/docs.md).
 ## Key Features of Go+
 
 * Approaching natural language expression and intuitive (see [How Go+ simplifies Go's expressions](#how-go-simplifies-gos-expressions)).
-* Smallest but Turing-complete syntax set in best practices (see [Go+ Specification for STEM Education](doc/spec-mini.md)).
-* Fully compatible with [Go](https://github.com/golang/go) and can mix Go/Go+ code in the same package (see [Go+ Full Specification](doc/spec.md) and [Go/Go+ Hybrid Programming](doc/docs.md#gogo-hybrid-programming)).
-* Integrating with the C ecosystem including Python and providing limitless possibilities based on [LLGo](https://github.com/goplus/llgo) (see [Support for C/C++ and Python](#support-for-cc-and-python)).
+* Smallest but Turing-complete syntax set in best practices (see [The Go+ Mini Specification](doc/spec-mini.md)).
+* Fully compatible with [Go](https://github.com/golang/go) and can mix Go/Go+ code in the same package (see [The Go+ Full Specification](doc/spec.md) and [Go/Go+ Hybrid Programming](doc/docs.md#gogo-hybrid-programming)).
+* Integrating with the C ecosystem including Python and providing limitless possibilities based on [LLGo](https://github.com/goplus/llgo) (see [Importing C/C++ and Python libraries](#importing-cc-and-python-libraries)).
 * Does not support DSL (Domain-Specific Languages), but supports SDF (Specific Domain Friendliness) (see [Go+ Classfiles](#go-classfiles)).
 
 
@@ -81,10 +81,10 @@ Code style is just the first step. We have made many efforts to make the code mo
 | OnStart(func() {<br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>}) | onStart => {<br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>} | Lambda expressions |
 | type Rect struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;Width&nbsp; float64<br>&nbsp;&nbsp;&nbsp;&nbsp;Height float64<br>}<br><br>func (this *Rect) Area() float64 { <br>&nbsp;&nbsp;&nbsp;&nbsp;return this.Width * this.Height<br>} | var (<br>&nbsp;&nbsp;&nbsp;&nbsp;Width&nbsp; float64<br>&nbsp;&nbsp;&nbsp;&nbsp;Height float64<br>)<br><br>func Area() float64 { <br>&nbsp;&nbsp;&nbsp;&nbsp;return Width * Height<br>} | [Go+ Classfiles](doc/classfile.md): We can express OOP with global variables and functions. |
 
-For more details, see [Go+ Specification for STEM Education](doc/spec-mini.md).
+For more details, see [The Go+ Mini Specification](doc/spec-mini.md).
 
 
-## Support for C/C++ and Python
+## Importing C/C++ and Python libraries
 
 Go+ can choose different Go compilers as its underlying support. Currently known supported Go compilers include:
 
@@ -115,10 +115,10 @@ module YourModulePath
 
 go 1.21 // llgo 1.0
 
-require github.com/goplus/llgo v0.9.7
+require github.com/goplus/llgo v0.9.9
 ```
 
-Based on LLGo, Go+ can support importing libraries written in C/C++ and Python.
+Based on LLGo, Go+ can import libraries written in C/C++ and Python.
 
 Here is an example (see [chello](demo/_llgo/chello/hello.gop)) of printing `Hello world` using C's `printf`:
 
diff --git a/VERSION b/VERSION
deleted file mode 100644
index bbfdb621..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.3.0-pre.2
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index da6227a5..87e86b6a 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -85,6 +85,26 @@ func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) {
 
 // -----------------------------------------------------------------------------
 
+// A NumberUnitLit node represents a number with unit.
+type NumberUnitLit struct {
+	ValuePos token.Pos   // literal position
+	Kind     token.Token // token.INT or token.FLOAT
+	Value    string      // literal string of the number; e.g. 42, 0x7f, 3.14, 1e-9
+	Unit     string      // unit string of the number; e.g. "px", "em", "rem"
+}
+
+func (*NumberUnitLit) exprNode() {}
+
+func (x *NumberUnitLit) Pos() token.Pos {
+	return x.ValuePos
+}
+
+func (x *NumberUnitLit) End() token.Pos {
+	return token.Pos(int(x.ValuePos) + len(x.Value) + len(x.Unit))
+}
+
+// -----------------------------------------------------------------------------
+
 // A EnvExpr node represents a ${name} expression.
 type EnvExpr struct {
 	TokPos token.Pos // position of "$"
diff --git a/builtin/stringslice/stringslice.go b/builtin/stringslice/stringslice.go
new file mode 100644
index 00000000..e2eb9823
--- /dev/null
+++ b/builtin/stringslice/stringslice.go
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2025 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 stringslice
+
+import (
+	"strings"
+
+	"github.com/qiniu/x/stringutil"
+)
+
+// Capitalize capitalizes the first letter of each string in the slice.
+func Capitalize(a []string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = stringutil.Capitalize(v)
+	}
+	return r
+}
+
+// ToTitle title-cases all strings in the slice.
+func ToTitle(a []string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.ToTitle(v)
+	}
+	return r
+}
+
+// ToUpper upper-cases all strings in the slice.
+func ToUpper(a []string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.ToUpper(v)
+	}
+	return r
+}
+
+// ToLower lower-cases all strings in the slice.
+func ToLower(a []string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.ToLower(v)
+	}
+	return r
+}
+
+// Repeat repeats each string in the slice count times.
+func Repeat(a []string, count int) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.Repeat(v, count)
+	}
+	return r
+}
+
+// Replace replaces all occurrences of old in each string in the slice with new.
+func Replace(a []string, old, new string, n int) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.Replace(v, old, new, n)
+	}
+	return r
+}
+
+// ReplaceAll replaces all occurrences of old in each string in the slice with new.
+func ReplaceAll(a []string, old, new string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.ReplaceAll(v, old, new)
+	}
+	return r
+}
+
+// Trim removes leading and trailing white space from each string in the slice.
+func Trim(a []string, cutset string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.Trim(v, cutset)
+	}
+	return r
+}
+
+// TrimSpace removes leading and trailing white space from each string in the slice.
+func TrimSpace(a []string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.TrimSpace(v)
+	}
+	return r
+}
+
+// TrimLeft removes leading white space from each string in the slice.
+func TrimLeft(a []string, cutset string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.TrimLeft(v, cutset)
+	}
+	return r
+}
+
+// TrimRight removes trailing white space from each string in the slice.
+func TrimRight(a []string, cutset string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.TrimRight(v, cutset)
+	}
+	return r
+}
+
+// TrimPrefix removes leading prefix from each string in the slice.
+func TrimPrefix(a []string, prefix string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.TrimPrefix(v, prefix)
+	}
+	return r
+}
+
+// TrimSuffix removes trailing suffix from each string in the slice.
+func TrimSuffix(a []string, suffix string) []string {
+	r := make([]string, len(a))
+	for i, v := range a {
+		r[i] = strings.TrimSuffix(v, suffix)
+	}
+	return r
+}
diff --git a/cl/builtin.go b/cl/builtin.go
index e1d59757..c61a6c5c 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -81,6 +81,8 @@ func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 	buil := pkg.TryImport("github.com/goplus/gop/builtin")
 	ng := pkg.TryImport("github.com/goplus/gop/builtin/ng")
 	iox := pkg.TryImport("github.com/goplus/gop/builtin/iox")
+	strx := pkg.TryImport("github.com/qiniu/x/stringutil")
+	stringslice := pkg.TryImport("github.com/goplus/gop/builtin/stringslice")
 	pkg.TryImport("strconv")
 	pkg.TryImport("strings")
 	if ng.Types != nil {
@@ -88,6 +90,30 @@ func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 	}
 	initBuiltin(pkg, builtin, os, fmt, ng, iox, buil)
 	gogen.InitBuiltin(pkg, builtin, conf)
+	if strx.Types != nil {
+		ti := pkg.BuiltinTI(types.Typ[types.String])
+		ti.AddMethods(
+			&gogen.BuiltinMethod{Name: "Capitalize", Fn: strx.Ref("Capitalize")},
+		)
+	}
+	if stringslice.Types != nil {
+		ti := pkg.BuiltinTI(types.NewSlice(types.Typ[types.String]))
+		ti.AddMethods(
+			&gogen.BuiltinMethod{Name: "Capitalize", Fn: stringslice.Ref("Capitalize")},
+			&gogen.BuiltinMethod{Name: "ToTitle", Fn: stringslice.Ref("ToTitle")},
+			&gogen.BuiltinMethod{Name: "ToUpper", Fn: stringslice.Ref("ToUpper")},
+			&gogen.BuiltinMethod{Name: "ToLower", Fn: stringslice.Ref("ToLower")},
+			&gogen.BuiltinMethod{Name: "Repeat", Fn: stringslice.Ref("Repeat")},
+			&gogen.BuiltinMethod{Name: "Replace", Fn: stringslice.Ref("Replace")},
+			&gogen.BuiltinMethod{Name: "ReplaceAll", Fn: stringslice.Ref("ReplaceAll")},
+			&gogen.BuiltinMethod{Name: "Trim", Fn: stringslice.Ref("Trim")},
+			&gogen.BuiltinMethod{Name: "TrimSpace", Fn: stringslice.Ref("TrimSpace")},
+			&gogen.BuiltinMethod{Name: "TrimLeft", Fn: stringslice.Ref("TrimLeft")},
+			&gogen.BuiltinMethod{Name: "TrimRight", Fn: stringslice.Ref("TrimRight")},
+			&gogen.BuiltinMethod{Name: "TrimPrefix", Fn: stringslice.Ref("TrimPrefix")},
+			&gogen.BuiltinMethod{Name: "TrimSuffix", Fn: stringslice.Ref("TrimSuffix")},
+		)
+	}
 	return builtin
 }
 
diff --git a/cl/classfile.go b/cl/classfile.go
index 0356b363..073f059c 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -41,11 +41,16 @@ type gmxClass struct {
 	proj    *gmxProject
 }
 
+type spxObj struct {
+	obj  gogen.Ref
+	proj string
+}
+
 type gmxProject struct {
-	gameClass  string               // <gmtype>.gmx
-	game       gogen.Ref            // Game (project base class)
-	sprite     map[string]gogen.Ref // .spx => Sprite
-	sptypes    []string             // <sptype>.spx
+	gameClass  string            // <gmtype>.gmx
+	game       gogen.Ref         // Game (project base class)
+	sprite     map[string]spxObj // .spx => Sprite
+	sptypes    []string          // <sptype>.spx
 	scheds     []string
 	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
 	pkgImps    []gogen.PkgRef
@@ -161,10 +166,10 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		if gt.Class != "" {
 			p.game, p.gameIsPtr = spxRef(spx, gt.Class)
 		}
-		p.sprite = make(map[string]types.Object)
+		p.sprite = make(map[string]spxObj)
 		for _, v := range gt.Works {
 			obj, _ := spxRef(spx, v.Class)
-			p.sprite[v.Ext] = obj
+			p.sprite[v.Ext] = spxObj{obj, v.Project}
 		}
 		if x := getStringConst(spx, "Gop_sched"); x != "" {
 			p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
diff --git a/cl/compile.go b/cl/compile.go
index 37798899..afe723cc 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -716,6 +716,7 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	var testType string
 	var baseTypeName string
 	var baseType types.Type
+	var spxProj string
 	var spxClass bool
 	var goxTestFile bool
 	var parent = ctx.pkgCtx
@@ -745,9 +746,10 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 					baseType = types.NewPointer(baseType)
 				}
 			} else {
-				o := proj.sprite[c.ext]
+				sp := proj.sprite[c.ext]
+				o := sp.obj
 				ctx.baseClass = o
-				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
+				baseTypeName, baseType, spxProj, spxClass = o.Name(), o.Type(), sp.proj, true
 			}
 		}
 	}
@@ -790,13 +792,18 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 					tags = append(tags, "")
 					chk.chkRedecl(ctx, baseTypeName, pos)
 				}
-				if spxClass && proj.gameClass != "" {
-					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: proj.gameClass}})
-					name := getTypeName(typ)
-					if !chk.chkRedecl(ctx, name, pos) {
-						fld := types.NewField(pos, pkg, name, typ, true)
-						flds = append(flds, fld)
-						tags = append(tags, "")
+				if spxClass {
+					if gameClass := proj.gameClass; gameClass != "" {
+						if spxProj == "" { // if spxProj is empty, use gameClass
+							spxProj = gameClass
+						}
+						typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: spxProj}})
+						name := getTypeName(typ)
+						if !chk.chkRedecl(ctx, name, pos) {
+							fld := types.NewField(pos, pkg, name, typ, true)
+							flds = append(flds, fld)
+							tags = append(tags, "")
+						}
 					}
 				}
 				rec := ctx.recorder()
diff --git a/cl/expr.go b/cl/expr.go
index 7b76ede1..dc2241e2 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -753,6 +753,12 @@ func fnCall(ctx *blockCtx, v *ast.CallExpr, flags gogen.InstrFlags, extra int) e
 }
 
 func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallExpr, ellipsis bool, flags gogen.InstrFlags) (err error) {
+	defer func() {
+		r := recover()
+		if r != nil {
+			err = ctx.recoverErr(r, v)
+		}
+	}()
 	var needInferFunc bool
 	for i, arg := range v.Args {
 		switch expr := arg.(type) {
@@ -807,12 +813,15 @@ func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallE
 			if typetype {
 				return
 			}
+		case *ast.NumberUnitLit:
+			compileNumberUnitLit(ctx, expr, fn.arg(i, ellipsis))
 		default:
 			compileExpr(ctx, arg)
 		}
 	}
 	if needInferFunc {
-		typ, err := gogen.InferFunc(ctx.pkg, pfn, fn.sig, nil, ctx.cb.InternalStack().GetArgs(len(v.Args)), flags)
+		args := ctx.cb.InternalStack().GetArgs(len(v.Args))
+		typ, err := gogen.InferFunc(ctx.pkg, pfn, fn.sig, nil, args, flags)
 		if err != nil {
 			return err
 		}
@@ -971,6 +980,12 @@ func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
 	}
 }
 
+func compileNumberUnitLit(ctx *blockCtx, v *ast.NumberUnitLit, expected types.Type) {
+	ctx.cb.ValWithUnit(
+		&goast.BasicLit{ValuePos: v.ValuePos, Kind: gotoken.Token(v.Kind), Value: v.Value},
+		expected, v.Unit)
+}
+
 func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
 	cb := ctx.cb
 	switch kind := v.Kind; kind {
@@ -1038,10 +1053,12 @@ func compileStringLitEx(ctx *blockCtx, cb *gogen.CodeBuilder, lit *ast.BasicLit)
 			t := cb.Get(-1).Type
 			if t.Underlying() != types.Typ[types.String] {
 				if _, err := cb.Member("string", gogen.MemberFlagAutoProperty); err != nil {
-					if e, ok := err.(*gogen.CodeError); ok {
-						err = ctx.newCodeErrorf(v.Pos(), "%s.string%s", ctx.LoadExpr(v), e.Msg)
+					if _, e2 := cb.Member("error", gogen.MemberFlagAutoProperty); e2 != nil {
+						if e, ok := err.(*gogen.CodeError); ok {
+							err = ctx.newCodeErrorf(v.Pos(), "%s.string%s", ctx.LoadExpr(v), e.Msg)
+						}
+						ctx.handleErr(err)
 					}
-					ctx.handleErr(err)
 				}
 			}
 			pos = v.End()
diff --git a/cl/internal/unit/unit.go b/cl/internal/unit/unit.go
new file mode 100644
index 00000000..6325b8d3
--- /dev/null
+++ b/cl/internal/unit/unit.go
@@ -0,0 +1,22 @@
+/*
+ Copyright 2024 The GoPlus Authors (goplus.org)
+ 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 unit
+
+// -----------------------------------------------------------------------------
+
+type Distance int
+
+const Gopu_Distance = "mm=1,cm=10,dm=100,m=1000"
+
+// -----------------------------------------------------------------------------
diff --git a/cl/stmt.go b/cl/stmt.go
index 57c4cefa..b2d4ff12 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -243,9 +243,44 @@ func compileIncDecStmt(ctx *blockCtx, expr *ast.IncDecStmt) {
 	ctx.cb.IncDec(gotoken.Token(expr.Tok))
 }
 
+// issue #2107:
+//
+//	a <- v
+//	foo.a <- v
+func isAppendable(x ast.Expr) bool {
+	switch x := x.(type) {
+	case *ast.Ident:
+		return true
+	case *ast.SelectorExpr:
+		_, ok := x.X.(*ast.Ident)
+		return ok
+	}
+	return false
+}
+
 func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) {
-	compileExpr(ctx, expr.Chan)
-	compileExpr(ctx, expr.Value)
+	cb := ctx.cb
+	ch, v := expr.Chan, expr.Value
+	stk := cb.InternalStack()
+	if isAppendable(ch) { // a <- v (issue #2107)
+		compileExpr(ctx, ch)
+		a := stk.Get(-1)
+		t := a.Type.Underlying()
+		if _, ok := t.(*types.Slice); ok { // a = append(a, x)
+			stk.Pop()
+			compileExprLHS(ctx, ch)
+			cb.Val(ctx.pkg.Builtin().Ref("append"))
+			stk.Push(a)
+			compileExpr(ctx, v)
+			cb.CallWith(2, 0, expr).AssignWith(1, 1, expr)
+			return
+		}
+		goto normal
+	}
+	compileExpr(ctx, ch)
+
+normal:
+	compileExpr(ctx, v)
 	ctx.cb.Send()
 }
 
diff --git a/cmd/chore/gopfmt/gopfmt.go b/cmd/chore/gopfmt/gopfmt.go
index 7cef6c6d..cb1ad300 100644
--- a/cmd/chore/gopfmt/gopfmt.go
+++ b/cmd/chore/gopfmt/gopfmt.go
@@ -30,8 +30,8 @@ import (
 
 	goformat "go/format"
 
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/format"
+	"github.com/goplus/gop/tool"
 )
 
 var (
@@ -132,7 +132,7 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 		dir, _ := filepath.Split(path)
 		fn, ok := w.dirMap[dir]
 		if !ok {
-			if mod, err := gop.LoadMod(path); err == nil {
+			if mod, err := tool.LoadMod(path); err == nil {
 				fn = func(ext string) (ok bool, class bool) {
 					switch ext {
 					case ".go", ".gop":
diff --git a/cmd/internal/base/pass.go b/cmd/internal/base/pass.go
index 729c1afc..e2dd70a4 100644
--- a/cmd/internal/base/pass.go
+++ b/cmd/internal/base/pass.go
@@ -3,6 +3,7 @@ package base
 import (
 	"flag"
 	"fmt"
+	"strings"
 )
 
 type stringValue struct {
@@ -42,6 +43,15 @@ type PassArgs struct {
 	Flag *flag.FlagSet
 }
 
+func (p *PassArgs) Tags() string {
+	for _, v := range p.Args {
+		if strings.HasPrefix(v, "-tags=") {
+			return v[6:]
+		}
+	}
+	return ""
+}
+
 func (p *PassArgs) Var(names ...string) {
 	for _, name := range names {
 		p.Flag.Var(&stringValue{p: p, name: name}, name, "")
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index db10d6e5..b73eea99 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -25,9 +25,9 @@ import (
 	"reflect"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopprojs"
 )
@@ -39,9 +39,9 @@ var Cmd = &base.Command{
 }
 
 var (
+	flag       = &Cmd.Flag
 	flagDebug  = flag.Bool("debug", false, "print debug information")
 	flagOutput = flag.String("o", "", "gop build output file")
-	flag       = &Cmd.Flag
 )
 
 func init() {
@@ -74,9 +74,9 @@ func runCmd(cmd *base.Command, args []string) {
 		log.Panicln("too many arguments:", args)
 	}
 
-	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
+	conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags())
 	if err != nil {
-		log.Panicln("gop.NewDefaultConf:", err)
+		log.Panicln("tool.NewDefaultConf:", err)
 	}
 	defer conf.UpdateCache()
 
@@ -92,23 +92,23 @@ func runCmd(cmd *base.Command, args []string) {
 	build(proj, conf, confCmd)
 }
 
-func build(proj gopprojs.Proj, conf *gop.Config, build *gocmd.BuildConfig) {
-	const flags = gop.GenFlagPrompt
+func build(proj gopprojs.Proj, conf *tool.Config, build *gocmd.BuildConfig) {
+	const flags = tool.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.BuildDir(obj, conf, build, flags)
+		err = tool.BuildDir(obj, conf, build, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.BuildPkgPath("", v.Path, conf, build, flags)
+		err = tool.BuildPkgPath("", v.Path, conf, build, flags)
 	case *gopprojs.FilesProj:
-		err = gop.BuildFiles(v.Files, conf, build)
+		err = tool.BuildFiles(v.Files, conf, build)
 	default:
 		log.Panicln("`gop build` doesn't support", reflect.TypeOf(v))
 	}
-	if gop.NotFound(err) {
+	if tool.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop build %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/doc/doc.go b/cmd/internal/doc/doc.go
index 6c755e22..e3f7d306 100644
--- a/cmd/internal/doc/doc.go
+++ b/cmd/internal/doc/doc.go
@@ -26,10 +26,10 @@ import (
 	"strings"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cl/outline"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 )
@@ -76,25 +76,25 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 
 	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
+	conf := &tool.Config{Gop: gopEnv}
 	outlinePkg(proj, conf)
 }
 
-func outlinePkg(proj gopprojs.Proj, conf *gop.Config) {
+func outlinePkg(proj gopprojs.Proj, conf *tool.Config) {
 	var obj string
 	var out outline.Package
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		out, err = gop.Outline(obj, conf)
+		out, err = tool.Outline(obj, conf)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		out, err = gop.OutlinePkgPath("", obj, conf, true)
+		out, err = tool.OutlinePkgPath("", obj, conf, true)
 	default:
 		log.Panicln("`gop doc` doesn't support", reflect.TypeOf(v))
 	}
-	if gop.NotFound(err) {
+	if tool.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop doc %v: not Go/Go+ files found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index d66682b9..31c64145 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -24,9 +24,9 @@ import (
 	"reflect"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/qiniu/x/errors"
 )
@@ -44,6 +44,7 @@ var (
 	flagSingleMode       = flag.Bool("s", false, "run in single file mode for package")
 	flagIgnoreNotatedErr = flag.Bool(
 		"ignore-notated-error", false, "ignore notated errors, only available together with -t (check mode)")
+	flagTags = flag.String("tags", "", "a comma-separated list of additional build tags to consider satisfied")
 )
 
 func init() {
@@ -71,30 +72,30 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", 0)
+	conf, err := tool.NewDefaultConf(".", 0, *flagTags)
 	if err != nil {
-		log.Panicln("gop.NewDefaultConf:", err)
+		log.Panicln("tool.NewDefaultConf:", err)
 	}
 	defer conf.UpdateCache()
 
-	flags := gop.GenFlagPrintError | gop.GenFlagPrompt
+	flags := tool.GenFlagPrintError | tool.GenFlagPrompt
 	if *flagCheckMode {
-		flags |= gop.GenFlagCheckOnly
+		flags |= tool.GenFlagCheckOnly
 		if *flagIgnoreNotatedErr {
 			conf.IgnoreNotatedError = true
 		}
 	}
 	if *flagSingleMode {
-		flags |= gop.GenFlagSingleFile
+		flags |= tool.GenFlagSingleFile
 	}
 	for _, proj := range projs {
 		switch v := proj.(type) {
 		case *gopprojs.DirProj:
-			_, _, err = gop.GenGoEx(v.Dir, conf, true, flags)
+			_, _, err = tool.GenGoEx(v.Dir, conf, true, flags)
 		case *gopprojs.PkgPathProj:
-			_, _, err = gop.GenGoPkgPathEx("", v.Path, conf, true, flags)
+			_, _, err = tool.GenGoPkgPathEx("", v.Path, conf, true, flags)
 		case *gopprojs.FilesProj:
-			_, err = gop.GenGoFiles("", v.Files, conf)
+			_, err = tool.GenGoFiles("", v.Files, conf)
 		default:
 			log.Panicln("`gop go` doesn't support", reflect.TypeOf(v))
 		}
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index cab4619d..1cf792da 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -26,10 +26,9 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/goplus/gop"
-
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/format"
+	"github.com/goplus/gop/tool"
 
 	goformat "go/format"
 	"go/parser"
@@ -147,7 +146,7 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 		dir, _ := filepath.Split(path)
 		fn, ok := w.dirMap[dir]
 		if !ok {
-			if mod, err := gop.LoadMod(path); err == nil {
+			if mod, err := tool.LoadMod(path); err == nil {
 				fn = func(ext string) (ok bool, class bool) {
 					switch ext {
 					case ".go", ".gop":
diff --git a/cmd/internal/gopget/get.go b/cmd/internal/gopget/get.go
index b9b6756a..0cff04a7 100644
--- a/cmd/internal/gopget/get.go
+++ b/cmd/internal/gopget/get.go
@@ -21,8 +21,8 @@ import (
 	"log"
 	"os"
 
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/mod/modcache"
 	"github.com/goplus/mod/modfetch"
 	"github.com/goplus/mod/modload"
@@ -62,7 +62,7 @@ func runCmd(cmd *base.Command, args []string) {
 func get(pkgPath string) {
 	modBase := ""
 	mod, err := modload.Load(".")
-	noMod := gop.NotFound(err)
+	noMod := tool.NotFound(err)
 	if !noMod {
 		check(err)
 		modBase = mod.Path()
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index 09166c84..9639faa6 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -24,9 +24,9 @@ import (
 	"reflect"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/mod/modfetch"
@@ -71,9 +71,9 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
+	conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags())
 	if err != nil {
-		log.Panicln("gop.NewDefaultConf:", err)
+		log.Panicln("tool.NewDefaultConf:", err)
 	}
 	defer conf.UpdateCache()
 
@@ -84,23 +84,23 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 }
 
-func install(proj gopprojs.Proj, conf *gop.Config, install *gocmd.InstallConfig) {
-	const flags = gop.GenFlagPrompt
+func install(proj gopprojs.Proj, conf *tool.Config, install *gocmd.InstallConfig) {
+	const flags = tool.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.InstallDir(obj, conf, install, flags)
+		err = tool.InstallDir(obj, conf, install, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.InstallPkgPath("", v.Path, conf, install, flags)
+		err = tool.InstallPkgPath("", v.Path, conf, install, flags)
 	case *gopprojs.FilesProj:
-		err = gop.InstallFiles(v.Files, conf, install)
+		err = tool.InstallFiles(v.Files, conf, install)
 	default:
 		log.Panicln("`gop install` doesn't support", reflect.TypeOf(v))
 	}
-	if gop.NotFound(err) {
+	if tool.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop install %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go
index 2a79d340..a40ce253 100644
--- a/cmd/internal/mod/tidy.go
+++ b/cmd/internal/mod/tidy.go
@@ -20,8 +20,8 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gopenv"
 )
 
@@ -36,9 +36,9 @@ func init() {
 }
 
 func runTidy(cmd *base.Command, args []string) {
-	err := gop.Tidy(".", gopenv.Get())
+	err := tool.Tidy(".", gopenv.Get())
 	if err != nil {
-		if gop.NotFound(err) {
+		if tool.NotFound(err) {
 			fmt.Fprintln(os.Stderr, "go.mod not found")
 		} else {
 			fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index 96cf53aa..8b01293e 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -23,9 +23,9 @@ import (
 	"reflect"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/qiniu/x/log"
@@ -80,9 +80,9 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 
 	noChdir := *flagNoChdir
-	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
+	conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags())
 	if err != nil {
-		log.Panicln("gop.NewDefaultConf:", err)
+		log.Panicln("tool.NewDefaultConf:", err)
 	}
 	defer conf.UpdateCache()
 
@@ -94,23 +94,23 @@ func runCmd(cmd *base.Command, args []string) {
 	run(proj, args, !noChdir, conf, confCmd)
 }
 
-func run(proj gopprojs.Proj, args []string, chDir bool, conf *gop.Config, run *gocmd.RunConfig) {
+func run(proj gopprojs.Proj, args []string, chDir bool, conf *tool.Config, run *gocmd.RunConfig) {
 	const flags = 0
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.RunDir(obj, args, conf, run, flags)
+		err = tool.RunDir(obj, args, conf, run, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.RunPkgPath(v.Path, args, chDir, conf, run, flags)
+		err = tool.RunPkgPath(v.Path, args, chDir, conf, run, flags)
 	case *gopprojs.FilesProj:
-		err = gop.RunFiles("", v.Files, args, conf, run)
+		err = tool.RunFiles("", v.Files, args, conf, run)
 	default:
 		log.Panicln("`gop run` doesn't support", reflect.TypeOf(v))
 	}
-	if gop.NotFound(err) {
+	if tool.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop run %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/watch/watch.go b/cmd/internal/watch/watch.go
index 32b2b022..b753052f 100644
--- a/cmd/internal/watch/watch.go
+++ b/cmd/internal/watch/watch.go
@@ -20,8 +20,8 @@ import (
 	"log"
 	"path/filepath"
 
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/fsnotify"
 	"github.com/goplus/gop/x/watcher"
 )
@@ -70,7 +70,7 @@ func runCmd(cmd *base.Command, args []string) {
 	for {
 		dir := w.Fetch(true)
 		log.Println("GenGo", dir)
-		_, _, err := gop.GenGo(dir, nil, *genTestPkg)
+		_, _, err := tool.GenGo(dir, nil, *genTestPkg)
 		if err != nil {
 			log.Println(err)
 		}
diff --git a/demo/_llgo/go.mod b/demo/_llgo/go.mod
index bb27fb17..8f07e413 100644
--- a/demo/_llgo/go.mod
+++ b/demo/_llgo/go.mod
@@ -2,4 +2,4 @@ module llgoexample
 
 go 1.18 // llgo 1.0
 
-require github.com/goplus/llgo v0.9.2
+require github.com/goplus/llgo v0.9.9
diff --git a/demo/_llgo/go.sum b/demo/_llgo/go.sum
index a400d919..7b666e2d 100644
--- a/demo/_llgo/go.sum
+++ b/demo/_llgo/go.sum
@@ -1,2 +1,2 @@
-github.com/goplus/llgo v0.9.2 h1:bzVroPtjT5Ib9qvLk3VYBcKcMS7Sbo5vPRKkqNZUzDk=
-github.com/goplus/llgo v0.9.2/go.mod h1:zsrtWZapL4aklZc99xBSZRynGzLTIT1mLRjP0VSn9iw=
+github.com/goplus/llgo v0.9.9 h1:sCM8ehOtuAEY/NQbJ4xAMRZhxEdDgilouWZpoaLiYrM=
+github.com/goplus/llgo v0.9.9/go.mod h1:udcq+s6tGOdhJq7I8fXPTv4qT2j17/KrlvtcJrMZAoM=
diff --git a/demo/gop-parser/gopcalc/calc.gop b/demo/gop-parser/gopcalc/calc.gop
new file mode 100644
index 00000000..d9bd0cc8
--- /dev/null
+++ b/demo/gop-parser/gopcalc/calc.gop
@@ -0,0 +1,52 @@
+import (
+	"gop/ast"
+	"gop/parser"
+	"gop/token"
+	"math"
+	"os"
+)
+
+func calc(e ast.Expr) float64 {
+	switch e := e.(type) {
+	case *ast.BasicLit:
+		return e.Value.float!
+	case *ast.BinaryExpr:
+		switch e.Op {
+		case token.ADD:
+			return calc(e.X) + calc(e.Y)
+		case token.SUB:
+			return calc(e.X) - calc(e.Y)
+		case token.MUL:
+			return calc(e.X) * calc(e.Y)
+		case token.QUO:
+			return calc(e.X) + calc(e.Y)
+		}
+	case *ast.CallExpr:
+		switch e.Fun.(*ast.Ident).Name {
+		case "sin":
+			return math.Sin(calc(e.Args[0]))
+		case "cos":
+			return math.Cos(calc(e.Args[0]))
+		case "pow":
+			return math.Pow(calc(e.Args[0]), calc(e.Args[1]))
+		}
+	case *ast.ParenExpr:
+		return calc(e.X)
+	case *ast.UnaryExpr:
+		switch e.Op {
+		case token.SUB:
+			return -calc(e.X)
+		}
+	}
+	panic("unknown expression")
+}
+
+print "> "
+for line <- os.Stdin {
+	e, err := parser.parseExpr(line)
+	if err != nil {
+		print "error: ${err}\n> "
+	} else {
+		print "${calc(e)}\n> "
+	}
+}
diff --git a/demo/gop-parser/parser.gop b/demo/gop-parser/parser.gop
new file mode 100644
index 00000000..6052030d
--- /dev/null
+++ b/demo/gop-parser/parser.gop
@@ -0,0 +1,7 @@
+import (
+	"gop/ast"
+	"gop/parser"
+)
+
+e := parser.parseExpr("10 + 3.2")!.(*ast.BinaryExpr)
+echo e.X, e.Op, e.Y
diff --git a/demo/gop-parser/pseudo/ast.gop b/demo/gop-parser/pseudo/ast.gop
new file mode 100644
index 00000000..5458bcaf
--- /dev/null
+++ b/demo/gop-parser/pseudo/ast.gop
@@ -0,0 +1,90 @@
+import "gop/ast"
+
+// Stmt - 语句
+type Stmt interface {
+}
+
+// Type - 类型
+type Type interface {
+}
+
+// Ident - 标识符（类型）
+//
+type Ident struct {
+	Name string
+}
+
+// EmptyStmt - 空语句
+type EmptyStmt struct {
+}
+
+// VarStmt - 变量声明语句
+//
+// DECLARE {变量名1}, {变量名2}... : {类型}
+type VarStmt struct {
+	Names []string
+	Type  Type
+}
+
+// ConstStmt - 常量声明语句
+//
+// CONSTANT {常量名} <- {表达式}
+type ConstStmt struct {
+	Name  string
+	Value ast.Expr
+}
+
+// AssignStmt - 赋值语句
+//
+// {变量名} <- {表达式}
+type AssignStmt struct {
+	Name  string
+	Value ast.Expr
+}
+
+// OutputStmt - 输出语句
+//
+// OUTPUT {表达式}
+type OutputStmt struct {
+	Value ast.Expr
+}
+
+// InputStmt - 输入语句
+//
+// INPUT {变量名1}, {变量名2}...
+type InputStmt struct {
+	Names []string
+}
+
+// IfStmt - 条件语句
+//
+// IF {表达式} THEN
+//	 {语句块}
+// ELSE
+//	 {语句块}
+// ENDIF
+type IfStmt struct {
+	Cond ast.Expr
+	Body []Stmt
+	Else []Stmt // 可选
+}
+
+// WhileStmt - While循环语句
+//
+// WHILE {表达式} DO
+//	 {语句块}
+// ENDWHILE
+type WhileStmt struct {
+	Cond ast.Expr
+	Body []Stmt
+}
+
+// UntilStmt - Until循环语句
+//
+// REPEAT
+//	 {语句块}
+// UNTIL {表达式}
+type UntilStmt struct {
+	Body []Stmt
+	Cond ast.Expr
+}
diff --git a/demo/gop-parser/pseudo/gauss.pseudo b/demo/gop-parser/pseudo/gauss.pseudo
new file mode 100644
index 00000000..eef9e9a6
--- /dev/null
+++ b/demo/gop-parser/pseudo/gauss.pseudo
@@ -0,0 +1,10 @@
+DECLARE n, i, sum : INTEGER
+OUTPUT "please input n:"
+INPUT n
+i <- 1
+sum <- 0
+WHILE i <= n DO
+    sum <- sum + i
+    i <- i + 1
+ENDWHILE
+OUTPUT sum
diff --git a/demo/gop-parser/pseudo/main.gop b/demo/gop-parser/pseudo/main.gop
new file mode 100644
index 00000000..46e8a55a
--- /dev/null
+++ b/demo/gop-parser/pseudo/main.gop
@@ -0,0 +1,15 @@
+import "os"
+
+func exec(code string) {
+	lines := code.split("\n")
+	stmts := parse(lines)
+	s := state.new()
+	s.exec(stmts)
+}
+
+if len(os.Args) < 2 {
+	println("Usage: pseudo <file>")
+	return
+}
+code := string(os.readFile(os.Args[1])!)
+exec(code)
diff --git a/demo/gop-parser/pseudo/parser.gop b/demo/gop-parser/pseudo/parser.gop
new file mode 100644
index 00000000..1004252e
--- /dev/null
+++ b/demo/gop-parser/pseudo/parser.gop
@@ -0,0 +1,200 @@
+import (
+	"gop/ast"
+	"gop/parser"
+	"gop/scanner"
+	"gop/token"
+)
+
+func parse(lines []string) []Stmt {
+	stmts, rest := parseBlockStmt(lines)
+	if len(rest) > 0 {
+		panic("unexpected ${rest[0]}")
+	}
+	return stmts
+}
+
+func parseBlockStmt(lines []string) (stmts []Stmt, rest []string) {
+	var stmt Stmt
+	for len(lines) > 0 {
+		stmt, lines = parseStmt(lines)
+		if stmt == nil {
+			break
+		}
+		stmts <- stmt
+	}
+	rest = lines
+	return
+}
+
+func parseStmt(lines []string) (Stmt, []string) {
+	parts := lines[0].trimLeft(" \t").splitN(" ", 2)
+	switch parts[0] {
+	case "DECLARE":
+		return parseVarStmt(parts[1], lines)
+	case "CONSTANT":
+		return parseConstStmt(parts[1], lines)
+	case "OUTPUT":
+		return parseOutputStmt(parts[1], lines)
+	case "INPUT":
+		return parseInputStmt(parts[1], lines)
+	case "IF":
+		return parseIfStmt(parts[1], lines)
+	case "WHILE":
+		return parseWhileStmt(parts[1], lines)
+	case "REPEAT":
+		return parseUntilStmt(lines)
+	case "":
+		return &EmptyStmt{}, lines[1:]
+	case "ENDIF", "ENDWHILE", "UNTIL", "ELSE":
+		return nil, lines
+	default:
+		return parseAssignStmt(lines)
+	}
+}
+
+func expect(s *scanner.Scanner, tokExp token.Token) (token.Pos, string) {
+	pos, tok, lit := s.scan()
+	if tok != tokExp {
+		panic("expect ${tokExp}")
+	}
+	return pos, lit
+}
+
+// Type - 类型
+func parseType(s *scanner.Scanner) Type {
+	_, name := expect(s, token.IDENT)
+	return &Ident{Name: name}
+}
+
+// {变量名1}, {变量名2}...
+func parseNames(s *scanner.Scanner) (names []string, tok token.Token) {
+	for {
+		_, name := expect(s, token.IDENT) // {名称}
+		names <- name
+		_, tok, _ = s.scan()
+		if tok == token.COMMA { // ,
+			continue
+		}
+		return
+	}
+}
+
+// {常量名} <- {表达式}
+func parseNameAndValue(rest string) (string, ast.Expr) {
+	s := scanner.new(rest, nil, 0)
+	_, name := expect(s, token.IDENT) // {名称}
+	pos, _ := expect(s, token.ARROW)  // <-
+	value := parseExpr(rest[pos+1:])  // {表达式}
+	return name, value
+}
+
+// Expr - 表达式
+func parseExpr(expr string) ast.Expr {
+	e, err := parser.parseExpr(expr)
+	if err != nil {
+		panic(err)
+	}
+	return e
+}
+
+// VarStmt - 变量声明语句
+//
+// DECLARE {变量名1}, {变量名2}... : {类型}
+func parseVarStmt(rest string, lines []string) (Stmt, []string) {
+	s := scanner.new(rest, nil, 0)
+	names, tok := parseNames(s) // {变量名1}, {变量名2}...
+	if tok != token.COLON {     // :
+		panic("DECLARE: expect , or :")
+	}
+	typ := parseType(s) // {类型}
+	return &VarStmt{Names: names, Type: typ}, lines[1:]
+}
+
+// ConstStmt - 常量声明语句
+//
+// CONSTANT {常量名} <- {表达式}
+func parseConstStmt(rest string, lines []string) (Stmt, []string) {
+	name, value := parseNameAndValue(rest)
+	return &ConstStmt{Name: name, Value: value}, lines[1:]
+}
+
+// AssignStmt - 赋值语句
+//
+// {变量名} <- {表达式}
+func parseAssignStmt(lines []string) (Stmt, []string) {
+	name, value := parseNameAndValue(lines[0])
+	return &AssignStmt{Name: name, Value: value}, lines[1:]
+}
+
+// OutputStmt - 输出语句
+//
+// OUTPUT {表达式}
+func parseOutputStmt(rest string, lines []string) (Stmt, []string) {
+	value := parseExpr(rest)
+	return &OutputStmt{Value: value}, lines[1:]
+}
+
+// InputStmt - 输入语句
+//
+// INPUT {变量名1}, {变量名2}...
+func parseInputStmt(rest string, lines []string) (Stmt, []string) {
+	s := scanner.new(rest, nil, 0)
+	names, _ := parseNames(s)
+	return &InputStmt{Names: names}, lines[1:]
+}
+
+// IfStmt - If 语句
+//
+// IF {条件} THEN
+//   {语句块}
+// ELSE
+//   {语句块}
+// ENDIF
+func parseIfStmt(rest string, lines []string) (Stmt, []string) {
+	rest = rest.trimRight(" \t").trimSuffix("THEN")
+	cond := parseExpr(rest)
+	body, lines := parseBlockStmt(lines[1:])
+	if len(lines) == 0 {
+		panic("expect ELSE or ENDIF")
+	}
+	stmt := &IfStmt{Cond: cond, Body: body}
+	if lines[0].trimSpace == "ELSE" {
+		stmt.Else, lines = parseBlockStmt(lines[1:])
+	}
+	if len(lines) == 0 || lines[0].trimSpace != "ENDIF" {
+		panic("expect ENDIF")
+	}
+	return stmt, lines[1:]
+}
+
+// WhileStmt - While 语句
+//
+// WHILE {条件} DO
+//   {语句块}
+// ENDWHILE
+func parseWhileStmt(rest string, lines []string) (Stmt, []string) {
+	rest = rest.trimRight(" \t").trimSuffix("DO")
+	cond := parseExpr(rest)
+	body, lines := parseBlockStmt(lines[1:])
+	if len(lines) == 0 || lines[0].trimSpace != "ENDWHILE" {
+		panic("expect ENDWHILE")
+	}
+	return &WhileStmt{Cond: cond, Body: body}, lines[1:]
+}
+
+// UntilStmt - Until 语句
+//
+// REPEAT
+//   {语句块}
+// UNTIL {条件}
+func parseUntilStmt(lines []string) (Stmt, []string) {
+	body, lines := parseBlockStmt(lines[1:])
+	if len(lines) > 0 {
+		line := lines[0].trimLeft(" \t")
+		if line.hasPrefix("UNTIL ") {
+			cond := parseExpr(line[6:])
+			return &UntilStmt{Body: body, Cond: cond}, lines[1:]
+		}
+	}
+	panic("expect UNTIL")
+}
diff --git a/demo/gop-parser/pseudo/state.gox b/demo/gop-parser/pseudo/state.gox
new file mode 100644
index 00000000..fe5b4b46
--- /dev/null
+++ b/demo/gop-parser/pseudo/state.gox
@@ -0,0 +1,484 @@
+import (
+	"fmt"
+	"gop/ast"
+	"gop/token"
+	"math"
+	"reflect"
+)
+
+var (
+	vars   map[string]any
+	consts map[string]any
+)
+
+func .new() *state {
+	return &state{vars: {}, consts: {}}
+}
+
+func exec(stmts []Stmt) {
+	for _, stmt := range stmts {
+		execStmt(stmt)
+	}
+}
+
+func execStmt(stmt Stmt) {
+	switch stmt := stmt.(type) {
+	case *EmptyStmt:
+	case *VarStmt:
+		execVarStmt(stmt)
+	case *AssignStmt:
+		execAssignStmt(stmt)
+	case *OutputStmt:
+		execOutputStmt(stmt)
+	case *InputStmt:
+		execInputStmt(stmt)
+	case *ConstStmt:
+		execConstStmt(stmt)
+	case *IfStmt:
+		execIfStmt(stmt)
+	case *WhileStmt:
+		execWhileStmt(stmt)
+	case *UntilStmt:
+		execUntilStmt(stmt)
+	default:
+		panic("unknown statement")
+	}
+}
+
+func execVarStmt(stmt *VarStmt) {
+	for name <- stmt.Names {
+		if _, ok := vars[name]; ok {
+			panic("variable `${name}` already declared")
+		}
+		vars[name] = zero(stmt.Type)
+	}
+}
+
+func execConstStmt(stmt *ConstStmt) {
+	name := stmt.Name
+	if _, ok := consts[name]; ok {
+		panic("constant `${name}` already declared")
+	}
+	consts[name] = calc(stmt.Value)
+}
+
+func execAssignStmt(stmt *AssignStmt) {
+	name := stmt.Name
+	oldv, ok := vars[name]
+	if !ok {
+		panic("undefined variable `${name}`")
+	}
+	v := calc(stmt.Value)
+	if reflect.typeOf(oldv) != reflect.typeOf(v) {
+		panic("assignment of `${name}`: type mismatch")
+	}
+	vars[name] = v
+}
+
+func execOutputStmt(stmt *OutputStmt) {
+	echo(calc(stmt.Value))
+}
+
+func execInputStmt(stmt *InputStmt) {
+	for name <- stmt.Names {
+		oldv, ok := vars[name]
+		if !ok {
+			panic("undefined variable `${name}`")
+		}
+		v := reflect.new(reflect.typeOf(oldv))
+		fmt.scanln(v.Interface())!
+		vars[name] = v.elem.Interface()
+	}
+}
+
+func execIfStmt(stmt *IfStmt) {
+	if toBool(calc(stmt.Cond), "IF") {
+		exec(stmt.Body)
+	} else if stmt.Else != nil {
+		exec(stmt.Else)
+	}
+}
+
+func execWhileStmt(stmt *WhileStmt) {
+	for toBool(calc(stmt.Cond), "WHILE") {
+		exec(stmt.Body)
+	}
+}
+
+func execUntilStmt(stmt *UntilStmt) {
+	for {
+		exec(stmt.Body)
+		if toBool(calc(stmt.Cond), "UNTIL") {
+			return
+		}
+	}
+}
+
+func calc(e ast.Expr) any {
+	switch e := e.(type) {
+	case *ast.BasicLit:
+		switch e.Kind {
+		case token.INT:
+			return e.Value.int!
+		case token.FLOAT:
+			return e.Value.float!
+		case token.STRING:
+			return e.Value.unquote!
+		}
+	case *ast.Ident:
+		return getValue(e.Name)
+	case *ast.BinaryExpr:
+		switch e.Op {
+		case token.ADD:
+			return calcAdd(calc(e.X), calc(e.Y))
+		case token.SUB:
+			return calcSub(calc(e.X), calc(e.Y))
+		case token.MUL:
+			return calcMul(calc(e.X), calc(e.Y))
+		case token.QUO:
+			return calcQuo(calc(e.X), calc(e.Y))
+		case token.EQL:
+			return calcEQL(calc(e.X), calc(e.Y))
+		case token.NEQ:
+			return calcNEQ(calc(e.X), calc(e.Y))
+		case token.LSS:
+			return calcLSS(calc(e.X), calc(e.Y))
+		case token.GTR:
+			return calcGTR(calc(e.Y), calc(e.X))
+		case token.LEQ:
+			return calcLEQ(calc(e.X), calc(e.Y))
+		case token.GEQ:
+			return calcGEQ(calc(e.Y), calc(e.X))
+		default:
+			panic("unknown binary operator ${e.Op}")
+		}
+	case *ast.CallExpr:
+		fn := e.Fun.(*ast.Ident).Name
+		switch fn {
+		case "SIN":
+			x := toFloat(calc(e.Args[0]), "SIN")
+			return math.Sin(x)
+		case "COS":
+			x := toFloat(calc(e.Args[0]), "COS")
+			return math.Cos(x)
+		case "POW":
+			x := toFloat(calc(e.Args[0]), "POW")
+			y := toFloat(calc(e.Args[1]), "POW")
+			return math.Pow(x, y)
+		default:
+			panic("unknown function `${fn}`")
+		}
+	case *ast.ParenExpr:
+		return calc(e.X)
+	case *ast.UnaryExpr:
+		switch e.Op {
+		case token.SUB:
+			return calcNeg(calc(e.X))
+		case token.NOT:
+			return calcNot(calc(e.X))
+		default:
+			panic("unknown unary operator ${e.Op}")
+		}
+	}
+	panic("unknown expression: ${reflect.typeOf(e)}")
+}
+
+func calcAdd(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a + b
+		case float64:
+			return float64(a) + b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a + float64(b)
+		case float64:
+			return a + b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a + b
+		}
+	}
+	panic("invalid operands of +")
+}
+
+func calcSub(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a - b
+		case float64:
+			return float64(a) - b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a - float64(b)
+		case float64:
+			return a - b
+		}
+	}
+	panic("invalid operands of -")
+}
+
+func calcMul(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a * b
+		case float64:
+			return float64(a) * b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a * float64(b)
+		case float64:
+			return a * b
+		}
+	}
+	panic("invalid operands of *")
+}
+
+func calcQuo(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a / b
+		case float64:
+			return float64(a) / b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a / float64(b)
+		case float64:
+			return a / b
+		}
+	}
+	panic("invalid operands of /")
+}
+
+func calcLSS(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a < b
+		case float64:
+			return float64(a) < b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a < float64(b)
+		case float64:
+			return a < b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a < b
+		}
+	}
+	panic("invalid operands of <")
+}
+
+func calcGTR(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a > b
+		case float64:
+			return float64(a) > b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a > float64(b)
+		case float64:
+			return a > b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a > b
+		}
+	}
+	panic("invalid operands of >")
+}
+
+func calcLEQ(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a <= b
+		case float64:
+			return float64(a) <= b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a <= float64(b)
+		case float64:
+			return a <= b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a <= b
+		}
+	}
+	panic("invalid operands of <=")
+}
+
+func calcGEQ(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a >= b
+		case float64:
+			return float64(a) >= b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a >= float64(b)
+		case float64:
+			return a >= b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a >= b
+		}
+	}
+	panic("invalid operands of >=")
+}
+
+func calcEQL(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a == b
+		case float64:
+			return float64(a) == b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a == float64(b)
+		case float64:
+			return a == b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a == b
+		}
+	case bool:
+		if b, ok := b.(bool); ok {
+			return a == b
+		}
+	}
+	panic("invalid operands of ==")
+}
+
+func calcNEQ(a, b any) any {
+	switch a := a.(type) {
+	case int:
+		switch b := b.(type) {
+		case int:
+			return a != b
+		case float64:
+			return float64(a) != b
+		}
+	case float64:
+		switch b := b.(type) {
+		case int:
+			return a != float64(b)
+		case float64:
+			return a != b
+		}
+	case string:
+		if b, ok := b.(string); ok {
+			return a != b
+		}
+	case bool:
+		if b, ok := b.(bool); ok {
+			return a != b
+		}
+	}
+	panic("invalid operands of !=")
+}
+
+func calcNeg(a any) any {
+	switch a := a.(type) {
+	case int:
+		return -a
+	case float64:
+		return -a
+	}
+	panic("invalid operand of unary -")
+}
+
+func calcNot(a any) any {
+	switch a := a.(type) {
+	case bool:
+		return !a
+	}
+	panic("invalid operand of !")
+}
+
+func getValue(name string) any {
+	if v, ok := vars[name]; ok {
+		return v
+	}
+	if v, ok := consts[name]; ok {
+		return v
+	}
+	panic("undefined variable `${name}`")
+}
+
+func toBool(v any, at string) bool {
+	switch v := v.(type) {
+	case bool:
+		return v
+	}
+	panic("${at}: need boolean expression")
+}
+
+func toFloat(v any, at string) float64 {
+	switch v := v.(type) {
+	case int:
+		return float64(v)
+	case float64:
+		return v
+	}
+	panic("${at}: cannot convert operand to float64")
+}
+
+// INTEGER, REAL, STRING, BOOLEAN
+func zero(t Type) any {
+	switch t := t.(type) {
+	case *Ident:
+		switch t.Name {
+		case "INTEGER":
+			return 0
+		case "REAL":
+			return 0.0
+		case "STRING":
+			return ""
+		case "BOOLEAN":
+			return false
+		}
+	}
+	panic("unknown type")
+}
diff --git a/demo/gop-scanner/rpncalc/rpncalc.gop b/demo/gop-scanner/rpncalc/rpncalc.gop
new file mode 100644
index 00000000..37fd9abf
--- /dev/null
+++ b/demo/gop-scanner/rpncalc/rpncalc.gop
@@ -0,0 +1,39 @@
+import (
+	"gop/scanner"
+	"gop/token"
+	"os"
+)
+
+func calc(expr string) string {
+	var vals []float64
+	s := scanner.new(expr, nil, 0)
+	for {
+		pos, tok, lit := s.scan()
+		switch tok {
+		case token.INT, token.FLOAT:
+			vals <- lit.float!
+		case token.ADD, token.SUB, token.MUL, token.QUO:
+			x := len(vals) - 1
+			switch tok {
+			case token.ADD:
+				vals[x-1] += vals[x]
+			case token.SUB:
+				vals[x-1] -= vals[x]
+			case token.MUL:
+				vals[x-1] *= vals[x]
+			case token.QUO:
+				vals[x-1] /= vals[x]
+			}
+			vals = vals[:x]
+		case token.EOF, token.SEMICOLON:
+			return vals[0].string
+		default:
+			return "${pos}: invalid token ${tok}"
+		}
+	}
+}
+
+print "> "
+for line <- os.Stdin {
+	print "${calc(line)}\n> "
+}
diff --git a/demo/gop-scanner/scanner.gop b/demo/gop-scanner/scanner.gop
new file mode 100644
index 00000000..4ee2845e
--- /dev/null
+++ b/demo/gop-scanner/scanner.gop
@@ -0,0 +1,7 @@
+import "gop/scanner"
+
+s := scanner.new("10 + 3.2", nil, 0)
+_, _, lit1 := s.scan()
+_, tok2, _ := s.scan()
+_, _, lit3 := s.scan()
+echo lit1, tok2, lit3
diff --git a/demo/gop-scanner/simplecalc/calc.gop b/demo/gop-scanner/simplecalc/calc.gop
new file mode 100644
index 00000000..272d5834
--- /dev/null
+++ b/demo/gop-scanner/simplecalc/calc.gop
@@ -0,0 +1,74 @@
+import (
+	"gop/scanner"
+	"gop/token"
+	"os"
+)
+
+func calc(expr string) string {
+	var (
+		ops  []token.Token
+		vals []float64
+	)
+	prec := {
+		token.ADD: 1,
+		token.SUB: 1,
+		token.MUL: 2,
+		token.QUO: 2,
+	}
+	s := scanner.new(expr, nil, 0)
+	n := 0       // op count
+	rv := true   // require value
+	neg := false // negative number
+	for {
+		pos, tok, lit := s.scan()
+		switch tok {
+		case token.INT, token.FLOAT:
+			if !rv {
+				return "${pos}: require operator"
+			}
+			v := lit.float!
+			if neg {
+				v = -v
+				neg = false
+			}
+			vals <- v
+			rv = false
+		case token.ADD, token.SUB, token.MUL, token.QUO, token.SEMICOLON:
+			if rv {
+				if tok == token.SUB {
+					neg = !neg // toggle negative flag
+					continue
+				}
+				return "${pos}: require value"
+			}
+			for n > 0 && prec[ops[n-1]] >= prec[tok] {
+				x := len(vals) - 1
+				switch ops[n-1] {
+				case token.ADD:
+					vals[x-1] += vals[x]
+				case token.SUB:
+					vals[x-1] -= vals[x]
+				case token.MUL:
+					vals[x-1] *= vals[x]
+				case token.QUO:
+					vals[x-1] /= vals[x]
+				}
+				vals = vals[:x]
+				n--
+			}
+			if tok == token.SEMICOLON {
+				return vals[0].string
+			}
+			ops = append(ops[:n], tok)
+			rv = true
+			n++
+		default:
+			return "${pos}: invalid token ${tok}"
+		}
+	}
+}
+
+print "> "
+for line <- os.Stdin {
+	print "${calc(line)}\n> "
+}
diff --git a/demo/stringtrans/transform.gop b/demo/stringtrans/transform.gop
new file mode 100644
index 00000000..a74c0a04
--- /dev/null
+++ b/demo/stringtrans/transform.gop
@@ -0,0 +1,3 @@
+t := "u_int32_t"
+echo t.split("_").capitalize.join("")
+echo t.split("_").repeat(3).join(" ")
diff --git a/demo/unitliteral/unitlit.gop b/demo/unitliteral/unitlit.gop
new file mode 100644
index 00000000..d91ed2e3
--- /dev/null
+++ b/demo/unitliteral/unitlit.gop
@@ -0,0 +1,5 @@
+import "time"
+
+echo "Let's wait for a second"
+time.sleep 1s
+echo "Hello, world"
diff --git a/doc/classfile.md b/doc/classfile.md
index 0e7bc2ee..7829c3d7 100644
--- a/doc/classfile.md
+++ b/doc/classfile.md
@@ -130,7 +130,7 @@ What is a `classfile`? Usually it consists of a `project class` and multiple `wo
 
 This is a bit abstract. Let's take the [2D Game Engine spx](https://github.com/goplus/spx) as an example. The base class of project class of `spx classfile` is called `Game`. The base class of worker class is called `Sprite`. Obviously, there will only be one Game instance in a game, but there are many types of sprites, so many types of worker classes are needed, but they all have the same base class called `Sprite`. Go+'s classfile allows you to specify different base classes for different worker classes. Although this is rare, it can be done.
 
-How does Go+ identify various class files of a classfile? by its filename. By convention, if we define a classfile called `foo`, then its project class is usually called `main_foo.gox`, and the worker class is usually called `xxx_foo.gox`. If this classfile does not have a worker class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen. For example, our previous `yap` framework only has project class, so a file name like `hello_yap.gox` can be correctly recognized.
+How does Go+ identify various class files of a classfile? by its filename. By convention, if we define a classfile called `foo`, then its project class is usually called `main_foo.gox`, and the worker class is usually called `xxx_foo.gox`. If this classfile does not have a worker class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen.
 
 The earliest version of Go+ allows classfiles to be identified through custom file extensions. For example, the project class of the `spx classfile` is called `main.spx`, and the worker class is called `xxx.spx`. Although this ability to customize extensions is still retained for now, we do not recommend its use and there is no guarantee that it will continue to be available in the future.
 
diff --git a/doc/spec-mini.md b/doc/spec-mini.md
index e33252b2..c362e0f7 100644
--- a/doc/spec-mini.md
+++ b/doc/spec-mini.md
@@ -1,7 +1,74 @@
-Go+ Specification for STEM Education
+The Go+ Mini Specification
 =====
 
-## Comments
+Go+ has a recommended best practice syntax set, which we call the Go+ Mini Specification. It is simple but Turing-complete and can elegantly implement any business requirements.
+
+## Notation
+
+The syntax is specified using a [variant](https://en.wikipedia.org/wiki/Wirth_syntax_notation) of Extended Backus-Naur Form (EBNF):
+
+```go
+Syntax      = { Production } .
+Production  = production_name "=" [ Expression ] "." .
+Expression  = Term { "|" Term } .
+Term        = Factor { Factor } .
+Factor      = production_name | token [ "…" token ] | Group | Option | Repetition .
+Group       = "(" Expression ")" .
+Option      = "[" Expression "]" .
+Repetition  = "{" Expression "}" .
+```
+
+Productions are expressions constructed from terms and the following operators, in increasing precedence:
+
+```go
+|   alternation
+()  grouping
+[]  option (0 or 1 times)
+{}  repetition (0 to n times)
+```
+
+Lowercase production names are used to identify lexical (terminal) tokens. Non-terminals are in CamelCase. Lexical tokens are enclosed in double quotes `""` or back quotes ``.
+
+The form `a … b` represents the set of characters from a through b as alternatives. The horizontal ellipsis `…` is also used elsewhere in the spec to informally denote various enumerations or code snippets that are not further specified. The character … (as opposed to the three characters `...`) is not a token of the Go+ language.
+
+## Source code representation
+
+Source code is Unicode text encoded in [UTF-8](https://en.wikipedia.org/wiki/UTF-8). The text is not canonicalized, so a single accented code point is distinct from the same character constructed from combining an accent and a letter; those are treated as two code points. For simplicity, this document will use the unqualified term character to refer to a Unicode code point in the source text.
+
+Each code point is distinct; for instance, uppercase and lowercase letters are different characters.
+
+Implementation restriction: For compatibility with other tools, a compiler may disallow the NUL character (U+0000) in the source text.
+
+Implementation restriction: For compatibility with other tools, a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) if it is the first Unicode code point in the source text. A byte order mark may be disallowed anywhere else in the source.
+
+### Characters
+
+The following terms are used to denote specific Unicode character categories:
+
+```go
+newline        = /* the Unicode code point U+000A */ .
+unicode_char   = /* an arbitrary Unicode code point except newline */ .
+unicode_letter = /* a Unicode code point categorized as "Letter" */ .
+unicode_digit  = /* a Unicode code point categorized as "Number, decimal digit" */ .
+```
+
+In [The Unicode Standard 8.0](https://www.unicode.org/versions/Unicode8.0.0/), Section 4.5 "General Category" defines a set of character categories. Go treats all characters in any of the Letter categories Lu, Ll, Lt, Lm, or Lo as Unicode letters, and those in the Number category Nd as Unicode digits.
+
+### Letters and digits
+
+The underscore character _ (U+005F) is considered a lowercase letter.
+
+```go
+letter        = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+binary_digit  = "0" | "1" .
+octal_digit   = "0" … "7" .
+hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Lexical elements
+
+### Comments
 
 Comments serve as program documentation. There are three forms:
 
@@ -17,14 +84,84 @@ A _general comment_ containing no newlines acts like a space. Any other comment
 /* this is a general comment */
 ```
 
-## Literals
+### Tokens
+
+Tokens form the vocabulary of the Go+ language. There are four classes: _identifiers_, _keywords_, _operators_ and _punctuation_, and _literals_. White space, formed from spaces (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A), is ignored except as it separates tokens that would otherwise combine into a single token. Also, a newline or end of file may trigger the insertion of a [semicolon](). While breaking the input into tokens, the next token is the longest sequence of characters that form a valid token.
+
+### Semicolons
+
+The formal syntax uses semicolons ";" as terminators in a number of productions. Go+ programs may omit most of these semicolons using the following two rules:
+
+* When the input is broken into tokens, a semicolon is automatically inserted into the token stream immediately after a line's final token if that token is
+  * an [identifier](#identifiers)
+  * an [integer](), [floating-point](), [imaginary](), [rune](), or [string]() literal
+  * one of the [keywords]() `break`, `continue`, `fallthrough`, or `return`
+  * one of the [operators and punctuation]() `++`, `--`, `)`, `]`, or `}`
+* To allow complex statements to occupy a single line, a semicolon may be omitted before a closing `")"` or `"}"`.
+
+To reflect idiomatic use, code examples in this document elide semicolons using these rules.
+
+### Identifiers
+
+Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```go
+identifier = letter { letter | unicode_digit } .
+```
+
+```go
+a
+_x9
+ThisVariableIsExported
+αβ
+```
+
+Some identifiers are [predeclared](#predeclared-identifiers).
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers (TODO: some keywords are allowed as identifiers).
+
+```go
+break        default      func         interface    select
+case         defer        go           map          struct
+chan         else         goto         package      switch
+const        fallthrough  if           range        type
+continue     for          import       return       var
+```
+
+### Operators and punctuation
+
+The following character sequences represent [operators](#operators) (including [assignment operators](#assignment-statements)) and punctuation:
+
+```
++    &     +=    &=     &&    ==    !=    (    )
+-    |     -=    |=     ||    <     <=    [    ]
+*    ^     *=    ^=     <-    >     >=    {    }
+/    <<    /=    <<=    ++    =     :=    ,    ;
+%    >>    %=    >>=    --    !     ...   .    :
+     &^          &^=          ~
+```
 
 ### Integer literals
 
-An integer literal is a sequence of digits representing an [integer constant](). An optional prefix sets a non-decimal base: 0b or 0B for binary, 0, 0o, or 0O for octal, and 0x or 0X for hexadecimal. A single 0 is considered a decimal zero. In hexadecimal literals, letters a through f and A through F represent values 10 through 15.
+An integer literal is a sequence of digits representing an [integer constant](#constants). An optional prefix sets a non-decimal base: 0b or 0B for binary, 0, 0o, or 0O for octal, and 0x or 0X for hexadecimal. A single 0 is considered a decimal zero. In hexadecimal literals, letters a through f and A through F represent values 10 through 15.
 
 For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal's value.
 
+```go
+int_lit        = decimal_lit | binary_lit | octal_lit | hex_lit .
+decimal_lit    = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
+binary_lit     = "0" ( "b" | "B" ) [ "_" ] binary_digits .
+octal_lit      = "0" [ "o" | "O" ] [ "_" ] octal_digits .
+hex_lit        = "0" ( "x" | "X" ) [ "_" ] hex_digits .
+
+decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
+binary_digits  = binary_digit { [ "_" ] binary_digit } .
+octal_digits   = octal_digit { [ "_" ] octal_digit } .
+hex_digits     = hex_digit { [ "_" ] hex_digit } .
+```
+
 ```go
 42
 4_2
@@ -46,7 +183,7 @@ _42         // an identifier, not an integer literal
 
 ### Floating-point literals
 
-A floating-point literal is a decimal or hexadecimal representation of a [floating-point constant]().
+A floating-point literal is a decimal or hexadecimal representation of a [floating-point constant](#constants).
 
 A decimal floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (e or E followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by 10<sup>exp</sup>.
 
@@ -54,6 +191,21 @@ A hexadecimal floating-point literal consists of a 0x or 0X prefix, an integer p
 
 For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal value.
 
+```go
+float_lit         = decimal_float_lit | hex_float_lit .
+
+decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
+                    decimal_digits decimal_exponent |
+                    "." decimal_digits [ decimal_exponent ] .
+decimal_exponent  = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .
+
+hex_float_lit     = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
+hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
+                    [ "_" ] hex_digits |
+                    "." hex_digits .
+hex_exponent      = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
+```
+
 ```go
 0.
 72.40
@@ -96,9 +248,13 @@ TODO
 
 ### Imaginary literals
 
-An imaginary literal represents the imaginary part of a [complex constant](). It consists of an [integer](#integer-literals) or [floating-point](#floating-point-literals) literal followed by the lowercase letter _i_. The value of an imaginary literal is the value of the respective integer or floating-point literal multiplied by the imaginary unit _i_.
+An imaginary literal represents the imaginary part of a [complex constant](#constants). It consists of an [integer](#integer-literals) or [floating-point](#floating-point-literals) literal followed by the lowercase letter _i_. The value of an imaginary literal is the value of the respective integer or floating-point literal multiplied by the imaginary unit _i_.
+
+```go
+imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
+```
 
-For backward compatibility, an imaginary literal's integer part consisting entirely of decimal digits (and possibly underscores) is considered a decimal integer, even if it starts with a leading 0.
+For backward compatibility, an imaginary literal's integer part consisting entirely of decimal digits (and possibly underscores) is considered a decimal integer, even if it starts with a leading `0`.
 
 ```go
 0i
@@ -117,7 +273,7 @@ For backward compatibility, an imaginary literal's integer part consisting entir
 
 ### Boolean literals
 
-The boolean truth values are represented by the predeclared constants `true` and `false`.
+The boolean truth values are represented by the [predeclared constants](#constants) `true` and `false`.
 
 ```go
 true
@@ -126,7 +282,7 @@ false
 
 ### Rune literals
 
-A rune literal represents a [rune constant](), an integer value identifying a Unicode code point. A rune literal is expressed as one or more characters enclosed in single quotes, as in `'x'` or `'\n'`. Within the quotes, any character may appear except newline and unescaped single quote. A single quoted character represents the Unicode value of the character itself, while multi-character sequences beginning with a backslash encode values in various formats.
+A rune literal represents a [rune constant](#constants), an integer value identifying a Unicode code point. A rune literal is expressed as one or more characters enclosed in single quotes, as in `'x'` or `'\n'`. Within the quotes, any character may appear except newline and unescaped single quote. A single quoted character represents the Unicode value of the character itself, while multi-character sequences beginning with a backslash encode values in various formats.
 
 The simplest form represents the single character within the quotes; since Go+ source text is Unicode characters encoded in UTF-8, multiple UTF-8-encoded bytes may represent a single integer value. For instance, the literal `'a'` holds a single byte representing a literal a, Unicode `U+0061`, value 0x61, while `'ä'` holds two bytes (0xc3 0xa4) representing a literal a-dieresis, `U+00E4`, value 0xe4.
 
@@ -151,6 +307,18 @@ After a backslash, certain single-character escapes represent special values:
 
 An unrecognized character following a backslash in a rune literal is illegal.
 
+```go
+rune_lit         = "'" ( unicode_value | byte_value ) "'" .
+unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value       = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value   = `\` "x" hex_digit hex_digit .
+little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
+                           hex_digit hex_digit hex_digit hex_digit .
+escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
+```
+
 ```go
 'a'
 'ä'
@@ -175,12 +343,18 @@ An unrecognized character following a backslash in a rune literal is illegal.
 
 ### String literals
 
-A string literal represents a [string constant]() obtained from concatenating a sequence of characters. There are two forms: raw string literals and interpreted string literals.
+A string literal represents a [string constant](#constants) obtained from concatenating a sequence of characters. There are two forms: raw string literals and interpreted string literals.
 
 Raw string literals are character sequences between back quotes, as in \`foo\`. Within the quotes, any character may appear except back quote. The value of a raw string literal is the string composed of the uninterpreted (implicitly UTF-8-encoded) characters between the quotes; in particular, backslashes have no special meaning and the string may contain newlines. Carriage return characters (`'\r'`) inside raw string literals are discarded from the raw string value.
 
 Interpreted string literals are character sequences between double quotes, as in `"bar"`. Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal, with backslash escapes interpreted as they are in [rune literals](#rune-literals) (except that `\'` is illegal and `\"` is legal), with the same restrictions. The three-digit octal (`\nnn`) and two-digit hexadecimal (`\xnn`) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal `\377` and `\xFF` represent a single byte of value 0xFF=255, while `ÿ`, `\u00FF`, `\U000000FF` and `\xc3\xbf` represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character `U+00FF`.
 
+```go
+string_lit             = raw_string_lit | interpreted_string_lit .
+raw_string_lit         = "`" { unicode_char | newline } "`" .
+interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
+```
+
 ```go
 `abc`                // same as "abc"
 `\n
@@ -220,9 +394,9 @@ iota
 
 There are _boolean constants_, _rune constants_, _integer constants_, _floating-point constants_, _complex constants_, and _string constants_. Rune, integer, floating-point, and complex constants are collectively called _numeric constants_.
 
-A constant value is represented by a [rune](#rune-literals), [integer](#integer-literals), [floating-point](#floating-point-literals), [imaginary](#imaginary-literals), [boolean](#boolean-literals) or [string](#string-literals) literal, an identifier denoting a constant, a [constant expression](), a [conversion](#conversions) with a result that is a constant, or the result value of some built-in functions such as `min` or `max` applied to constant arguments, `unsafe.Sizeof` applied to [certain values](), `cap` or `len` applied to [some expressions](), `real` and `imag` applied to a complex constant and complex applied to numeric constants. The predeclared identifier `iota` denotes an integer constant.
+A constant value is represented by a [rune](#rune-literals), [integer](#integer-literals), [floating-point](#floating-point-literals), [imaginary](#imaginary-literals), [boolean](#boolean-literals) or [string](#string-literals) literal, an identifier denoting a constant, a [constant expression](), a [conversion](#conversions) with a result that is a constant, or the result value of some built-in functions such as `min` or `max` applied to constant arguments, `unsafe.Sizeof` applied to [certain values](), `cap` or `len` applied to [some expressions](), `real` and `imag` applied to a complex constant and complex applied to numeric constants. The boolean truth values are represented by the predeclared constants `true` and `false`. The predeclared identifier `iota` denotes an integer constant.
 
-Not all literals are constants. For example:
+Not all literals are constants. For example (TODO: check this):
 
 ```sh
 nil
@@ -263,6 +437,21 @@ A variable's value is retrieved by referring to the variable in an [expression](
 
 ## Types
 
+A type determines a set of values together with operations and methods specific to those values. A type may be denoted by a _type name_. A type may also be specified using a type literal, which composes a type from existing types.
+
+```go
+Type      = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
+TypeName  = identifier | QualifiedIdent .
+TypeArgs  = "[" TypeList [ "," ] "]" .
+TypeList  = Type { "," Type } .
+TypeLit   = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
+            SliceType | MapType . // TODO: check this
+```
+
+The language [predeclares]() certain type names. Others are introduced with [type declarations](#type-declarations). _Composite types_—array, struct, pointer, function, interface, slice, map—may be constructed using type literals.
+
+Predeclared types and defined types are called _named types_. An alias denotes a named type if the type given in the alias declaration is a named type.
+
 ### Boolean types
 
 A _boolean type_ represents the set of Boolean truth values denoted by the predeclared constants true and false. The predeclared boolean type is `bool`; it is a defined type.
@@ -331,28 +520,49 @@ The length of a string `s` can be discovered using the built-in function [len]()
 An array is a numbered sequence of elements of a single type, called the element type. The number of elements is called the length of the array and is never negative.
 
 ```go
-[N]T
+ArrayType   = "[" ArrayLength "]" ElementType .
+ArrayLength = Expression .
+ElementType = Type .
 ```
 
-The length is part of the array's type; it must evaluate to a non-negative [constant]() [representable]() by a value of type int. The length of array `a` can be discovered using the built-in function [len](). The elements can be addressed by integer [indices]() `0` through `len(a)-1`. Array types are always one-dimensional but may be composed to form multi-dimensional types.
+The length is part of the array's type; it must evaluate to a non-negative [constant](#constants) [representable]() by a value of type int. The length of array `a` can be discovered using the built-in function [len](). The elements can be addressed by integer [indices]() `0` through `len(a)-1`. Array types are always one-dimensional but may be composed to form multi-dimensional types.
 
 ```go
 [32]byte
+[2*N] struct { x, y int32 }
 [1000]*float64
 [3][5]int
 [2][2][2]float64  // same as [2]([2]([2]float64))
 ```
 
+An array type T may not have an element of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or struct types.
+
+```go
+// invalid array types
+type (
+	T1 [10]T1                 // element type of T1 is T1
+	T2 [10]struct{ f T2 }     // T2 contains T2 as component of a struct
+	T3 [10]T4                 // T3 contains T3 as component of a struct in T4
+	T4 struct{ f T3 }         // T4 contains T4 as component of array T3 in a struct
+)
+
+// valid array types
+type (
+	T5 [10]*T5                // T5 contains T5 as component of a pointer
+	T6 [10]func() T6          // T6 contains T6 as component of a function type
+	T7 [10]struct{ f []T7 }   // T7 contains T7 as component of a slice in a struct
+)
+```
+
 ### Pointer types
 
-A _pointer_ type denotes the set of all pointers to [variables]() of a given type, called the base type of the pointer. The value of an uninitialized pointer is `nil`.
+A _pointer_ type denotes the set of all pointers to [variables](#variables) of a given type, called the base type of the pointer. The value of an uninitialized pointer is `nil`.
 
 ```go
-*T
+PointerType = "*" BaseType .
+BaseType    = Type .
 ```
 
-For example:
-
 ```go
 *Point
 *[4]int
@@ -360,10 +570,10 @@ For example:
 
 ### Slice types
 
-A _slice_ is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The number of elements is called the length of the slice and is never negative. The value of an uninitialized slice is `nil`.
+A _slice_ is a descriptor for a contiguous segment of an _underlying array_ and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The number of elements is called the length of the slice and is never negative. The value of an uninitialized slice is `nil`.
 
 ```go
-[]T
+SliceType = "[" "]" ElementType .
 ```
 
 The length of a slice `s` can be discovered by the built-in function [len](); unlike with arrays it may change during execution. The elements can be addressed by integer [indices]() `0` through `len(s)-1`. The slice index of a given element may be less than the index of the same element in the underlying array.
@@ -387,19 +597,114 @@ new([100]int)[0:50]
 
 Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.
 
+### Struct types
+
+A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-[blank](#blank-identifier) field names must be [unique]().
+
+```go
+StructType    = "struct" "{" { FieldDecl ";" } "}" .
+FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
+EmbeddedField = [ "*" ] TypeName [ TypeArgs ] .
+Tag           = string_lit .
+```
+
+```go
+// An empty struct.
+struct {}
+
+// A struct with 6 fields.
+struct {
+	x, y int
+	u float32
+	_ float32  // padding
+	A *[]int
+	F func()
+}
+```
+
+A field declared with a type but no explicit field name is called an _embedded field_. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.
+
+```go
+// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
+struct {
+	T1        // field name is T1
+	*T2       // field name is T2
+	P.T3      // field name is T3
+	*P.T4     // field name is T4
+	x, y int  // field names are x and y
+}
+```
+
+The following declaration is illegal because field names must be unique in a struct type:
+
+```go
+struct {
+	T     // conflicts with embedded field *T and *P.T
+	*T    // conflicts with embedded field T and *P.T
+	*P.T  // conflicts with embedded field T and *T
+}
+```
+
+A field `f` or [method]() of an embedded field in a struct `x` is called promoted if `x.f` is a legal [selector]() that denotes that field or method `f`.
+
+Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in [composite literals]() of the struct.
+
+Given a struct type `S` and a [named type](#types) `T`, promoted methods are included in the method set of the struct as follows:
+
+* If `S` contains an embedded field `T`, the [method sets]() of `S` and `*S` both include promoted methods with receiver `T`. The method set of `*S` also includes promoted methods with receiver `*T`.
+* If `S` contains an embedded field `*T`, the method sets of `S` and `*S` both include promoted methods with receiver `T` or `*T`.
+
+A field declaration may be followed by an optional string literal _tag_, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a [reflection interface]() and take part in [type identity]() for structs but are otherwise ignored.
+
+```go
+struct {
+	x, y float64 ""  // an empty tag string is like an absent tag
+	name string  "any string is permitted as a tag"
+	_    [4]byte "ceci n'est pas un champ de structure"
+}
+
+// A struct corresponding to a TimeStamp protocol buffer.
+// The tag strings define the protocol buffer field numbers;
+// they follow the convention outlined by the reflect package.
+struct {
+	microsec  uint64 `protobuf:"1"`
+	serverIP6 uint64 `protobuf:"2"`
+}
+```
+
+A struct type `T` may not contain a field of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or struct types.
+
+```go
+// invalid struct types
+type (
+	T1 struct{ T1 }            // T1 contains a field of T1
+	T2 struct{ f [10]T2 }      // T2 contains T2 as component of an array
+	T3 struct{ T4 }            // T3 contains T3 as component of an array in struct T4
+	T4 struct{ f [10]T3 }      // T4 contains T4 as component of struct T3 in an array
+)
+
+// valid struct types
+type (
+	T5 struct{ f *T5 }         // T5 contains T5 as component of a pointer
+	T6 struct{ f func() T6 }   // T6 contains T6 as component of a function type
+	T7 struct{ f [10][]T7 }    // T7 contains T7 as component of a slice in an array
+)
+```
+
 ### Map types
 
-A _map_ is an unordered group of elements of one type, called the element type, indexed by a set of unique keys of another type, called the key type. The value of an uninitialized map is `nil`.
+A _map_ is an unordered group of elements of one type, called the element type, indexed by a set of unique _keys_ of another type, called the key type. The value of an uninitialized map is `nil`.
 
 ```go
-map[KeyT]ElemT
+MapType     = "map" "[" KeyType "]" ElementType .
+KeyType     = Type .
 ```
 
 The comparison operators `==` and `!=` must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.
 
 ```go
 map[string]int
-map[*T]string
+map[*T]struct{ x, y float64 }
 map[string]any
 ```
 
@@ -414,13 +719,17 @@ make(map[string]int, 100)
 
 The initial capacity does not bound its size: maps grow to accommodate the number of items stored in them, with the exception of nil maps. A nil map is equivalent to an empty map except that no elements may be added.
 
-
 ### Function types
 
 A _function_ type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is `nil`.
 
 ```go
-func(parameters) results
+FunctionType   = "func" Signature .
+Signature      = Parameters [ Result ] .
+Result         = Parameters | Type .
+Parameters     = "(" [ ParameterList [ "," ] ] ")" .
+ParameterList  = ParameterDecl { "," ParameterDecl } .
+ParameterDecl  = [ IdentifierList ] [ "..." ] Type .
 ```
 
 Within a list of parameters or results, the names (IdentifierList) must either all be present or all be absent. If present, each name stands for one item (parameter or result) of the specified type and all non-[blank]() names in the signature must be [unique](). If absent, each type stands for one item of that type. Parameter and result lists are always parenthesized except that if there is exactly one unnamed result it may be written as an unparenthesized type.
@@ -440,175 +749,561 @@ func(n int) func(p *T)
 
 ### Interface types
 
-#### Built-in interfaces
-
-TODO:
+An interface type specifies a [method set]() called its _interface_. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to _implement the interface_. The value of an uninitialized variable of interface type is `nil`.
 
 ```go
-error
-any
+InterfaceType      = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" .
+MethodSpec         = MethodName Signature .
+MethodName         = identifier .
+InterfaceTypeName  = TypeName .
 ```
 
-### Classes
-
-TODO (classfile)
+An interface type may specify methods _explicitly_ through method specifications, or it may _embed_ methods of other interfaces through interface type names.
 
+```go
+// A simple File interface.
+interface {
+	Read([]byte) (int, error)
+	Write([]byte) (int, error)
+	Close() error
+}
+```
 
-## Expressions
+The name of each explicitly specified method must be [unique]() and not [blank]().
 
-### Commands and calls
+```go
+interface {
+	String() string
+	String() string  // illegal: String not unique
+	_(x int)         // illegal: method must have non-blank name
+}
+```
 
-TODO
+More than one type may implement an interface. For instance, if two types `S1` and `S2` have the method set
 
 ```go
-echo "Hello world"
-echo("Hello world")
+func Read(p []byte) (n int, err error)
+func Write(p []byte) (n int, err error)
+func Close() error
 ```
 
-### Built-in functions
-
-TODO
+(where `T` stands for either `S1` or `S2`) then the `File` interface is implemented by both `S1` and `S2`, regardless of what other methods `S1` and `S2` may have or share.
 
-### Operators
+A type implements any interface comprising any subset of its methods and may therefore implement several distinct interfaces. For instance, all types implement the _empty interface_:
 
-Operators combine operands into expressions.
+```go
+interface{}
+```
 
-Binary operators:
+Similarly, consider this interface specification, which appears within a [type declaration](#type-declarations) to define an interface called `Locker`:
 
 ```go
-|| && == != < <= > >=
-+ - * / %
-| & ^ &^ << >>
+type Locker interface {
+	Lock()
+	Unlock()
+}
 ```
 
-Unary operators:
+If `S1` and `S2` also implement
 
 ```go
-+ - ! ^ * &
+func Lock() { … }
+func Unlock() { … }
 ```
 
-#### Operator precedence
+they implement the `Locker` interface as well as the `File` interface.
 
-_Unary operators_ have the highest precedence. As the ++ and -- operators form statements, not expressions, they fall outside the operator hierarchy. As a consequence, statement *p++ is the same as (*p)++.
+An interface `T` may use a (possibly qualified) interface type name `E` in place of a method specification. This is called _embedding_ interface `E` in `T`. The method set of `T` is the union of the method sets of T’s explicitly declared methods and of T’s embedded interfaces.
 
-There are five precedence levels for _binary operators_. Multiplication operators bind strongest, followed by addition operators, comparison operators, && (logical AND), and finally || (logical OR):
+```go
+type Reader interface {
+	Read(p []byte) (n int, err error)
+	Close() error
+}
 
-```
-Precedence    Operator
-    5             *  /  %  <<  >>  &  &^
-    4             +  -  |  ^
-    3             ==  !=  <  <=  >  >=
-    2             &&
-    1             ||
+type Writer interface {
+	Write(p []byte) (n int, err error)
+	Close() error
+}
+
+// ReadWriter's methods are Read, Write, and Close.
+type ReadWriter interface {
+	Reader  // includes methods of Reader in ReadWriter's method set
+	Writer  // includes methods of Writer in ReadWriter's method set
+}
 ```
 
-Binary operators of the same precedence associate from left to right. For instance, `x / y * z` is the same as `(x / y) * z`.
+A union of method sets contains the (exported and non-exported) methods of each method set exactly once, and methods with the [same]() names must have [identical]() signatures.
 
 ```go
-+x                         // x
-42 + a - b                 // (42 + a) - b
-23 + 3*x[i]                // 23 + (3 * x[i])
-x <= f()                   // x <= f()
-^a >> b                    // (^a) >> b
-f() || g()                 // f() || g()
-x == y+1 && <-chanInt > 0  // (x == (y+1)) && ((<-chanInt) > 0)
+type ReadCloser interface {
+	Reader   // includes methods of Reader in ReadCloser's method set
+	Close()  // illegal: signatures of Reader.Close and Close are different
+}
 ```
 
-#### Arithmetic operators
+An interface type `T` may not embed itself or any interface type that embeds `T`, recursively.
 
-_Arithmetic operators_ apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to [integer](), [floating-point](), [rational]() and [complex]() types; + also applies to [strings](). The bitwise logical and shift operators apply to integers only.
+```go
+// illegal: Bad cannot embed itself
+type Bad interface {
+	Bad
+}
 
+// illegal: Bad1 cannot embed itself using Bad2
+type Bad1 interface {
+	Bad2
+}
+type Bad2 interface {
+	Bad1
+}
 ```
-+    sum                    integers (including bigint), floats, bigrat, complex values, strings
--    difference             integers (including bigint), floats, bigrat, complex values
-*    product                integers (including bigint), floats, bigrat, complex values
-/    quotient               integers (including bigint), floats, bigrat, complex values
-%    remainder              integers (including bigint)
 
-&    bitwise AND            integers (including bigint)
-|    bitwise OR             integers (including bigint)
-^    bitwise XOR            integers (including bigint)
-&^   bit clear (AND NOT)    integers (including bigint)
+#### Built-in interfaces
 
-<<   left shift             integer << integer >= 0
->>   right shift            integer >> integer >= 0
+TODO:
+
+```go
+error
+any
 ```
 
-TODO
+#### Errors
 
-#### Comparison operators
+The predeclared type `error` is defined as
 
-_Comparison operators_ compare two operands and yield an untyped boolean value.
+```go
+type error interface {
+	Error() string
+}
+```
+
+It is the conventional interface for representing an `error` condition, with the nil value representing no error. For instance, a function to read data from a file might be defined:
 
 ```go
-==    equal
-!=    not equal
-<     less
-<=    less or equal
->     greater
->=    greater or equal
+func Read(f *File, b []byte) (n int, err error)
 ```
 
-In any comparison, the first operand must be [assignable]() to the type of the second operand, or vice versa.
+### Classes
 
-The equality operators == and != apply to operands of comparable types. The ordering operators <, <=, >, and >= apply to operands of ordered types. These terms and the result of the comparisons are defined as follows:
+TODO (classfile)
 
-* Boolean types are comparable. Two boolean values are equal if they are either both true or both false.
-* Integer types are comparable and ordered. Two integer values are compared in the usual way.
-* Floating-point types are comparable and ordered. Two floating-point values are compared as defined by the IEEE-754 standard.
-* Complex types are comparable. Two complex values u and v are equal if both real(u) == real(v) and imag(u) == imag(v).
-* String types are comparable and ordered. Two string values are compared lexically byte-wise.
-* Pointer types are comparable. Two pointer values are equal if they point to the same variable or if both have value `nil`. Pointers to distinct [zero-size]() variables may or may not be equal.
-* Interface types are comparable. Two interface values are equal if they have [identical]() dynamic types and equal dynamic values or if both have value `nil`.
-* A value x of non-interface type X and a value t of interface type T can be compared if type X is comparable and X [implements]() T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.
-* Array types are comparable if their array element types are comparable. Two array values are equal if their corresponding element values are equal. The elements are compared in ascending index order, and comparison stops as soon as two element values differ (or all elements have been compared).
 
-A comparison of two interface values with identical dynamic types causes a [run-time panic]() if that type is not comparable. This behavior applies not only to direct interface value comparisons but also when comparing arrays of interface values or structs with interface-valued fields.
+## Properties of types and values
 
-Slice, map, and function types are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier `nil`. Comparison of pointer, channel, and interface values to `nil` is also allowed and follows from the general rules above.
+### Underlying types
 
-#### Logical operators
+Each type T has an _underlying type_: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its declaration.
 
-Logical operators apply to [boolean]() values and yield a result of the same type as the operands. The left operand is evaluated, and then the right if the condition requires it.
+```go
+type (
+	A1 = string
+	A2 = A1
+)
 
-```
-&&    conditional AND    p && q  is  "if p then q else false"
-||    conditional OR     p || q  is  "if p then true else q"
-!     NOT                !p      is  "not p"
+type (
+	B1 string
+	B2 B1
+	B3 []B1
+	B4 B3
+)
 ```
 
-#### Address operators
+The underlying type of `string`, `A1`, `A2`, `B1`, and `B2` is `string`. The underlying type of `[]B1`, `B3`, and `B4` is `[]B1`.
 
-For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) [composite literal](). If the evaluation of x would cause a [run-time panic](), then the evaluation of &x does too.
+### Type identity
 
-For an operand x of pointer type *T, the pointer indirection *x denotes the [variable]() of type T pointed to by x. If x is nil, an attempt to evaluate *x will cause a [run-time panic]().
+Two types are either _identical_ or _different_.
 
-```go
-&x
-&a[f(2)]
-&Point{2, 3}
-*p
-*pf(x)
+A [named type](#types) is always different from any other type. Otherwise, two types are identical if their [underlying type](#underlying-types) literals are structurally equivalent; that is, they have the same literal structure and corresponding components have identical types. In detail:
 
-var x *int = nil
-*x   // causes a run-time panic
-&*x  // causes a run-time panic
-```
+* Two array types are identical if they have identical element types and the same array length.
+* Two slice types are identical if they have identical element types.
+* Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. [Non-exported](#exported-identifiers) field names from different packages are always different.
+* Two pointer types are identical if they have identical base types.
+* Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is. Parameter and result names are not required to match.
+* Two interface types are identical if they define the same type set.
+* Two map types are identical if they have identical key and element types.
 
-#### Conversions
+Given the declarations
 
-A _conversion_ changes the [type](#types) of an expression to the type specified by the conversion. A conversion may appear literally in the source, or it may be _implied_ by the context in which an expression appears.
+```go
+type (
+	A0 = []string
+	A1 = A0
+	A2 = struct{ a, b int }
+	A3 = int
+	A4 = func(A3, float64) *A0
+	A5 = func(x int, _ float64) *[]string
 
-An _explicit conversion_ is an expression of the form `T(x)` where `T` is a type and `x` is an expression that can be converted to type `T`.
+	B0 A0
+	B1 []string
+	B2 struct{ a, b int }
+	B3 struct{ a, c int }
+	B4 func(int, float64) *B0
+	B5 func(x int, y float64) *A1
 
-```go
-T(x)
+	C0 = B0
+)
 ```
 
-If the type starts with the operator * or <-, or if the type starts with the keyword func and has no result list, it must be parenthesized when necessary to avoid ambiguity:
+these types are identical:
 
 ```go
-*Point(p)        // same as *(Point(p))
+A0, A1, and []string
+A2 and struct{ a, b int }
+A3 and int
+A4, func(int, float64) *[]string, and A5
+
+B0 and C0
+[]int and []int
+struct{ a, b *B5 } and struct{ a, b *B5 }
+func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5
+```
+
+`B0` and `B1` are different because they are new types created by distinct [type definitions](#type-definitions); `func(int, float64) *B0` and `func(x int, y float64) *[]string` are different because `B0` is different from `[]string`.
+
+### Assignability
+
+A value x of type V is _assignable_ to a [variable](#variables) of type T ("x is assignable to T") if one of the following conditions applies:
+
+* V and T are identical.
+* V and T have identical [underlying types](#underlying-types) but are not type parameters and at least one of V or T is not a [named type](#types).
+* T is an interface type, and x [implements]() T.
+* x is the predeclared identifier nil and T is a pointer, function, slice, map, or interface type.
+* x is an untyped [constant](#constants) [representable](#representability) by a value of type T.
+
+### Representability
+
+A [constant](#constants) x is _representable_ by a value of type T if one of the following conditions applies:
+
+* x is in the set of values [determined](#types) by T.
+* T is a floating-point type and x can be rounded to T's precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity.
+* T is a complex type, and x's [components](#manipulating-complex-numbers) `real(x)` and `imag(x)` are representable by values of T's component type (`float32` or `float64`).
+
+```go
+x                   T           x is representable by a value of T because
+
+'a'                 byte        97 is in the set of byte values
+97                  rune        rune is an alias for int32, and 97 is in the set of 32-bit integers
+"foo"               string      "foo" is in the set of string values
+1024                int16       1024 is in the set of 16-bit integers
+42.0                byte        42 is in the set of unsigned 8-bit integers
+1e10                uint64      10000000000 is in the set of unsigned 64-bit integers
+2.718281828459045   float32     2.718281828459045 rounds to 2.7182817 which is in the set of float32 values
+-1e-1000            float64     -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0
+0i                  int         0 is an integer value
+(42 + 0i)           float32     42.0 (with zero imaginary part) is in the set of float32 values
+```
+
+```go
+x                   T           x is not representable by a value of T because
+
+0                   bool        0 is not in the set of boolean values
+'a'                 string      'a' is a rune, it is not in the set of string values
+1024                byte        1024 is not in the set of unsigned 8-bit integers
+-1                  uint16      -1 is not in the set of unsigned 16-bit integers
+1.1                 int         1.1 is not an integer value
+42i                 float32     (0 + 42i) is not in the set of float32 values
+1e1000              float64     1e1000 overflows to IEEE +Inf after rounding
+```
+
+### Method sets
+
+The _method set_ of a type determines the methods that can be [called](#commands-and-calls) on an [operand]() of that type. Every type has a (possibly empty) method set associated with it:
+
+* The method set of a [defined type](#type-definitions) T consists of all [methods]() declared with receiver type T.
+* The method set of a pointer to a defined type T (where T is neither a pointer nor an interface) is the set of all methods declared with receiver *T or T.
+* The method set of an [interface type](#interface-types) is the intersection of the method sets of each type in the interface's [type set](#interface-types) (the resulting method set is usually just the set of declared methods in the interface).
+
+Further rules apply to structs (and pointer to structs) containing embedded fields, as described in the section on [struct types](#struct-types). Any other type has an empty method set.
+
+In a method set, each method must have a [unique](#uniqueness-of-identifiers) non-[blank](#blank-identifier) [method name]().
+
+
+## Expressions
+
+An expression specifies the computation of a value by applying operators and functions to operands.
+
+### Operands
+
+Operands denote the elementary values in an expression. An operand may be a literal, a (possibly [qualified]()) non-[blank](#blank-identifier) identifier denoting a [constant](#constant-declarations), [variable](#variable-declarations), or [function](#function-declarations), or a parenthesized expression.
+
+```go
+Operand     = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
+Literal     = BasicLit | CompositeLit | FunctionLit .
+BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
+OperandName = identifier | QualifiedIdent .
+```
+
+The [blank identifier](#blank-identifier) may appear as an operand only on the left-hand side of an [assignment statement](#assignment-statements).
+
+### Qualified identifiers
+
+A _qualified identifier_ is an identifier qualified with a package name prefix. Both the package name and the identifier must not be [blank](#blank-identifier).
+
+```go
+QualifiedIdent = PackageName "." identifier .
+```
+
+A qualified identifier accesses an identifier in a different package, which must be [imported](#import-declarations). The identifier must be [exported](#exported-identifiers) and declared in the [package block](#blocks) of that package.
+
+```go
+math.Sin // denotes the Sin function in package math
+```
+
+### Composite literals
+
+Composite literals construct new composite values each time they are evaluated. They consist of the type of the literal followed by a brace-bound list of elements. Each element may optionally be preceded by a corresponding key.
+
+```go
+CompositeLit  = LiteralType LiteralValue .
+LiteralType   = TypeName [ TypeArgs ] .
+LiteralValue  = "{" [ ElementList [ "," ] ] "}" .
+ElementList   = KeyedElement { "," KeyedElement } .
+KeyedElement  = [ Key ":" ] Element .
+Key           = FieldName | Expression | LiteralValue .
+FieldName     = identifier .
+Element       = Expression | LiteralValue .
+```
+
+The LiteralType's [underlying type](#underlying-types) `T` must be a [struct](#struct-types) or a [classfile]() type. The types of the elements and keys must be [assignable](#assignability) to the respective field; there is no additional conversion. It is an error to specify multiple elements with the same field name.
+
+* A key must be a field name declared in the struct type.
+* An element list that does not contain any keys must list an element for each struct field in the order in which the fields are declared.
+* If any element has a key, every element must have a key.
+* An element list that contains keys does not need to have an element for each struct field. Omitted fields get the zero value for that field.
+* A literal may omit the element list; such a literal evaluates to the zero value for its type.
+* It is an error to specify an element for a non-exported field of a struct belonging to a different package.
+
+Given the declarations
+
+```go
+type Point3D struct { x, y, z float64 }
+type Line struct { p, q Point3D }
+```
+
+one may write
+
+```go
+origin := Point3D{}                            // zero value for Point3D
+line := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x
+```
+
+[Taking the address](#address-operators) of a composite literal generates a pointer to a unique [variable](#variables) initialized with the literal's value.
+
+```go
+var pointer *Point3D = &Point3D{y: 1000}
+```
+
+A parsing ambiguity arises when a composite literal using the TypeName form of the LiteralType appears as an operand between the [keyword](#keywords) and the opening brace of the block of an "if", "for", or "switch" statement, and the composite literal is not enclosed in parentheses, square brackets, or curly braces. In this rare case, the opening brace of the literal is erroneously parsed as the one introducing the block of statements. To resolve the ambiguity, the composite literal must appear within parentheses.
+
+```go
+if x == (T{a,b,c}[i]) { … }
+if (x == T{a,b,c}[i]) { … }
+```
+
+### Function literals
+
+A function literal represents an anonymous [function](#function-declarations).
+
+```go
+FunctionLit                 = [ AnonymousParameters ] "=>" AnonymousFunctionBody .
+AnonymousParameters         = SingleAnonymousParameter | MultipleAnonymousParameters .
+SingleAnonymousParameter    = identifier .
+MultipleAnonymousParameters = "(" AnonymousParameterList ")" .
+AnonymousParameterList      = identifier { "," identifier } .
+AnonymousFunctionBody       = Expression | FunctionBody .
+```
+
+A anonymous function body can be a single expression.
+
+```go
+x => math.Sin(x)
+(x, y) => x*x + y*y
+```
+
+It can also be the same as a normal function body. The above examples are equivalent to
+
+```go
+x => {
+	return math.Sin(x)
+}
+
+(x, y) => {
+	return x*x + y*y
+}
+```
+
+Anonymous functions are typically passed in as parameters for function calls.
+
+```go
+import "sort"
+
+a := [32, 100, 50, 6, 92]
+sort.Slice a, (i, j) => {
+	return a[i] < b[j]
+}
+```
+
+Note: Both the parameter types and return value types of anonymous functions are omitted. Go+ determines the parameter types and return value types of anonymous functions through type inference.
+
+Function literals are _closures_: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.
+
+
+### Commands and calls
+
+TODO
+
+```go
+echo "Hello world"
+echo("Hello world")
+```
+
+### Built-in functions
+
+TODO
+
+### Operators
+
+Operators combine operands into expressions.
+
+Binary operators:
+
+```go
+|| && == != < <= > >=
++ - * / %
+| & ^ &^ << >>
+```
+
+Unary operators:
+
+```go
++ - ! ^ * &
+```
+
+#### Operator precedence
+
+_Unary operators_ have the highest precedence. As the ++ and -- operators form statements, not expressions, they fall outside the operator hierarchy. As a consequence, statement *p++ is the same as (*p)++.
+
+There are five precedence levels for _binary operators_. Multiplication operators bind strongest, followed by addition operators, comparison operators, && (logical AND), and finally || (logical OR):
+
+```
+Precedence    Operator
+    5             *  /  %  <<  >>  &  &^
+    4             +  -  |  ^
+    3             ==  !=  <  <=  >  >=
+    2             &&
+    1             ||
+```
+
+Binary operators of the same precedence associate from left to right. For instance, `x / y * z` is the same as `(x / y) * z`.
+
+```go
++x                         // x
+42 + a - b                 // (42 + a) - b
+23 + 3*x[i]                // 23 + (3 * x[i])
+x <= f()                   // x <= f()
+^a >> b                    // (^a) >> b
+f() || g()                 // f() || g()
+x == y+1 && <-chanInt > 0  // (x == (y+1)) && ((<-chanInt) > 0)
+```
+
+#### Arithmetic operators
+
+_Arithmetic operators_ apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to [integer](), [floating-point](), [rational]() and [complex]() types; + also applies to [strings](). The bitwise logical and shift operators apply to integers only.
+
+```
++    sum                    integers (including bigint), floats, bigrat, complex values, strings
+-    difference             integers (including bigint), floats, bigrat, complex values
+*    product                integers (including bigint), floats, bigrat, complex values
+/    quotient               integers (including bigint), floats, bigrat, complex values
+%    remainder              integers (including bigint)
+
+&    bitwise AND            integers (including bigint)
+|    bitwise OR             integers (including bigint)
+^    bitwise XOR            integers (including bigint)
+&^   bit clear (AND NOT)    integers (including bigint)
+
+<<   left shift             integer << integer >= 0
+>>   right shift            integer >> integer >= 0
+```
+
+TODO
+
+#### Comparison operators
+
+_Comparison operators_ compare two operands and yield an untyped boolean value.
+
+```go
+==    equal
+!=    not equal
+<     less
+<=    less or equal
+>     greater
+>=    greater or equal
+```
+
+In any comparison, the first operand must be [assignable]() to the type of the second operand, or vice versa.
+
+The equality operators == and != apply to operands of comparable types. The ordering operators <, <=, >, and >= apply to operands of ordered types. These terms and the result of the comparisons are defined as follows:
+
+* Boolean types are comparable. Two boolean values are equal if they are either both true or both false.
+* Integer types are comparable and ordered. Two integer values are compared in the usual way.
+* Floating-point types are comparable and ordered. Two floating-point values are compared as defined by the IEEE-754 standard.
+* Complex types are comparable. Two complex values u and v are equal if both real(u) == real(v) and imag(u) == imag(v).
+* String types are comparable and ordered. Two string values are compared lexically byte-wise.
+* Pointer types are comparable. Two pointer values are equal if they point to the same variable or if both have value `nil`. Pointers to distinct [zero-size]() variables may or may not be equal.
+* Interface types are comparable. Two interface values are equal if they have [identical]() dynamic types and equal dynamic values or if both have value `nil`.
+* A value x of non-interface type X and a value t of interface type T can be compared if type X is comparable and X [implements]() T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.
+* Array types are comparable if their array element types are comparable. Two array values are equal if their corresponding element values are equal. The elements are compared in ascending index order, and comparison stops as soon as two element values differ (or all elements have been compared).
+
+A comparison of two interface values with identical dynamic types causes a [run-time panic]() if that type is not comparable. This behavior applies not only to direct interface value comparisons but also when comparing arrays of interface values or structs with interface-valued fields.
+
+Slice, map, and function types are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier `nil`. Comparison of pointer, and interface values to `nil` is also allowed and follows from the general rules above.
+
+#### Logical operators
+
+Logical operators apply to [boolean]() values and yield a result of the same type as the operands. The left operand is evaluated, and then the right if the condition requires it.
+
+```
+&&    conditional AND    p && q  is  "if p then q else false"
+||    conditional OR     p || q  is  "if p then true else q"
+!     NOT                !p      is  "not p"
+```
+
+#### Address operators
+
+For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) [composite literal](). If the evaluation of x would cause a [run-time panic](), then the evaluation of &x does too.
+
+For an operand x of pointer type *T, the pointer indirection *x denotes the [variable]() of type T pointed to by x. If x is nil, an attempt to evaluate *x will cause a [run-time panic]().
+
+```go
+&x
+&a[f(2)]
+&Point{2, 3}
+*p
+*pf(x)
+
+var x *int = nil
+*x   // causes a run-time panic
+&*x  // causes a run-time panic
+```
+
+#### Conversions
+
+A _conversion_ changes the [type](#types) of an expression to the type specified by the conversion. A conversion may appear literally in the source, or it may be _implied_ by the context in which an expression appears.
+
+An _explicit conversion_ is an expression of the form `T(x)` where `T` is a type and `x` is an expression that can be converted to type `T`.
+
+```go
+T(x)
+```
+
+If the type starts with the operator * or <-, or if the type starts with the keyword func and has no result list, it must be parenthesized when necessary to avoid ambiguity:
+
+```go
+*Point(p)        // same as *(Point(p))
 (*Point)(p)      // p is converted to *Point
 func()(x)        // function signature func() x
 (func())(x)      // x is converted to func()
@@ -728,17 +1423,15 @@ int8(^1)   // same as int8(-2)
 A short variable declaration uses the syntax:
 
 ```go
-varName[, ...] = expression[, ...]
+ShortVarDecl = IdentifierList ":=" ExpressionList .
 ```
 
-It is shorthand for a regular [variable declaration]() with initializer expressions but no types:
+It is shorthand for a regular [variable declaration](#variable-declarations) with initializer expressions but no types:
 
 ```go
-var varName[, ...] = expression[, ...]
+"var" IdentifierList "=" ExpressionList .
 ```
 
-For example:
-
 ```go
 i, j := 0, 10
 f := func() int { return 7 }
@@ -820,22 +1513,59 @@ var a map[string]any = {"Monday": 1, "Sunday": 7}
 echo a
 ```
 
-## Statements
+### Order of evaluation
 
-Statements control execution.
+At package level, [initialization dependencies]() determine the evaluation order of individual initialization expressions in [variable declarations](). Otherwise, when evaluating the [operands]() of an expression, assignment, or [return statement](#return-statements), all function calls, method calls, [receive operations](), and [binary logical operations](#logical-operators) are evaluated in lexical left-to-right order.
 
-```go
-Statement =
-	Declaration | SimpleStmt | IfStmt | ForStmt | SwitchStmt |
-    LabeledStmt | BreakStmt | ContinueStmt | FallthroughStmt | GotoStmt |
-	ReturnStmt | DeferStmt | Block .
+For example, in the (function-local) assignment
 
-SimpleStmt = EmptyStmt | ExpressionStmt | IncDecStmt | Assignment | ShortVarDecl .
+```go
+y[f()], ok = g(z || h(), i()+x[j()], <-c), k()
 ```
 
-### Empty statements
-
-The empty statement does nothing.
+the function calls and communication happen in the order `f()`, `h()` (if z evaluates to false), `i()`, `j()`, `<-c`, `g()`, and `k()`. However, the order of those events compared to the evaluation and indexing of `x` and the evaluation of `y` and `z` is not specified, except as required lexically. For instance, `g` cannot be called before its arguments are evaluated.
+
+```go
+a := 1
+f := func() int { a++; return a }
+x := [a, f()]      // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
+m := {a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
+n := {a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified
+```
+
+At package level, initialization dependencies override the left-to-right rule for individual initialization expressions, but not for operands within each expression:
+
+```go
+var a, b, c = f() + v(), g(), sqr(u()) + v()
+
+func f() int        { return c }
+func g() int        { return a }
+func sqr(x int) int { return x*x }
+
+// functions u and v are independent of all other variables and functions
+```
+
+The function calls happen in the order `u()`, `sqr()`, `v()`, `f()`, `v()`, and `g()`.
+
+Floating-point operations within a single expression are evaluated according to the associativity of the operators. Explicit parentheses affect the evaluation by overriding the default associativity. In the expression `x + (y + z)` the addition `y + z` is performed before adding `x`.
+
+
+## Statements
+
+Statements control execution.
+
+```go
+Statement =
+	Declaration | SimpleStmt | IfStmt | ForStmt | SwitchStmt |
+    LabeledStmt | BreakStmt | ContinueStmt | FallthroughStmt | GotoStmt |
+	ReturnStmt | DeferStmt | Block .
+
+SimpleStmt = EmptyStmt | ExpressionStmt | IncDecStmt | Assignment | ShortVarDecl .
+```
+
+### Empty statements
+
+The empty statement does nothing.
 
 ```go
 EmptyStmt = .
@@ -856,8 +1586,6 @@ append cap complex imag len make new real
 unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
 ```
 
-For example:
-
 ```go
 h(x+y)
 f.Close()
@@ -913,7 +1641,7 @@ a[i] <<= 2
 i &^= 1<<n
 ```
 
-A tuple assignment assigns the individual elements of a multi-valued operation to a list of variables. There are two forms. In the first, the right hand operand is a single multi-valued expression such as a function call, a channel or [map](#map-types) operation, or a [type assertion](). The number of operands on the left hand side must match the number of values. For instance, if f is a function returning two values,
+A tuple assignment assigns the individual elements of a multi-valued operation to a list of variables. There are two forms. In the first, the right hand operand is a single multi-valued expression such as a function call or [map](#map-types) operation, or a [type assertion](). The number of operands on the left hand side must match the number of values. For instance, if f is a function returning two values,
 
 ```go
 x, y = f()
@@ -971,8 +1699,6 @@ In assignments, each value must be [assignable]() to the type of the operand to
 IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
 ```
 
-For example:
-
 ```go
 if x > 1 {
 	x = 1
@@ -1020,8 +1746,6 @@ InitStmt = SimpleStmt .
 PostStmt = SimpleStmt .
 ```
 
-For example:
-
 ```go
 for i := 0; i < 10; i++ {
 	f(i)
@@ -1201,8 +1925,6 @@ LabeledStmt = Label ":" Statement .
 Label       = identifier .
 ```
 
-For example:
-
 ```go
 Error:
 	log.Panic("error encountered")
@@ -1272,8 +1994,6 @@ A "goto" statement transfers control to the statement with the corresponding lab
 GotoStmt = "goto" Label .
 ```
 
-For example:
-
 ```go
 goto Error
 ```
@@ -1351,11 +2071,6 @@ func complexF3() (re float64, im float64) {
 	im = 4.0
 	return
 }
-
-func (devnull) Write(p []byte) (n int, _ error) {
-	n = len(p)
-	return
-}
 ```
 
 Regardless of how they are declared, all the result values are initialized to the [zero values]() for their type upon entry to the function. A "return" statement that specifies results sets the result parameters before any deferred functions are executed.
@@ -1414,10 +2129,10 @@ The built-in functions do not have standard Go types, so they can only appear in
 
 The built-in functions `append` and `copy` assist in common slice operations. For both functions, the result is independent of whether the memory referenced by the arguments overlaps.
 
-The [variadic](#function-types) function append appends zero or more values x to a slice s and returns the resulting slice of the same type as s. The [core type]() of s must be a slice of type `[]E`. The values x are passed to a parameter of type `...E` and the respective [parameter passing rules]() apply. As a special case, if the core type of s is []byte, append also accepts a second argument with core type [bytestring]() followed by `...`. This form appends the bytes of the byte slice or string.
+The [variadic](#function-types) function append appends zero or more values x to a slice s and returns the resulting slice of the same type as s. The [underlying type](#underlying-types) of s must be a slice of type `[]E`. The values x are passed to a parameter of type `...E` and the respective [parameter passing rules]() apply. As a special case, if the underlying type of s is []byte, append also accepts a second argument with underlying type [bytestring]() followed by `...`. This form appends the bytes of the byte slice or string.
 
 ```go
-append(s S, x ...E) S  // core type of S is []E
+append(s S, x ...E) S  // underlying type of S is []E
 ```
 
 If the capacity of s is not large enough to fit the additional values, append [allocates]() a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
@@ -1436,7 +2151,7 @@ var b []byte
 b = append(b, "bar"...)            // append string contents      b is []byte("bar")
 ```
 
-The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. The [core types]() of both arguments must be slices with [identical]() element type. The number of elements copied is the minimum of `len(src)` and `len(dst)`. As a special case, if the destination's core type is `[]byte`, copy also accepts a source argument with core type [bytestring](). This form copies the bytes from the byte slice or string into the byte slice.
+The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. The [underlying types](#underlying-types) of both arguments must be slices with [identical]() element type. The number of elements copied is the minimum of `len(src)` and `len(dst)`. As a special case, if the destination's underlying type is `[]byte`, copy also accepts a source argument with underlying type [bytestring](). This form copies the bytes from the byte slice or string into the byte slice.
 
 ```go
 copy(dst, src []T) int
@@ -1472,4 +2187,838 @@ If the map or slice is `nil`, `clear` is a no-op.
 
 ### Manipulating complex numbers
 
+Three functions assemble and disassemble complex numbers. The built-in function complex constructs a complex value from a floating-point real and imaginary part, while real and imag extract the real and imaginary parts of a complex value.
+
+```go
+complex(realPart, imaginaryPart floatT) complexT
+real(complexT) floatT
+imag(complexT) floatT
+```
+
+The type of the arguments and return value correspond. For `complex`, the two arguments must be of the same [floating-point type](#numeric-types) and the return type is the [complex type](#numeric-types) with the corresponding floating-point constituents: `complex64` for `float32` arguments, and `complex128` for `float64` arguments. If one of the arguments evaluates to an untyped constant, it is first implicitly [converted](#conversions) to the type of the other argument. If both arguments evaluate to untyped constants, they must be non-complex numbers or their imaginary parts must be zero, and the return value of the function is an untyped complex constant.
+
+For `real` and `imag`, the argument must be of complex type, and the return type is the corresponding floating-point type: `float32` for a `complex64` argument, and float64 for a complex128 argument. If the argument evaluates to an untyped constant, it must be a number, and the return value of the function is an untyped floating-point constant.
+
+The `real` and `imag` functions together form the inverse of `complex`, so for a value `z` of a complex type `Z`, `z == Z(complex(real(z), imag(z)))`.
+
+If the operands of these functions are all constants, the return value is a constant.
+
+```go
+var a = complex(2, -2)             // complex128
+const b = complex(1.0, -1.4)       // untyped complex constant 1 - 1.4i
+x := float32(math.Cos(math.Pi/2))  // float32
+var c64 = complex(5, -x)           // complex64
+var s int = complex(1, 0)          // untyped complex constant 1 + 0i can be converted to int
+_ = complex(1, 2<<s)               // illegal: 2 assumes floating-point type, cannot shift
+var rl = real(c64)                 // float32
+var im = imag(a)                   // float64
+const c = imag(b)                  // untyped constant -1.4
+_ = imag(3 << s)                   // illegal: 3 assumes complex type, cannot shift
+```
+
+### Deletion of map elements
+
+The built-in function `delete` removes the element with key `k` from a [map](#map-types) `m`. The value `k` must be [assignable]() to the key type of `m`.
+
+```go
+delete(m, k)  // remove element m[k] from map m
+```
+
+If the map m is nil or the element m[k] does not exist, delete is a no-op.
+
+### Length and capacity
+
+The built-in functions `len` and `cap` take arguments of various types and return a result of type `int`. The implementation guarantees that the result always fits into an `int`.
+
+```go
+Call      Argument type    Result
+
+len(s)    string type      string length in bytes
+          [n]T, *[n]T      array length (== n)
+          []T              slice length
+          map[K]T          map length (number of defined keys)
+          type parameter   see below
+
+cap(s)    [n]T, *[n]T      array length (== n)
+          []T              slice capacity
+          type parameter   see below
+```
+
+The capacity of a slice is the number of elements for which there is space allocated in the underlying array. At any time the following relationship holds:
+
+```go
+0 <= len(s) <= cap(s)
+```
+
+The length of a `nil` slice, map is `0`. The capacity of a `nil` slice is `0`.
+
+The expression `len(s)` is [constant](#constants) if s is a string constant. The expressions `len(s)` and `cap(s)` are constants if the type of `s` is an array or pointer to an array and the expression `s` does not contain (non-constant) [function calls](#commands-and-calls); in this case `s` is not evaluated. Otherwise, invocations of `len` and `cap` are not constant and `s` is evaluated.
+
+```go
+const (
+	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
+	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
+	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
+	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
+	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
+)
+var z complex128
+```
+
+Making slices and maps
+
+The built-in function `make` takes a type `T`, optionally followed by a type-specific list of expressions. The [underlying type]() of `T` must be a slice or map. It returns a value of type `T` (not `*T`). The memory is initialized as described in the section on [initial values]().
+
+```go
+Call             Underlying type    Result
+
+make(T, n)       slice        slice of type T with length n and capacity n
+make(T, n, m)    slice        slice of type T with length n and capacity m
+
+make(T)          map          map of type T
+make(T, n)       map          map of type T with initial space for approximately n elements
+```
+
+Each of the size arguments `n` and `m` must be of [integer type](#numeric-types), have a [type set](#interface-types) containing only integer types, or be an untyped constant. A constant size argument must be non-negative and [representable]() by a value of type `int`; if it is an untyped constant it is given type `int`. If both `n` and `m` are provided and are constant, then `n` must be no larger than `m`. For slices, if `n` is negative or larger than `m` at run time, a [run-time panic]() occurs.
+
+```go
+s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
+s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
+s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
+s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
+m := make(map[string]int, 100)  // map with initial space for approximately 100 elements
+```
+
+Calling make with a map type and size hint `n` will create a map with initial space to hold `n` map elements. The precise behavior is implementation-dependent.
+
+
+### Allocation
+
+The built-in function `new` takes a type `T`, allocates storage for a [variable](#variables) of that type at run time, and returns a value of type `*T` [pointing](#pointer-types) to it. The variable is initialized as described in the section on [initial values]().
+
+```go
+new(T)
+```
+
+For instance
+
+```go
+new(int)
+```
+
+allocates storage for a variable of type `int`, initializes it `0`, and returns a value of type `*int` containing the address of the location.
+
+
+### Min and max
+
+The built-in functions `min` and `max` compute the smallest—or largest, respectively—value of a fixed number of arguments of [ordered types](). There must be at least one argument.
+
+The same type rules as for [operators](#operators) apply: for [ordered]() arguments `x` and `y`, `min(x, y)` is valid if `x + y` is valid, and the type of `min(x, y)` is the type of `x + y` (and similarly for `max`). If all arguments are constant, the result is constant.
+
+```go
+var x, y int
+m := min(x)                 // m == x
+m := min(x, y)              // m is the smaller of x and y
+m := max(x, y, 10)          // m is the larger of x and y but at least 10
+c := max(1, 2.0, 10)        // c == 10.0 (floating-point kind)
+f := max(0, float32(x))     // type of f is float32
+var s []string
+_ = min(s...)               // invalid: slice arguments are not permitted
+t := max("", "foo", "bar")  // t == "foo" (string kind)
+```
+
+For numeric arguments, assuming all `NaN`s are equal, `min` and `max` are commutative and associative:
+
+```
+min(x, y)    == min(y, x)
+min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
+```
+
+For floating-point arguments negative zero, `NaN`, and infinity the following rules apply:
+
+```go
+x        y    min(x, y)    max(x, y)
+
+-0.0    0.0         -0.0          0.0    // negative zero is smaller than (non-negative) zero
+-Inf      y         -Inf            y    // negative infinity is smaller than any other number
++Inf      y            y         +Inf    // positive infinity is larger than any other number
+ NaN      y          NaN          NaN    // if any argument is a NaN, the result is a NaN
+```
+
+For string arguments the result for min is the first argument with the smallest (or for max, largest) value, compared lexically byte-wise:
+
+```go
+min(x, y)    == if x <= y then x else y
+min(x, y, z) == min(min(x, y), z)
+```
+
+### Handling panics
+
+Two built-in functions, `panic` and `recover`, assist in reporting and handling [run-time panics]() and program-defined error conditions.
+
+```go
+func panic(any)
+func recover() any
+```
+
+While executing a function `F`, an explicit call to `panic` or a [run-time panic]() terminates the execution of `F`. Any functions [deferred]() by `F` are then executed as usual. Next, any deferred functions run by `F`'s caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called _panicking_.
+
+```go
+panic(42)
+panic("unreachable")
+panic(Error("cannot parse"))
+```
+
+The `recover` function allows a program to manage behavior of a panicking goroutine. Suppose a function `G` defers a function `D` that calls `recover` and a panic occurs in a function on the same goroutine in which `G` is executing. When the running of deferred functions reaches `D`, the return value of `D`'s call to recover will be the value passed to the call of panic. If `D` returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between `G` and the call to panic is discarded, and normal execution resumes. Any functions deferred by `G` before `D` are then run and `G`'s execution terminates by returning to its caller.
+
+The return value of `recover` is `nil` when the goroutine is not panicking or `recover` was not called directly by a deferred function. Conversely, if a goroutine is panicking and recover was called directly by a deferred function, the return value of recover is guaranteed not to be `nil`. To ensure this, calling panic with a `nil` interface value (or an untyped nil) causes a [run-time panic]().
+
+The `protect` function in the example below invokes the function argument `g` and protects callers from run-time panics raised by `g`.
+
+```go
+func protect(g func()) {
+	defer func() {
+		log.Println("done")  // Println executes normally even if there is a panic
+		if x := recover(); x != nil {
+			log.Printf("run time panic: %v", x)
+		}
+	}()
+	log.Println("start")
+	g()
+}
+```
+
+### TODO
+
+```go
+print
+printf
+println
+...
+```
+
+## Blocks
+
+A _block_ is a possibly empty sequence of declarations and statements within matching brace brackets.
+
+```go
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+* The _universe block_ encompasses all Go+ source text.
+* Each [package](#packages) has a _package block_ containing all Go+ source text for that package.
+* Each file has a _file block_ containing all Go+ source text in that file.
+* Each "[if](#if-statements)", "[for](#for-statements)", and "[switch](#switch-statements)" statement is considered to be in its own implicit block.
+* Each clause in a "[switch](#switch-statements)" statement acts as an implicit block.
+
+Blocks nest and influence [scoping]().
+
+
+## Declarations and scope
+
+A _declaration_ binds a non-[blank]() identifier to a [constant](), [type](), [variable](), [function](), [label](), or [package](). Every identifier in a program must be declared. No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block.
+
+The [blank identifier]() may be used like any other identifier in a declaration, but it does not introduce a binding and thus is not declared. In the package block, the identifier `init` may only be used for [init function]() declarations, and like the blank identifier it does not introduce a new binding.
+
+```go
+Declaration   = ConstDecl | TypeDecl | VarDecl .
+TopLevelDecl  = Declaration | FunctionDecl .
+```
+
+The scope of a declared identifier is the extent of source text in which the identifier denotes the specified constant, type, variable, function, label, or package.
+
+Go+ is lexically scoped using blocks:
+
+* The scope of a [predeclared identifier]() is the universe block.
+* The scope of an identifier denoting a constant, type, variable, or function declared at top level (outside any function) is the package block.
+* The scope of the package name of an imported package is the file block of the file containing the import declaration.
+* The scope of an identifier denoting function parameter, or result variable is the function body.
+* The scope of a constant or variable identifier declared inside a function begins at the end of the `ConstSpec` or `VarSpec` (`ShortVarDecl` for short variable declarations) and ends at the end of the innermost containing block.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the entity declared by the inner declaration.
+
+The [package clause]() is not a declaration; the package name does not appear in any scope. Its purpose is to identify the files belonging to the same [package](#packages) and to specify the default package name for import declarations.
+
+### Label scopes
+
+Labels are declared by [labeled statements](#labeled-statements) and are used in the "[break](#break-statements)", "[continue](#continue-statements)", and "[goto](#goto-statements)" statements. It is illegal to define a label that is never used. In contrast to other identifiers, labels are not block scoped and do not conflict with identifiers that are not labels. The scope of a label is the body of the function in which it is declared and excludes the body of any nested function.
+
+
+### Blank identifier
+
+The _blank identifier_ is represented by the underscore character `_`. It serves as an anonymous placeholder instead of a regular (non-blank) identifier and has special meaning in [declarations](#declarations-and-scope), as an [operand](), and in [assignment statements](#assignment-statements).
+
+### Predeclared identifiers
+
+The following identifiers are implicitly declared in the [universe block]():
+
+```go
+Types:
+	any bigint bigrat bool byte comparable
+	complex64 complex128 error float32 float64
+	int int8 int16 int32 int64 rune string
+	uint uint8 uint16 uint32 uint64 uintptr
+
+Constants:
+	true false iota
+
+Zero value:
+	nil
+
+Functions:
+	append cap clear close complex copy delete echo imag len
+	make max min new panic print printf println real recover // TODO(xsw): more
+```
+
+### Exported identifiers
+
+An identifier may be _exported_ to permit access to it from another package. An identifier is exported if both:
+
+* the first character of the identifier's name is a Unicode uppercase letter (Unicode character category Lu); and
+* the identifier is declared in the [package block]() or it is a [field name]() or [method name]().
+
+All other identifiers are not exported.
+
+### Uniqueness of identifiers
+
+Given a set of identifiers, an identifier is called _unique_ if it is _different_ from every other in the set. Two identifiers are different if they are spelled differently, or if they appear in different [packages](#packages) and are not [exported](#exported-identifiers). Otherwise, they are the same.
+
+### Constant declarations
+
+A constant declaration binds a list of identifiers (the names of the constants) to the values of a list of [constant expressions](#constant-expressions). The number of identifiers must be equal to the number of expressions, and the nth identifier on the left is bound to the value of the nth expression on the right.
+
+```go
+ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
+ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .
+
+IdentifierList = identifier { "," identifier } .
+ExpressionList = Expression { "," Expression } .
+```
+
+If the type is present, all constants take the type specified, and the expressions must be [assignable]() to that type, which must not be a type parameter. If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped [constants](#constants), the declared constants remain untyped and the constant identifiers denote the constant values. For instance, if the expression is a floating-point literal, the constant identifier denotes a floating-point constant, even if the literal's fractional part is zero.
+
+```go
+const Pi float64 = 3.14159265358979323846
+const zero = 0.0         // untyped floating-point constant
+const (
+	size int64 = 1024
+	eof        = -1  // untyped integer constant
+)
+const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
+const u, v float32 = 0, 3    // u = 0.0, v = 3.0
+```
+
+Within a parenthesized const declaration list the expression list may be omitted from any but the first ConstSpec. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list. The number of identifiers must be equal to the number of expressions in the previous list. Together with the [iota constant generator](#iota) this mechanism permits light-weight declaration of sequential values:
+
+```go
+const (
+	Sunday = iota
+	Monday
+	Tuesday
+	Wednesday
+	Thursday
+	Friday
+	Partyday
+	numberOfDays  // this constant is not exported
+)
+```
+
+### Iota
+
+Within a [constant declaration](#constant-declarations), the predeclared identifier iota represents successive untyped integer [constants](#constants). Its value is the index of the respective [ConstSpec]() in that constant declaration, starting at zero. It can be used to construct a set of related constants:
+
+```go
+const (
+	c0 = iota  // c0 == 0
+	c1 = iota  // c1 == 1
+	c2 = iota  // c2 == 2
+)
+
+const (
+	a = 1 << iota  // a == 1  (iota == 0)
+	b = 1 << iota  // b == 2  (iota == 1)
+	c = 3          // c == 3  (iota == 2, unused)
+	d = 1 << iota  // d == 8  (iota == 3)
+)
+
+const (
+	u         = iota * 42  // u == 0     (untyped integer constant)
+	v float64 = iota * 42  // v == 42.0  (float64 constant)
+	w         = iota * 42  // w == 84    (untyped integer constant)
+)
+
+const x = iota  // x == 0
+const y = iota  // y == 0
+```
+
+By definition, multiple uses of iota in the same ConstSpec all have the same value:
+
+```go
+const (
+	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
+	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
+	_, _                                  //                        (iota == 2, unused)
+	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
+)
+```
+
+This last example exploits the [implicit repetition](#constant-declarations) of the last non-empty expression list.
+
+### Type declarations
+
+A type declaration binds an identifier, the _type name_, to a [type](#types). Type declarations come in two forms: alias declarations and type definitions.
+
+```go
+TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
+TypeSpec = AliasDecl | TypeDef .
+```
+
+#### Alias declarations
+
+An alias declaration binds an identifier to the given type.
+
+```go
+AliasDecl = identifier "=" Type .
+```
+
+Within the [scope]() of the identifier, it serves as an alias for the type.
+
+```go
+type (
+	nodeList = []*Node  // nodeList and []*Node are identical types
+	Polar    = polar    // Polar and polar denote identical types
+)
+```
+
+#### Type definitions
+
+A type definition creates a new, distinct type with the same [underlying type](#underlying-types) and operations as the given type and binds an identifier, the _type name_, to it.
+
+```go
+TypeDef = identifier Type .
+```
+
+The new type is called a _defined type_. It is [different]() from any other type, including the type it is created from.
+
+```go
+type (
+	Point struct{ x, y float64 }  // Point and struct{ x, y float64 } are different types
+	polar Point                   // polar and Point denote different types
+)
+
+type TreeNode struct {
+	left, right *TreeNode
+	value any
+}
+
+type Block interface {
+	BlockSize() int
+	Encrypt(src, dst []byte)
+	Decrypt(src, dst []byte)
+}
+```
+
+Type definitions may be used to define different boolean, numeric, or string types:
+
+```go
+type TimeZone int
+
+const (
+	EST TimeZone = -(5 + iota)
+	CST
+	MST
+	PST
+)
+```
+
+### Variable declarations
+
+A variable declaration creates one or more [variables](#variables), binds corresponding identifiers to them, and gives each a type and an initial value.
+
+```go
+VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
+VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
+```
+
+```go
+var i int
+var U, V, W float64
+var k = 0
+var x, y float32 = -1, -2
+var (
+	i       int
+	u, v, s = 2.0, 3.0, "bar"
+)
+var re, im = complexSqrt(-1)
+var _, found = entries[name]  // map lookup; only interested in "found"
+```
+
+If a list of expressions is given, the variables are initialized with the expressions following the rules for [assignment statements](#assignment-statements). Otherwise, each variable is initialized to its [zero value](#the-zero-value).
+
+If a type is present, each variable is given that type. Otherwise, each variable is given the type of the corresponding initialization value in the assignment. If that value is an untyped constant, it is first implicitly [converted](#conversions) to its [default type](#constants); if it is an untyped boolean value, it is first implicitly converted to type `bool`. The predeclared value `nil` cannot be used to initialize a variable with no explicit type.
+
+```go
+var d = math.Sin(0.5)  // d is float64
+var i = 42             // i is int
+var t, ok = x.(T)      // t is T, ok is bool
+var n = nil            // illegal
+```
+
+Implementation restriction: A compiler may make it illegal to declare a variable inside a [function body](#function-declarations) if the variable is never used.
+
+In a function body, variables do not need to be explicitly defined.
+
+```go
+d := math.Sin(0.5)  // d is float64
+i := 42             // i is int
+t, ok := x.(T)      // t is T, ok is bool
+```
+
+See [short variable declarations](#short-variable-declarations).
+
+### Function declarations
+
+A function declaration binds an identifier, the function name, to a function.
+
+```go
+FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
+FunctionName = identifier .
+FunctionBody = Block .
+```
+
+If the function's [signature](#function-types) declares result parameters, the function body's statement list must end in a [terminating statement](#terminating-statements).
+
+```go
+func IndexRune(s string, r rune) int {
+	for i, c := range s {
+		if c == r {
+			return i
+		}
+	}
+	// invalid: missing return statement
+}
+```
+
+
+## Packages
+
+Go+ programs are constructed by linking together packages. A package in turn is constructed from one or more source files that together declare constants, types, variables and functions belonging to the package and which are accessible in all files of the same package. Those elements may be [exported]() and used in another package.
+
+### Source file organization
+
+Each source file consists of a package clause defining the package to which it belongs, followed by a possibly empty set of import declarations that declare packages whose contents it wishes to use, followed by a possibly empty set of declarations of functions, types, variables, and constants.
+
+```go
+SourceFile       = [ PackageClause ";" ] { ImportDecl ";" } { TopLevelDecl ";" } .
+```
+
+### Package clause
+
+A package clause begins each source file and defines the package to which the file belongs.
+
+```go
+PackageClause  = "package" PackageName .
+PackageName    = identifier .
+```
+
+The PackageName must not be the [blank identifier]().
+
+```go
+package math
+```
+
+### Import declarations
+
+An import declaration states that the source file containing the declaration depends on functionality of the `imported` package ([Program initialization and execution]()) and enables access to [exported]() identifiers of that package. The import names an identifier (PackageName) to be used for access and an ImportPath that specifies the package to be imported.
+
+```go
+ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
+ImportSpec       = [ "." | PackageName ] ImportPath .
+ImportPath       = string_lit .
+```
+
+The PackageName is used in [qualified identifiers]() to access exported identifiers of the package within the importing source file. It is declared in the [file block](). If the PackageName is omitted, it defaults to the identifier specified in the [package clause](#package-clause) of the imported package. If an explicit period (.) appears instead of a name, all the package's exported identifiers declared in that package's [package block]() will be declared in the importing source file's file block and must be accessed without a qualifier.
+
+The interpretation of the ImportPath is implementation-dependent but it is typically a substring of the full file name of the compiled package and may be relative to a repository of installed packages.
+
+Implementation restriction: A compiler may restrict ImportPaths to non-empty strings using only characters belonging to [Unicode's]() L, M, N, P, and S general categories (the Graphic characters without spaces) and may also exclude the characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD.
+
+Consider a compiled a package containing the package clause package math, which exports function `Sin`, and installed the compiled package in the file identified by "lib/math". This table illustrates how `Sin` is accessed in files that import the package after the various types of import declaration.
+
+```go
+Import declaration          Local name of Sin
+
+import   "lib/math"         math.Sin
+import m "lib/math"         m.Sin
+import . "lib/math"         Sin
+```
+
+An import declaration declares a dependency relation between the importing and imported package. It is illegal for a package to import itself, directly or indirectly, or to directly import a package without referring to any of its exported identifiers. To import a package solely for its side-effects (initialization), use the [blank]() identifier as explicit package name:
+
+```go
+import _ "lib/math"
+```
+
+### An example package
+
+Here is a complete Go+ package that implements XXX.
+
+```go
 TODO
+```
+
+## Program initialization and execution
+
+### The zero value
+
+When storage is allocated for a [variable](#variables), either through a declaration or a call of `new`, or when a new value is created, either through a composite literal or a call of `make`, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the _zero_ value for its type: `false` for booleans, `0` for numeric types, `""` for strings, and `nil` for pointers, functions, interfaces, slices, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
+
+These two simple declarations are equivalent:
+
+```go
+var i int
+var i int = 0
+```
+
+TODO
+
+### Package initialization
+
+Within a package, package-level variable initialization proceeds stepwise, with each step selecting the variable earliest in `declaration order` which has no dependencies on uninitialized variables.
+
+More precisely, a package-level variable is considered ready for `initialization` if it is not yet initialized and either has no [initialization expression]() or its initialization expression has no dependencies on uninitialized variables. Initialization proceeds by repeatedly initializing the next package-level variable that is earliest in declaration order and ready for initialization, until there are no variables ready for initialization.
+
+If any variables are still uninitialized when this process ends, those variables are part of one or more initialization cycles, and the program is not valid.
+
+Multiple variables on the left-hand side of a variable declaration initialized by single (multi-valued) expression on the right-hand side are initialized together: If any of the variables on the left-hand side is initialized, all those variables are initialized in the same step.
+
+```go
+var x = a
+var a, b = f() // a and b are initialized together, before x is initialized
+```
+
+For the purpose of package initialization, [blank]() variables are treated like any other variables in declarations.
+
+The declaration order of variables declared in multiple files is determined by the order in which the files are presented to the compiler: Variables declared in the first file are declared before any of the variables declared in the second file, and so on. To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.
+
+Dependency analysis does not rely on the actual values of the variables, only on lexical `references` to them in the source, analyzed transitively. For instance, if a variable x's initialization expression refers to a function whose body refers to variable y then x depends on y. Specifically:
+
+* A reference to a variable or function is an identifier denoting that variable or function.
+* A reference to a method m is a [method value]() or [method expression]() of the form `t.m`, where the (static) type of t is not an interface type, and the method `m` is in the method set of `t`. It is immaterial whether the resulting function value `t.m` is invoked.
+* A variable, function, or method x depends on a variable y if x's initialization expression or body (for functions and methods) contains a reference to y or to a function or method that depends on y.
+
+For example, given the declarations
+
+```go
+var (
+	a = c + b  // == 9
+	b = f()    // == 4
+	c = f()    // == 5
+	d = 3      // == 5 after initialization has finished
+)
+
+func f() int {
+	d++
+	return d
+}
+```
+
+the initialization order is `d`, `b`, `c`, `a`. Note that the order of subexpressions in initialization expressions is irrelevant: `a = c + b` and `a = b + c` result in the same initialization order in this example.
+
+Dependency analysis is performed per package; only references referring to variables, functions, and (non-interface) methods declared in the current package are considered. If other, hidden, data dependencies exists between variables, the initialization order between those variables is unspecified.
+
+For instance, given the declarations (TODO: use classfile instead of method)
+
+```go
+var x = I(T{}).ab()   // x has an undetected, hidden dependency on a and b
+var _ = sideEffect()  // unrelated to x, a, or b
+var a = b
+var b = 42
+
+type I interface      { ab() []int }
+type T struct{}
+func (T) ab() []int   { return []int{a, b} }
+```
+
+the variable `a` will be initialized after `b` but whether `x` is initialized before `b`, between `b` and `a`, or after `a`, and thus also the moment at which `sideEffect()` is called (before or after `x` is initialized) is not specified.
+
+Variables may also be initialized using functions named init declared in the package block, with no arguments and no result parameters.
+
+```go
+func init() { … }
+```
+
+Multiple such functions may be defined per package, even within a single source file. In the package block, the `init` identifier can be used only to declare `init` functions, yet the identifier itself is not [declared](). Thus init functions cannot be referred to from anywhere in a program.
+
+The entire package is initialized by assigning initial values to all its package-level variables followed by calling all `init` functions in the order they appear in the source, possibly in multiple files, as presented to the compiler.
+
+### Program initialization
+
+The packages of a complete program are initialized stepwise, one package at a time. If a package has imports, the imported packages are initialized before initializing the package itself. If multiple packages import a package, the imported package will be initialized only once. The importing of packages, by construction, guarantees that there can be no cyclic initialization dependencies. More precisely:
+
+Given the list of all packages, sorted by import path, in each step the first uninitialized package in the list for which all imported packages (if any) are already initialized is [initialized](#package-initialization). This step is repeated until all packages are initialized.
+
+Package initialization—variable initialization and the invocation of `init` functions—happens in a single goroutine, sequentially, one package at a time. An `init` function may launch other goroutines, which can run concurrently with the initialization code. However, initialization always sequences the `init` functions: it will not invoke the next one until the previous one has returned.
+
+### Program execution
+
+A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value.
+
+```go
+func main() { … }
+```
+
+Program execution begins by [initializing the program](#program-initialization) and then invoking the function `main` in package `main`. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
+
+
+### Run-time panics
+
+Execution errors such as attempting to index an array out of bounds trigger a _run-time panic_ equivalent to a call of the built-in function [panic]() with a value of the implementation-defined interface type `runtime.Error`. That type satisfies the predeclared interface type [error](#errors). The exact error values that represent distinct run-time error conditions are unspecified.
+
+```go
+package runtime
+
+type Error interface {
+	error
+	// and perhaps other methods
+}
+```
+
+
+## System considerations
+
+### Package unsafe
+
+The built-in package `unsafe`, known to the compiler and accessible through the [import path]() `"unsafe"`, provides facilities for low-level programming including operations that violate the type system. A package using unsafe must be vetted manually for type safety and may not be portable. The package provides the following interface:
+
+```go
+package unsafe
+
+type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
+type Pointer *ArbitraryType
+
+func Alignof(variable ArbitraryType) uintptr
+func Offsetof(selector ArbitraryType) uintptr
+func Sizeof(variable ArbitraryType) uintptr
+
+type IntegerType int  // shorthand for an integer type; it is not a real type
+func Add(ptr Pointer, len IntegerType) Pointer
+func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
+func SliceData(slice []ArbitraryType) *ArbitraryType
+func String(ptr *byte, len IntegerType) string
+func StringData(str string) *byte
+```
+
+A Pointer is a [pointer type](#pointer-types) but a Pointer value may not be [dereferenced](#address-operators). Any pointer or value of [underlying type](#underlying-types) `uintptr` can be [converted](#conversions) to a type of underlying type Pointer and vice versa. The effect of converting between Pointer and `uintptr` is implementation-defined.
+
+```go
+var f float64
+bits = *(*uint64)(unsafe.Pointer(&f))
+
+type ptr unsafe.Pointer
+bits = *(*uint64)(ptr(&f))
+
+func f[P ~*B, B any](p P) uintptr {
+	return uintptr(unsafe.Pointer(p))
+}
+
+var p ptr = nil
+```
+
+The functions `Alignof` and `Sizeof` take an expression `x` of any type and return the alignment or size, respectively, of a hypothetical variable `v` as if `v` was declared via `var v = x`.
+
+The function `Offsetof` takes a (possibly parenthesized) [selector]() `s.f`, denoting a field `f` of the struct denoted by `s` or `*s`, and returns the field offset in bytes relative to the struct's address. If `f` is an [embedded field](), it must be reachable without pointer indirections through fields of the struct. For a struct `s` with field `f`:
+
+```go
+uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
+```
+
+Computer architectures may require memory addresses to be _aligned_; that is, for addresses of a variable to be a multiple of a factor, the variable's type's _alignment_. The function `Alignof` takes an expression denoting a variable of any type and returns the alignment of the (type of the) variable in bytes. For a variable `x`:
+
+```go
+uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
+```
+
+A (variable of) type T has _variable size_ if T is a [type parameter](), or if it is an array or struct type containing elements or fields of variable size. Otherwise the size is constant. Calls to `Alignof`, `Offsetof`, and `Sizeof` are compile-time [constant expressions](#constant-expressions) of type `uintptr` if their arguments (or the struct `s` in the selector expression `s.f` for `Offsetof`) are types of constant size.
+
+The function `Add` adds `len` to `ptr` and returns the updated pointer `unsafe.Pointer(uintptr(ptr) + uintptr(len))`. The `len` argument must be of [integer type](#numeric-types) or an untyped [constant](#constants). A constant `len` argument must be [representable]() by a value of type `int`; if it is an untyped constant it is given type `int`. The rules for [valid uses]() of Pointer still apply.
+
+The function `Slice` returns a slice whose underlying array starts at `ptr` and whose length and capacity are `len`. `Slice(ptr, len)` is equivalent to
+
+```go
+(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
+```
+
+except that, as a special case, if `ptr` is `nil` and `len` is zero, `Slice` returns `nil`.
+
+The `len` argument must be of [integer type](#numeric-types) or an untyped [constant](#constants). A constant `len` argument must be non-negative and [representable]() by a value of type `int`; if it is an untyped constant it is given type `int`. At run time, if `len` is negative, or if `ptr` is nil and `len` is not zero, a [run-time panic](#run-time-panics) occurs.
+
+The function `SliceData` returns a pointer to the underlying array of the `slice` argument. If the slice's capacity `cap(slice)` is not zero, that pointer is `&slice[:1][0]`. If slice is `nil`, the result is `nil`. Otherwise it is a non-nil pointer to an unspecified memory address.
+
+The function `String` returns a `string` value whose underlying bytes start at `ptr` and whose length is `len`. The same requirements apply to the `ptr` and `len` argument as in the function `Slice`. If `len` is zero, the result is the empty string `""`. Since Go+ strings are immutable, the bytes passed to `String` must not be modified afterwards.
+
+The function `StringData` returns a pointer to the underlying bytes of the `str` argument. For an empty string the return value is unspecified, and may be `nil`. Since Go+ strings are immutable, the bytes returned by `StringData` must not be modified.
+
+### Size and alignment guarantees
+
+For the [numeric types](#numeric-types), the following sizes are guaranteed:
+
+```go
+type                                 size in bytes
+
+byte, uint8, int8                     1
+uint16, int16                         2
+uint32, int32, float32                4
+uint64, int64, float64, complex64     8
+complex128                           16
+```
+
+The following minimal alignment properties are guaranteed:
+
+* For a variable `x` of any type: `unsafe.Alignof(x)` is at least 1.
+* For a variable `x` of struct type: `unsafe.Alignof(x)` is the largest of all the values `unsafe.Alignof(x.f)` for each field `f` of `x`, but at least 1.
+* For a variable `x` of array type: `unsafe.Alignof(x)` is the same as the alignment of a variable of the array's element type.
+
+A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
+
+## Appendix
+
+### Language versions
+
+TODO
+
+### Type unification rules
+
+The type unification rules describe if and how two types unify. The precise details are relevant for Go+ implementations, affect the specifics of error messages (such as whether a compiler reports a type inference or other error), and may explain why type inference fails in unusual code situations. But by and large these rules can be ignored when writing Go code: type inference is designed to mostly "work as expected", and the unification rules are fine-tuned accordingly.
+
+Type unification is controlled by a matching mode, which may be `exact` or `loose`. As unification recursively descends a composite type structure, the matching mode used for elements of the type, the element matching mode, remains the same as the matching mode except when two types are unified for [assignability]() (≡A): in this case, the matching mode is loose at the top level but then changes to exact for element types, reflecting the fact that types don't have to be identical to be assignable.
+
+Two types that are not bound type parameters unify exactly if any of following conditions is true:
+
+* Both types are [identical]().
+* Both types have identical structure and their element types unify exactly.
+* Exactly one type is an [unbound]() type parameter with a [underlying type](#underlying-types), and that underlying type unifies with the other type per the unification rules for ≡A (loose unification at the top level and exact unification for element types).
+
+If both types are bound type parameters, they unify per the given matching modes if:
+
+* Both type parameters are identical.
+* At most one of the type parameters has a known type argument. In this case, the type parameters are `joined`: they both stand for the same type argument. If neither type parameter has a known type argument yet, a future type argument inferred for one the type parameters is simultaneously inferred for both of them.
+* Both type parameters have a known type argument and the type arguments unify per the given matching modes.
+
+A single bound type parameter P and another type T unify per the given matching modes if:
+
+* P doesn't have a known type argument. In this case, T is inferred as the type argument for P.
+* P does have a known type argument A, A and T unify per the given matching modes, and one of the following conditions is true:
+  * Both A and T are interface types: In this case, if both A and T are also [defined]() types, they must be [identical](). Otherwise, if neither of them is a defined type, they must have the same number of methods (unification of A and T already established that the methods match).
+  * Neither A nor T are interface types: In this case, if T is a defined type, T replaces A as the inferred type argument for P.
+
+Finally, two types that are not bound type parameters unify loosely (and per the element matching mode) if:
+
+* Both types unify exactly.
+* One type is a [defined type](), the other type is a type literal, but not an interface, and their underlying types unify per the element matching mode.
+* Both types are interfaces (but not type parameters) with identical [type terms](), both or neither embed the predeclared type [comparable](), corresponding method types unify exactly, and the method set of one of the interfaces is a subset of the method set of the other interface.
+* Only one type is an interface (but not a type parameter), corresponding methods of the two types unify per the element matching mode, and the method set of the interface is a subset of the method set of the other type.
+* Both types have the same structure and their element types unify per the element matching mode.
diff --git a/doc/spec.md b/doc/spec.md
index 60206edc..52eaa53c 100644
--- a/doc/spec.md
+++ b/doc/spec.md
@@ -1,6 +1,8 @@
-Go+ Full Specification
+The Go+ Full Specification
 =====
 
+TODO
+
 ## Comments
 
 See [Comments](spec-mini.md#comments).
diff --git a/format/formatutil/format_gop.go b/format/formatutil/format_gop.go
new file mode 100644
index 00000000..7bcb69a1
--- /dev/null
+++ b/format/formatutil/format_gop.go
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2025 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 formatutil
+
+import (
+	"github.com/goplus/gop/format"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gop/token"
+)
+
+// RearrangeFuncs rearranges functions in src.
+func RearrangeFuncs(src []byte, filename ...string) ([]byte, error) {
+	var fname string
+	if filename != nil {
+		fname = filename[0]
+	}
+
+	fset := token.NewFileSet()
+	base := fset.Base()
+	f := fset.AddFile(fname, base, len(src))
+
+	var s scanner.Scanner
+	s.Init(f, src, nil, scanner.ScanComments)
+	stmts := splitStmts(&s)
+	first := firstNonDecl(stmts)
+	if first < 0 { // no non-decl stmt
+		return src, nil
+	}
+
+	ret := make([]byte, 0, len(src))
+	off := int(stmts[first].words[0].pos) - base
+	ret = append(ret, src[:off]...)
+
+	rest := stmts[first:]
+	for i, s := range rest {
+		if s.isFuncDecl() {
+			ret = append(ret, codeOf(src, base, i, rest)...)
+		}
+	}
+	for i, s := range rest {
+		if !s.isFuncDecl() {
+			ret = append(ret, codeOf(src, base, i, rest)...)
+		}
+	}
+	return ret, nil
+}
+
+func codeOf(src []byte, base, i int, rest []aStmt) []byte {
+	from := int(rest[i].words[0].pos) - base
+	to := 0
+	if i == len(rest)-1 {
+		to = len(src)
+	} else {
+		to = int(rest[i+1].words[0].pos) - base
+	}
+	return src[from:to]
+}
+
+func firstNonDecl(stmts []aStmt) int {
+	for i, stmt := range stmts {
+		if !stmt.isDecl() {
+			return i
+		}
+	}
+	return -1
+}
+
+func splitStmts(s *scanner.Scanner) (stmts []aStmt) {
+	var level int
+	var stmt aStmt
+	for {
+		pos, tok, _ := s.Scan()
+		if tok == token.EOF {
+			return
+		}
+		stmt.words = append(stmt.words, aWord{pos, tok})
+		switch tok {
+		case token.LBRACE:
+			level++
+		case token.RBRACE:
+			level--
+		}
+		if tok == token.SEMICOLON && level == 0 {
+			stmt.tok, stmt.at = tokOf(stmt.words)
+			stmts = append(stmts, stmt)
+			stmt = aStmt{}
+			continue
+		}
+	}
+}
+
+type aWord struct {
+	pos token.Pos
+	tok token.Token
+}
+
+type aStmt struct {
+	words []aWord
+	tok   token.Token
+	at    int
+}
+
+func (s aStmt) isFuncDecl() bool {
+	return s.tok == token.FUNC && isFuncDecl(s.words[s.at+1:])
+}
+
+func (s aStmt) isDecl() bool {
+	switch s.tok {
+	case token.CONST, token.TYPE, token.VAR:
+		return true
+	case token.FUNC:
+		return isFuncDecl(s.words[s.at+1:])
+	}
+	return false
+}
+
+func tokOf(words []aWord) (tok token.Token, at int) {
+	for i, w := range words {
+		if w.tok != token.COMMENT {
+			return w.tok, i
+		}
+	}
+	return words[0].tok, 0
+}
+
+func isFuncDecl(words []aWord) bool {
+	if startWith(words, token.LPAREN) { // func (
+		words = seekAfter(words[1:], token.RPAREN, token.LPAREN) // func (...)
+		if startWith(words, token.LBRACE) {                      // func (...) {
+			return false
+		}
+	}
+	return true
+}
+
+func seekAfter(words []aWord, tokR, tokL token.Token) []aWord {
+	level := 0
+	for i, w := range words {
+		switch w.tok {
+		case tokR:
+			if level == 0 {
+				return words[i+1:]
+			}
+			level--
+		case tokL:
+			level++
+		}
+	}
+	return nil
+}
+
+func startWith(words []aWord, tok token.Token) bool {
+	for _, w := range words {
+		switch w.tok {
+		case token.COMMENT:
+			continue
+		case tok:
+			return true
+		}
+		break
+	}
+	return false
+}
+
+// SourceEx formats src in canonical gopfmt style and returns the result
+// or an (I/O or syntax) error. src is expected to be a syntactically
+// correct Go+ source file, or a list of Go+ declarations or statements.
+//
+// If src is a partial source file, the leading and trailing space of src
+// is applied to the result (such that it has the same leading and trailing
+// space as src), and the result is indented by the same amount as the first
+// line of src containing code. Imports are not sorted for partial source files.
+func SourceEx(src []byte, class bool, filename ...string) (formatted []byte, err error) {
+	formatted, err = format.Source(src, class, filename...)
+	if err == nil {
+		return
+	}
+	src, err = RearrangeFuncs(src, filename...)
+	if err == nil {
+		formatted, err = format.Source(src, class, filename...)
+	}
+	return
+}
diff --git a/format/internal.go b/format/internal.go
index ded55ece..37584a85 100644
--- a/format/internal.go
+++ b/format/internal.go
@@ -32,7 +32,7 @@ import (
 
 // parse parses src, which was read from the named file,
 // as a Go source file, declaration, or statement list.
-func parse(fset *token.FileSet, filename string, src []byte, class, fragmentOk bool) (
+func parse(fset *token.FileSet, filename string, src []byte, class, _ /* fragmentOk */ bool) (
 	file *ast.File,
 	sourceAdj func(src []byte, indent int) []byte,
 	indentAdj int,
diff --git a/go.mod b/go.mod
index 3262118e..e04806b3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,16 @@ module github.com/goplus/gop
 go 1.18
 
 require (
-	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/gogen v1.16.0
-	github.com/goplus/llgo v0.9.7
-	github.com/goplus/mod v0.13.12
-	github.com/qiniu/x v1.13.10
+	github.com/fsnotify/fsnotify v1.8.0
+	github.com/goplus/gogen v1.16.6
+	github.com/goplus/llgo v0.9.9
+	github.com/goplus/mod v0.13.17
+	github.com/qiniu/x v1.13.12
 	golang.org/x/tools v0.19.0
 )
 
 require (
-	golang.org/x/mod v0.19.0 // indirect
+	golang.org/x/mod v0.20.0 // indirect
 	golang.org/x/sys v0.21.0 // indirect
 )
 
diff --git a/go.sum b/go.sum
index 1b2846d7..2a4c6ac4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,15 @@
-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/goplus/gogen v1.16.0 h1:hAK2ZX8vCjH+Y2QoJl9viSZ8Gw9pzE0vCz5voYBYnv4=
-github.com/goplus/gogen v1.16.0/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
-github.com/goplus/llgo v0.9.7 h1:LRF2Fq9ts4QrVxOPZufexalbIoJ1oiBERjCWQ45wxbg=
-github.com/goplus/llgo v0.9.7/go.mod h1:5Fs+08NslqofJ7xtOiIXugkurYOoQvY02ZkFNWA1uEI=
-github.com/goplus/mod v0.13.12 h1:Trwk6j3i9VvBuW6/9ZxmkoFlEL2v3HKQu0Na1c6DAdw=
-github.com/goplus/mod v0.13.12/go.mod h1:fyCcoiL02uUQK9CWxGK9pQzuJT+rZIvRKaaG+hSa2bk=
-github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=
-github.com/qiniu/x v1.13.10/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
-golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
-golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/goplus/gogen v1.16.6 h1:Zwv18HoTbPDk8s2ajxgVeqZE5i4/GMV722KHl6GS8Yk=
+github.com/goplus/gogen v1.16.6/go.mod h1:6TQYbabXDF9LCdDkOOzHmfg1R4ENfXQ3XpHa9RhTSD8=
+github.com/goplus/llgo v0.9.9 h1:sCM8ehOtuAEY/NQbJ4xAMRZhxEdDgilouWZpoaLiYrM=
+github.com/goplus/llgo v0.9.9/go.mod h1:udcq+s6tGOdhJq7I8fXPTv4qT2j17/KrlvtcJrMZAoM=
+github.com/goplus/mod v0.13.17 h1:aWp14xosENrh7t0/0qcIejDmQEiTgI3ou2+KoLDlSlE=
+github.com/goplus/mod v0.13.17/go.mod h1:XlHf8mnQ4QkRDX14Of2tpywuHDd+JVpPStvh3egog+0=
+github.com/qiniu/x v1.13.12 h1:UyAwja6dgKUOYWZMzzc02wLodwnZ7wmK/0XzRd0e78g=
+github.com/qiniu/x v1.13.12/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
+golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
+golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/parser/parser.go b/parser/parser.go
index 9b8ea67f..a9850690 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1729,11 +1729,25 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		if p.tok == token.STRING && len(p.lit) > 1 {
 			bl.Extra = p.stringLit(p.pos, p.lit)
 		}
-		x = bl
-		if debugParseOutput {
-			log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", p.tok, p.lit)
-		}
 		p.next()
+		if p.tok == token.UNIT {
+			nu := &ast.NumberUnitLit{
+				ValuePos: bl.ValuePos,
+				Kind:     bl.Kind,
+				Value:    bl.Value,
+				Unit:     p.lit,
+			}
+			x = nu
+			if debugParseOutput {
+				log.Printf("ast.NumberUnitLit{Kind: %v, Value: %v, Unit: %v}\n", nu.Kind, nu.Value, nu.Unit)
+			}
+			p.next()
+		} else {
+			x = bl
+			if debugParseOutput {
+				log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", bl.Kind, bl.Value)
+			}
+		}
 		return
 
 	case token.LPAREN:
@@ -2168,6 +2182,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	case *ast.BadExpr:
 	case *ast.Ident:
 	case *ast.BasicLit:
+	case *ast.NumberUnitLit:
 	case *ast.FuncLit:
 	case *ast.CompositeLit:
 	case *ast.SliceLit:
@@ -2242,12 +2257,14 @@ func (p *parser) checkExprOrType(x ast.Expr) ast.Expr {
 }
 
 // If lhs is set and the result is an identifier, it is not resolved.
-func (p *parser) parsePrimaryExpr(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
+func (p *parser) parsePrimaryExpr(iden *ast.Ident, lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
 	if p.trace {
 		defer un(trace(p, "PrimaryExpr"))
 	}
 
-	if x, isTuple = p.parseOperand(lhs, allowTuple, allowCmd); isTuple {
+	if iden != nil {
+		x = iden
+	} else if x, isTuple = p.parseOperand(lhs, allowTuple, allowCmd); isTuple {
 		return
 	}
 L:
@@ -2369,7 +2386,7 @@ func (p *parser) checkCmd() bool {
 
 // parseErrWrapExpr: expr! expr? expr?:defval
 func (p *parser) parseErrWrapExpr(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
-	if x, isTuple = p.parsePrimaryExpr(lhs, allowTuple, allowCmd); isTuple {
+	if x, isTuple = p.parsePrimaryExpr(nil, lhs, allowTuple, allowCmd); isTuple {
 		return
 	}
 	if expr, ok := x.(*ast.ErrWrapExpr); ok {
@@ -2659,8 +2676,14 @@ const (
 // of a range clause (with mode == rangeOk). The returned statement is an
 // assignment with a right-hand side that is a single unary expression of
 // the form "range x". No guarantees are given for the left-hand side.
-func (p *parser) parseSimpleStmt(mode int, allowCmd bool) (ast.Stmt, bool) {
-	return p.parseSimpleStmtEx(mode, allowCmd, false)
+func (p *parser) parseSimpleStmt(mode int, allowCmd bool) ast.Stmt {
+	ss, _ /* isRange */ := p.parseSimpleStmtEx(mode, allowCmd, false)
+	return ss
+}
+
+func (p *parser) parseBranchCmdStmt(iden *ast.Ident) ast.Stmt { // Go+: goto as command
+	x, _ := p.parsePrimaryExpr(iden, false, false, true)
+	return &ast.ExprStmt{X: x}
 }
 
 func (p *parser) parseSimpleStmtEx(mode int, allowCmd, allowRangeExpr bool) (ast.Stmt, bool) {
@@ -2818,20 +2841,29 @@ func (p *parser) parseBranchStmt(tok token.Token) ast.Stmt {
 
 	oldpos, oldlit := p.pos, p.lit // Go+: save token to allow goto() as a function
 	pos := p.expect(tok)
-	if p.tok == token.LPAREN { // Go+: allow goto() as a function
+	next := p.tok
+	if next != token.IDENT && next != token.SEMICOLON { // Go+: allow goto() as a function
 		p.unget(oldpos, token.IDENT, oldlit)
-		s, _ := p.parseSimpleStmt(basic, false)
+		s := p.parseSimpleStmt(basic, true)
 		p.expectSemi()
 		return s
 	}
 
 	var label *ast.Ident
-	if tok != token.FALLTHROUGH && p.tok == token.IDENT {
+	if tok != token.FALLTHROUGH && next == token.IDENT {
 		label = p.parseIdent()
 		// add to list of unresolved targets
 		n := len(p.targetStack) - 1
 		p.targetStack[n] = append(p.targetStack[n], label)
 	}
+	if p.tok != token.SEMICOLON { // Go+: goto command
+		if label != nil {
+			p.unget(label.NamePos, token.IDENT, label.Name)
+		}
+		s := p.parseBranchCmdStmt(&ast.Ident{NamePos: oldpos, Name: oldlit})
+		p.expectSemi()
+		return s
+	}
 	p.expectSemi()
 
 	return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label}
@@ -2872,7 +2904,7 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
 			p.next()
 			p.error(p.pos, "var declaration not allowed in 'IF' initializer")
 		}
-		init, _ = p.parseSimpleStmt(basic, false)
+		init = p.parseSimpleStmt(basic, false)
 	}
 
 	var condStmt ast.Stmt
@@ -2889,7 +2921,7 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
 			p.expect(token.SEMICOLON)
 		}
 		if p.tok != token.LBRACE {
-			condStmt, _ = p.parseSimpleStmt(basic, false)
+			condStmt = p.parseSimpleStmt(basic, false)
 		}
 	} else {
 		condStmt = init
@@ -2936,24 +2968,24 @@ func (p *parser) parseForPhraseCond() (init ast.Stmt, cond ast.Expr) {
 			p.next()
 			p.error(p.pos, "var declaration not allowed in 'IF' initializer")
 		}
-		init, _ = p.parseSimpleStmt(basic, false)
+		init = p.parseSimpleStmt(basic, false)
 	}
 
 	var condStmt ast.Stmt
 	var semi struct {
 		pos token.Pos
-		lit string // ";" or "\n"; valid if pos.IsValid()
+		//lit string // ";" or "\n"; valid if pos.IsValid()
 	}
 	if !isForPhraseCondEnd(p.tok) {
 		if p.tok == token.SEMICOLON {
 			semi.pos = p.pos
-			semi.lit = p.lit
+			//semi.lit = p.lit
 			p.next()
 		} else {
 			p.expect(token.SEMICOLON)
 		}
 		if !isForPhraseCondEnd(p.tok) {
-			condStmt, _ = p.parseSimpleStmt(basic, false)
+			condStmt = p.parseSimpleStmt(basic, false)
 		}
 	} else {
 		condStmt = init
@@ -3087,7 +3119,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt {
 		prevLev := p.exprLev
 		p.exprLev = -1
 		if p.tok != token.SEMICOLON {
-			s2, _ = p.parseSimpleStmt(basic, false)
+			s2 = p.parseSimpleStmt(basic, false)
 		}
 		if p.tok == token.SEMICOLON {
 			p.next()
@@ -3108,7 +3140,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt {
 				// Having the extra nested but empty scope won't affect it.
 				p.openScope()
 				defer p.closeScope()
-				s2, _ = p.parseSimpleStmt(basic, false)
+				s2 = p.parseSimpleStmt(basic, false)
 			}
 		}
 		p.exprLev = prevLev
@@ -3246,6 +3278,8 @@ func (p *parser) toIdent(e ast.Expr) *ast.Ident {
 		return v
 	case *ast.BasicLit:
 		p.errorExpected(e.Pos(), fmt.Sprintf("'IDENT', found %v", v.Value), 2)
+	case *ast.NumberUnitLit:
+		p.errorExpected(e.Pos(), fmt.Sprintf("'IDENT', found %v", v.Value+v.Unit), 2)
 	default:
 		p.errorExpected(e.Pos(), "'IDENT'", 2)
 	}
@@ -3312,11 +3346,11 @@ func (p *parser) parseForStmt() ast.Stmt {
 			s1 = s2
 			s2 = nil
 			if p.tok != token.SEMICOLON {
-				s2, _ = p.parseSimpleStmt(basic, false)
+				s2 = p.parseSimpleStmt(basic, false)
 			}
 			p.expectSemi()
 			if p.tok != token.LBRACE {
-				s3, _ = p.parseSimpleStmt(basic, false)
+				s3 = p.parseSimpleStmt(basic, false)
 			}
 		}
 		p.exprLev = prevLev
@@ -3395,7 +3429,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 		allowCmd = false
 		fallthrough
 	case token.IDENT, token.MAP: // operands
-		s, _ = p.parseSimpleStmt(labelOk, allowCmd)
+		s = p.parseSimpleStmt(labelOk, allowCmd)
 		// because of the required look-ahead, labeled statements are
 		// parsed by parseSimpleStmt - don't expect a semicolon after
 		// them
@@ -3667,7 +3701,7 @@ func (p *parser) parseOverloadFunc() (ast.Expr, bool) {
 	case token.FUNC:
 		return p.parseFuncTypeOrLit(), true
 	case token.LPAREN:
-		x, _ := p.parsePrimaryExpr(false, false, false)
+		x, _ := p.parsePrimaryExpr(nil, false, false, false)
 		return x, true
 	}
 	return nil, false
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 356ad96c..e7b3b02b 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -57,15 +57,15 @@ type FileSystem = fsx.FileSystem
 
 // -----------------------------------------------------------------------------
 
-// Parse parses a single Go+ source file. The target specifies the Go+ source file.
+// Parse parses a single Go+ source file. The filename specifies the Go+ source file.
 // If the file couldn't be read, a nil map and the respective error are returned.
-func Parse(fset *token.FileSet, target string, src interface{}, mode Mode) (pkgs map[string]*ast.Package, err error) {
-	file, err := ParseFile(fset, target, src, mode)
+func Parse(fset *token.FileSet, filename string, src interface{}, mode Mode) (pkgs map[string]*ast.Package, err error) {
+	file, err := ParseFile(fset, filename, src, mode)
 	if err != nil {
 		return
 	}
 	pkgs = make(map[string]*ast.Package)
-	pkgs[file.Name.Name] = astFileToPkg(file, target)
+	pkgs[file.Name.Name] = astFileToPkg(file, filename)
 	return
 }
 
diff --git a/printer/nodes.go b/printer/nodes.go
index 2cf61f61..7e7c2a08 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -825,6 +825,10 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 	case *ast.BasicLit:
 		p.print(x)
 
+	case *ast.NumberUnitLit:
+		p.print(&ast.BasicLit{Kind: x.Kind, Value: x.Value})
+		p.print(&ast.Ident{Name: x.Unit})
+
 	case *ast.FuncLit:
 		p.print(x.Type.Pos(), token.FUNC)
 		// See the comment in funcDecl about how the header size is computed.
diff --git a/scanner/errors.go b/scanner/gop.go
similarity index 56%
rename from scanner/errors.go
rename to scanner/gop.go
index 77deee5c..9dd8bf39 100644
--- a/scanner/errors.go
+++ b/scanner/gop.go
@@ -19,8 +19,13 @@ package scanner
 import (
 	"go/scanner"
 	"io"
+
+	"github.com/goplus/gop/token"
+	"github.com/qiniu/x/byteutil"
 )
 
+// -----------------------------------------------------------------------------
+
 // Error is an alias of go/scanner.Error
 type Error = scanner.Error
 
@@ -33,3 +38,21 @@ type ErrorList = scanner.ErrorList
 func PrintError(w io.Writer, err error) {
 	scanner.PrintError(w, err)
 }
+
+// -----------------------------------------------------------------------------
+
+// New creates a scanner to tokenize the text src.
+//
+// Calls to Scan will invoke the error handler err if they encounter a
+// syntax error and err is not nil. Also, for each error encountered,
+// the Scanner field ErrorCount is incremented by one. The mode parameter
+// determines how comments are handled.
+func New(src string, err ErrorHandler, mode Mode) *Scanner {
+	fset := token.NewFileSet()
+	file := fset.AddFile("", fset.Base(), len(src))
+	scanner := new(Scanner)
+	scanner.Init(file, byteutil.Bytes(src), err, mode)
+	return scanner
+}
+
+// -----------------------------------------------------------------------------
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 3c791483..3842de77 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -55,6 +55,7 @@ type Scanner struct {
 	lineOffset int  // current line offset
 	nParen     int
 	insertSemi bool // insert a semicolon before next newline
+	needUnit   bool
 
 	// public state - ok to modify
 	ErrorCount int // number of errors encountered
@@ -509,6 +510,8 @@ func (s *Scanner) scanNumber() (token.Token, string) {
 	} else if s.ch == 'r' {
 		tok = token.RAT
 		s.next()
+	} else if isLetter(s.ch) {
+		s.needUnit = true
 	}
 
 	lit := string(s.src[offs:s.offset])
@@ -828,6 +831,12 @@ scanAgain:
 
 	// determine token value
 	insertSemi := false
+	if s.needUnit { // number with unit
+		insertSemi = true
+		tok, lit = token.UNIT, s.scanIdentifier()
+		s.needUnit = false
+		goto done
+	}
 	switch ch := s.ch; {
 	case isLetter(ch):
 		lit = s.scanIdentifier()
@@ -1021,9 +1030,10 @@ scanAgain:
 			lit = string(ch)
 		}
 	}
+
+done:
 	if s.mode&dontInsertSemis == 0 {
 		s.insertSemi = insertSemi
 	}
-
 	return
 }
diff --git a/token/token.go b/token/token.go
index 56e3a958..aaa2a07a 100644
--- a/token/token.go
+++ b/token/token.go
@@ -142,11 +142,13 @@ const (
 	TILDE // additional tokens, handled in an ad-hoc manner
 	additional_end
 	additional_end2
+	additional_end3
 
 	additional_literal_beg = 96
 	additional_literal_end = 97
 
-	ENV = additional_end2 // ${name}
+	ENV  = additional_end2 // ${name}
+	UNIT = additional_end3 // 1m, 2s, 3ms, 4us, 5ns, 6m, 7h, 8d, 9w, 10y
 
 	PYSTRING = additional_literal_beg // py"Hello"
 
diff --git a/_gendeps.go b/tool/_gendeps.go
similarity index 99%
rename from _gendeps.go
rename to tool/_gendeps.go
index 1c80fc29..7a2c4389 100644
--- a/_gendeps.go
+++ b/tool/_gendeps.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"fmt"
diff --git a/build_install_run.go b/tool/build_install_run.go
similarity index 88%
rename from build_install_run.go
rename to tool/build_install_run.go
index b0c6d138..1f66f2d9 100644
--- a/build_install_run.go
+++ b/tool/build_install_run.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"log"
@@ -37,7 +37,7 @@ func genFlags(flags []GenFlags) GenFlags {
 func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
 	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
+		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false)
 	}
 	return gocmd.Install(dir, install)
 }
@@ -46,7 +46,7 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig, flags ..
 func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
 	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, true, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, true)
+		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, true)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, true)
 	}
 	old := chdir(localDir)
 	defer os.Chdir(old)
@@ -64,7 +64,7 @@ func cwdParam(recursively bool) string {
 func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
-		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "gop.GenGoFiles", "", files, conf)
+		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf)
 	}
 	return gocmd.InstallFiles(files, install)
 }
@@ -87,7 +87,7 @@ func chdir(dir string) string {
 func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
 	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
+		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false)
 	}
 	return gocmd.Build(dir, build)
 }
@@ -96,7 +96,7 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig, flags ...GenFl
 func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
 	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
+		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
 	old, mod := chdirAndMod(localDir)
 	defer restoreDirAndMod(old, mod)
@@ -107,7 +107,7 @@ func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfi
 func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
-		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "gop.GenGoFiles", "", files, conf)
+		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf)
 	}
 	return gocmd.BuildFiles(files, build)
 }
@@ -141,7 +141,7 @@ func getBuildDir(conf *Config) string {
 func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
 	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
+		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false)
 	}
 	return gocmd.RunDir(getBuildDir(conf), dir, args, run)
 }
@@ -150,7 +150,7 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig, flags
 func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
 	localDir, recursively, err := GenGoPkgPathEx("", pkgPath, conf, true, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGoPkgPath("", pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", "", pkgPath, conf, true)
+		return errors.NewWith(err, `GenGoPkgPath("", pkgPath, conf, true)`, -2, "tool.GenGoPkgPath", "", pkgPath, conf, true)
 	}
 	if recursively {
 		return errors.NewWith(errors.New("can't use ... pattern for `gop run` command"), `recursively`, -1, "", recursively)
@@ -167,7 +167,7 @@ func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *go
 func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
 	files, err = GenGoFiles(autogen, files, conf)
 	if err != nil {
-		return errors.NewWith(err, `GenGoFiles(autogen, files, conf)`, -2, "gop.GenGoFiles", autogen, files, conf)
+		return errors.NewWith(err, `GenGoFiles(autogen, files, conf)`, -2, "tool.GenGoFiles", autogen, files, conf)
 	}
 	return gocmd.RunFiles(getBuildDir(conf), files, args, run)
 }
@@ -178,7 +178,7 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 func TestDir(dir string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
 	_, _, err = GenGoEx(dir, conf, true, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGo(dir, conf, true)`, -2, "gop.GenGo", dir, conf, true)
+		return errors.NewWith(err, `GenGo(dir, conf, true)`, -2, "tool.GenGo", dir, conf, true)
 	}
 	return gocmd.Test(dir, test)
 }
@@ -187,7 +187,7 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags
 func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
 	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
-		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
+		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
 	old, mod := chdirAndMod(localDir)
 	defer restoreDirAndMod(old, mod)
@@ -198,7 +198,7 @@ func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig,
 func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
-		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "gop.GenGoFiles", "", files, conf)
+		return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf)
 	}
 	return gocmd.TestFiles(files, test)
 }
diff --git a/gengo.go b/tool/gengo.go
similarity index 97%
rename from gengo.go
rename to tool/gengo.go
index 336bf21e..2d091117 100644
--- a/gengo.go
+++ b/tool/gengo.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"fmt"
@@ -154,7 +154,7 @@ func genGoSingleFile(file string, conf *Config, flags GenFlags) (err error) {
 	}
 	out, err := LoadFiles(".", []string{file}, conf)
 	if err != nil {
-		return errors.NewWith(err, `LoadFiles(files, conf)`, -2, "gop.LoadFiles", file)
+		return errors.NewWith(err, `LoadFiles(files, conf)`, -2, "tool.LoadFiles", file)
 	}
 	if flags&GenFlagCheckOnly != 0 {
 		return nil
@@ -171,7 +171,7 @@ func genGoIn(dir string, conf *Config, genTestPkg bool, flags GenFlags, gen ...*
 		if NotFound(err) { // no Go+ source files
 			return nil
 		}
-		return errors.NewWith(err, `LoadDir(dir, conf, genTestPkg)`, -5, "gop.LoadDir", dir, conf, genTestPkg)
+		return errors.NewWith(err, `LoadDir(dir, conf, genTestPkg)`, -5, "tool.LoadDir", dir, conf, genTestPkg)
 	}
 	if flags&GenFlagCheckOnly != 0 {
 		return nil
@@ -288,7 +288,7 @@ func GenGoFiles(autogen string, files []string, conf *Config) (outFiles []string
 	}
 	out, err := LoadFiles(".", files, conf)
 	if err != nil {
-		err = errors.NewWith(err, `LoadFiles(files, conf)`, -2, "gop.LoadFiles", files, conf)
+		err = errors.NewWith(err, `LoadFiles(files, conf)`, -2, "tool.LoadFiles", files, conf)
 		return
 	}
 	err = out.WriteFile(autogen)
diff --git a/imp.go b/tool/imp.go
similarity index 96%
rename from imp.go
rename to tool/imp.go
index 6a1d01f8..6c7c3239 100644
--- a/imp.go
+++ b/tool/imp.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"crypto/sha256"
@@ -71,6 +71,13 @@ func NewImporter(mod *gopmod.Module, gop *env.Gop, fset *token.FileSet) *Importe
 	return ret
 }
 
+func (p *Importer) SetTags(tags string) {
+	p.impFrom.SetTags(tags)
+	if c, ok := p.impFrom.Cache().(*cache.Impl); ok {
+		c.SetTags(tags)
+	}
+}
+
 // CacheFile returns file path of the cache.
 func (p *Importer) CacheFile() string {
 	cacheDir, _ := os.UserCacheDir()
@@ -83,6 +90,9 @@ func (p *Importer) CacheFile() string {
 		io.WriteString(h, root)
 		fname = filepath.Base(root)
 	}
+	if tags := p.impFrom.Tags(); tags != "" {
+		io.WriteString(h, tags)
+	}
 	hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
 	return cacheDir + hash + fname
 }
diff --git a/load.go b/tool/load.go
similarity index 96%
rename from load.go
rename to tool/load.go
index f6c3cf46..185bee9c 100644
--- a/load.go
+++ b/tool/load.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"fmt"
@@ -145,7 +145,7 @@ const (
 )
 
 // NewDefaultConf creates a dfault configuration for common cases.
-func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) {
+func NewDefaultConf(dir string, flags ConfFlags, tags ...string) (conf *Config, err error) {
 	mod, err := LoadMod(dir)
 	if err != nil {
 		return
@@ -153,6 +153,9 @@ func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) {
 	gop := gopenv.Get()
 	fset := token.NewFileSet()
 	imp := NewImporter(mod, gop, fset)
+	if len(tags) > 0 {
+		imp.SetTags(strings.Join(tags, ","))
+	}
 	conf = &Config{
 		Gop: gop, Fset: fset, Mod: mod, Importer: imp,
 		IgnoreNotatedError: flags&ConfFlagIgnoreNotatedError != 0,
@@ -235,7 +238,7 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	mod := conf.Mod
 	if mod == nil {
 		if mod, err = LoadMod(dir); err != nil {
-			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir)
 			return
 		}
 	}
@@ -355,7 +358,7 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gogen.Package, er
 	mod := conf.Mod
 	if mod == nil {
 		if mod, err = LoadMod(dir); err != nil {
-			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir)
 			return
 		}
 	}
diff --git a/outline.go b/tool/outline.go
similarity index 95%
rename from outline.go
rename to tool/outline.go
index 99b059ff..8b0b4b36 100644
--- a/outline.go
+++ b/tool/outline.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"fmt"
@@ -46,7 +46,7 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 	mod := conf.Mod
 	if mod == nil {
 		if mod, err = LoadMod(dir); err != nil {
-			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir)
 			return
 		}
 	}
@@ -119,7 +119,7 @@ func OutlinePkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (ou
 	mod := conf.Mod
 	if mod == nil {
 		if mod, err = LoadMod(workDir); err != nil {
-			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", workDir)
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", workDir)
 			return
 		}
 	}
diff --git a/tidy.go b/tool/tidy.go
similarity index 94%
rename from tidy.go
rename to tool/tidy.go
index 1e2f2c11..7df72376 100644
--- a/tidy.go
+++ b/tool/tidy.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package gop
+package tool
 
 import (
 	"os"
@@ -35,7 +35,7 @@ func Tidy(dir string, gop *env.Gop) (err error) {
 	/*
 		depMods, err := GenDepMods(modObj, modRoot, true)
 		if err != nil {
-			return errors.NewWith(err, `GenDepMods(modObj, modRoot, true)`, -2, "gop.GenDepMods", modObj, modRoot, true)
+			return errors.NewWith(err, `GenDepMods(modObj, modRoot, true)`, -2, "tool.GenDepMods", modObj, modRoot, true)
 		}
 
 		old := modObj.DepMods()
@@ -63,7 +63,7 @@ func Tidy(dir string, gop *env.Gop) (err error) {
 	conf := &Config{Gop: gop}
 	err = genGoDir(modRoot, conf, true, true, 0)
 	if err != nil {
-		return errors.NewWith(err, `genGoDir(modRoot, conf, true, true)`, -2, "gop.genGoDir", modRoot, conf, true, true)
+		return errors.NewWith(err, `genGoDir(modRoot, conf, true, true)`, -2, "tool.genGoDir", modRoot, conf, true, true)
 	}
 
 	cmd := exec.Command("go", "mod", "tidy")
diff --git a/x/format/README.md b/x/format/README.md
index 5276fd6c..c902e40c 100644
--- a/x/format/README.md
+++ b/x/format/README.md
@@ -51,7 +51,7 @@ n, err := fmt.Println("Hello world")
 will be converted into:
 
 ```go
-n, err := println("Hello world")
+n, err := echo("Hello world")
 ```
 
 Note:
@@ -62,7 +62,7 @@ Note:
 * Convert `fmt.Fprintln => fprintln`
 * Convert `fmt.Print` => `print`
 * Convert `fmt.Printf` => `printf`
-* Convert `fmt.Println` => `println`
+* Convert `fmt.Println` => `echo`
 * Convert `fmt.Sprint => sprint`
 * Convert `fmt.Sprintf => sprintf`
 * Convert `fmt.Sprintln => sprintln`
@@ -79,13 +79,13 @@ fmt.Println(fmt.Println("Hello world"))
 will be converted into:
 
 ```go
-println
-println println("Hello world")
+echo
+echo echo("Hello world")
 ```
 
 Note:
 
-* Only the outermost function call statement is converted into command style. So `fmt.Println(fmt.Println("Hello world"))` is converted into `println println("Hello world")`, not `println println "Hello world"`.
+* Only the outermost function call statement is converted into command style. So `fmt.Println(fmt.Println("Hello world"))` is converted into `echo echo("Hello world")`, not `echo echo "Hello world"`.
 
 
 ### pkg.Fncall starting with lowercase
@@ -93,11 +93,40 @@ Note:
 ```go
 import "math"
 
-println math.Sin(math.Pi/3)
+echo math.Sin(math.Pi/3)
 ```
 
 will be converted into:
 
 ```go
-println math.sin(math.Pi/3)
+echo math.sin(math.Pi/3)
+```
+
+### Funclit of params convert to lambda in fncall (skip named results)
+
+```go
+echo(demo(func(n int) int {
+	return n+100
+}))
+
+echo(demo(func(n int) (v int) {
+	return n+100
+}))
+
+onStart(func() {
+	echo("start")
+})
+```
+
+will be converted into:
+```
+echo demo(n => n + 100)
+
+echo demo(func(n int) (v int) {
+	return n + 100
+})
+
+onStart => {
+	echo "start"
+}
 ```
diff --git a/x/format/format.go b/x/format/format.go
index eefbdc01..0d27a7a2 100644
--- a/x/format/format.go
+++ b/x/format/format.go
@@ -40,7 +40,11 @@ func fmtToBuiltin(ctx *importCtx, sel *ast.Ident, ref *ast.Expr) bool {
 	if ctx.pkgPath == "fmt" {
 		for _, fns := range printFuncs {
 			if fns[0] == sel.Name || fns[1] == sel.Name {
-				*ref = &ast.Ident{NamePos: sel.NamePos, Name: fns[1]}
+				name := fns[1]
+				if name == "println" {
+					name = "echo"
+				}
+				*ref = &ast.Ident{NamePos: sel.NamePos, Name: name}
 				return true
 			}
 		}
@@ -48,6 +52,8 @@ func fmtToBuiltin(ctx *importCtx, sel *ast.Ident, ref *ast.Expr) bool {
 	return false
 }
 
+// -----------------------------------------------------------------------------
+
 func commandStyleFirst(v *ast.CallExpr) {
 	switch v.Fun.(type) {
 	case *ast.Ident, *ast.SelectorExpr:
@@ -57,6 +63,8 @@ func commandStyleFirst(v *ast.CallExpr) {
 	}
 }
 
+// -----------------------------------------------------------------------------
+
 func fncallStartingLowerCase(v *ast.CallExpr) {
 	switch fn := v.Fun.(type) {
 	case *ast.SelectorExpr:
@@ -65,3 +73,41 @@ func fncallStartingLowerCase(v *ast.CallExpr) {
 }
 
 // -----------------------------------------------------------------------------
+
+func funcLitToLambdaExpr(v *ast.FuncLit, ret *ast.Expr) {
+	nres, named := checkResult(v.Type.Results)
+	if len(named) > 0 {
+		return
+	}
+	var lsh []*ast.Ident
+	for _, p := range v.Type.Params.List {
+		if p.Names == nil {
+			lsh = append(lsh, ast.NewIdent("_"))
+		} else {
+			lsh = append(lsh, p.Names...)
+		}
+	}
+	if len(v.Body.List) == 1 {
+		if stmt, ok := v.Body.List[0].(*ast.ReturnStmt); ok && len(stmt.Results) == nres {
+			*ret = &ast.LambdaExpr{First: v.Pos(), Last: v.Pos(), Lhs: lsh, Rhs: stmt.Results, LhsHasParen: len(lsh) > 1, RhsHasParen: len(stmt.Results) > 1}
+			return
+		}
+	}
+	*ret = &ast.LambdaExpr2{Lhs: lsh, Body: v.Body, LhsHasParen: len(lsh) > 1}
+}
+
+func checkResult(v *ast.FieldList) (nres int, named []*ast.Ident) {
+	if v != nil {
+		for _, f := range v.List {
+			if f.Names == nil {
+				nres++
+			} else {
+				nres += len(f.Names)
+				named = append(named, f.Names...)
+			}
+		}
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go
index eb2a758e..b5e13c65 100644
--- a/x/format/stmt_expr_or_type.go
+++ b/x/format/stmt_expr_or_type.go
@@ -168,6 +168,11 @@ func formatSliceExpr(ctx *formatCtx, v *ast.SliceExpr) {
 func formatCallExpr(ctx *formatCtx, v *ast.CallExpr) {
 	formatExpr(ctx, v.Fun, &v.Fun)
 	fncallStartingLowerCase(v)
+	for i, arg := range v.Args {
+		if fn, ok := arg.(*ast.FuncLit); ok {
+			funcLitToLambdaExpr(fn, &v.Args[i])
+		}
+	}
 	formatExprs(ctx, v.Args)
 }
 
diff --git a/x/langserver/server.go b/x/langserver/server.go
index 1e26e6be..47de37cb 100644
--- a/x/langserver/server.go
+++ b/x/langserver/server.go
@@ -23,7 +23,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/goplus/gop"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gop/x/jsonrpc2"
 )
@@ -99,7 +99,7 @@ func (p *handler) runLoop() {
 			time.Sleep(duration)
 			continue
 		}
-		gop.GenGoEx(dir, nil, true, gop.GenFlagPrompt)
+		tool.GenGoEx(dir, nil, true, tool.GenFlagPrompt)
 	}
 }
 
@@ -138,19 +138,19 @@ func GenGo(pattern ...string) (err error) {
 	if err != nil {
 		return
 	}
-	conf, _ := gop.NewDefaultConf(".", 0)
+	conf, _ := tool.NewDefaultConf(".", 0)
 	if conf != nil {
 		defer conf.UpdateCache()
 	}
 	for _, proj := range projs {
 		switch v := proj.(type) {
 		case *gopprojs.DirProj:
-			gop.GenGoEx(v.Dir, conf, true, 0)
+			tool.GenGoEx(v.Dir, conf, true, 0)
 		case *gopprojs.PkgPathProj:
 			if v.Path == "builtin" {
 				continue
 			}
-			gop.GenGoPkgPathEx("", v.Path, conf, true, 0)
+			tool.GenGoPkgPathEx("", v.Path, conf, true, 0)
 		}
 	}
 	return


Test patch:
diff --git a/cl/_testgop/matrix/in.gop b/cl/_testgop/_matrix/in.gop
similarity index 100%
rename from cl/_testgop/matrix/in.gop
rename to cl/_testgop/_matrix/in.gop
diff --git a/cl/_testgop/append/in.gop b/cl/_testgop/append/in.gop
new file mode 100644
index 00000000..5956a8ab
--- /dev/null
+++ b/cl/_testgop/append/in.gop
@@ -0,0 +1,14 @@
+type foo struct {
+	a []int
+}
+
+a := [1, 2, 3]
+a <- 4
+echo a
+
+f := foo{a: [1, 2, 3]}
+f.a <- 4
+echo f
+
+f2 := [2]chan int{}
+f2[0] <- 4
diff --git a/cl/_testgop/append/out.go b/cl/_testgop/append/out.go
new file mode 100644
index 00000000..e0dcabed
--- /dev/null
+++ b/cl/_testgop/append/out.go
@@ -0,0 +1,18 @@
+package main
+
+import "fmt"
+
+type foo struct {
+	a []int
+}
+
+func main() {
+	a := []int{1, 2, 3}
+	a = append(a, 4)
+	fmt.Println(a)
+	f := foo{a: []int{1, 2, 3}}
+	f.a = append(f.a, 4)
+	fmt.Println(f)
+	f2 := [2]chan int{}
+	f2[0] <- 4
+}
diff --git a/cl/_testgop/cap/in.gop b/cl/_testgop/cap/in.gop
new file mode 100644
index 00000000..300af174
--- /dev/null
+++ b/cl/_testgop/cap/in.gop
@@ -0,0 +1,2 @@
+a := ["hello", "world", "123"]
+echo a.capitalize
diff --git a/cl/_testgop/cap/out.go b/cl/_testgop/cap/out.go
new file mode 100644
index 00000000..fc418f06
--- /dev/null
+++ b/cl/_testgop/cap/out.go
@@ -0,0 +1,11 @@
+package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/builtin/stringslice"
+)
+
+func main() {
+	a := []string{"hello", "world", "123"}
+	fmt.Println(stringslice.Capitalize(a))
+}
diff --git a/cl/_testgop/unit/in.gop b/cl/_testgop/unit/in.gop
new file mode 100644
index 00000000..87d209a7
--- /dev/null
+++ b/cl/_testgop/unit/in.gop
@@ -0,0 +1,12 @@
+import (
+    "time"
+
+    "github.com/goplus/gop/cl/internal/unit"
+)
+
+func Wait(time.Duration) {}
+func Step(unit.Distance) {}
+
+wait 0.5µs
+wait 1m
+step 1m
diff --git a/cl/_testgop/unit/out.go b/cl/_testgop/unit/out.go
new file mode 100644
index 00000000..3dcd42d7
--- /dev/null
+++ b/cl/_testgop/unit/out.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+	"github.com/goplus/gop/cl/internal/unit"
+	"time"
+)
+
+func Wait(time.Duration) {
+}
+func Step(unit.Distance) {
+}
+func main() {
+	Wait(500)
+	Wait(60000000000)
+	Step(1000)
+}
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
index a2c7f3e9..013908ca 100644
--- a/cl/cltest/cltest.go
+++ b/cl/cltest/cltest.go
@@ -26,13 +26,13 @@ import (
 	"testing"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/fsx"
 	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/modfile"
 )
@@ -47,7 +47,7 @@ func init() {
 	gogen.SetDebug(gogen.DbgFlagAll)
 	cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen)
 	fset := token.NewFileSet()
-	imp := gop.NewImporter(nil, Gop, fset)
+	imp := tool.NewImporter(nil, Gop, fset)
 	Conf = &cl.Config{
 		Fset:          fset,
 		Importer:      imp,
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index 73874dfe..d95d39f6 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -1570,3 +1570,122 @@ func main() {
 }
 `)
 }
+
+func TestOverlodOptions(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+type PlayOptions struct {
+	Action int
+	Wait   bool
+	Loop   bool
+}
+type Game struct {
+}
+func (g *Game) Play__0(options *PlayOptions) {
+}
+func (g *Game) Play__1(name string, options *PlayOptions) {
+}
+`, `
+g := &Game{}
+g.play "work", { Action: 0, Loop: true }
+`, `package main
+
+func main() {
+	g := &Game{}
+	g.Play__1("work", &PlayOptions{Action: 0, Loop: true})
+}
+`)
+}
+
+func TestEmbedField(t *testing.T) {
+	gopClTest(t, `package main
+
+type Info struct {
+	id int
+}
+type T struct {
+	Info
+	id string
+}
+func demo(t *T) {
+	t.id = "0"
+}
+func main() {
+}
+`, `package main
+
+type Info struct {
+	id int
+}
+type T struct {
+	Info
+	id string
+}
+
+func demo(t *T) {
+	t.id = "0"
+}
+func main() {
+}
+`)
+}
+
+func TestOverloadUntyped(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+type specialObj int
+type SpriteName string
+
+type SpriteImpl struct {
+}
+
+func (p *SpriteImpl) turn(v any) {
+}
+func (p *SpriteImpl) TurnTo__0(sprite *SpriteImpl) {
+}
+func (p *SpriteImpl) TurnTo__1(sprite SpriteName) {
+}
+func (p *SpriteImpl) TurnTo__2(obj specialObj) {
+}
+func (p *SpriteImpl) TurnTo__3(degree float64) {
+}
+`, `
+p := &SpriteImpl{}
+p.turnTo 180.0
+p.turnTo 180.1
+`, `package main
+
+func main() {
+	p := &SpriteImpl{}
+	p.TurnTo__2(180.0)
+	p.TurnTo__3(180.1)
+}
+`)
+}
+
+func TestOverloadUntyped2(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+type SpriteImpl struct {
+}
+
+func (p *SpriteImpl) Rand__0(from int, to int) {
+}
+func (p *SpriteImpl) Rand__1(from float64, to float64) {
+}
+`, `
+p := &SpriteImpl{}
+p.rand(1.0,2.0)
+p.rand(float64(1),float64(2))
+`, `package main
+
+func main() {
+	p := &SpriteImpl{}
+	p.Rand__0(1.0, 2.0)
+	p.Rand__1(float64(1), float64(2))
+}
+`)
+}
diff --git a/cl/compile_testdir_test.go b/cl/compile_testdir_test.go
index 122cf720..f41cd71c 100644
--- a/cl/compile_testdir_test.go
+++ b/cl/compile_testdir_test.go
@@ -22,6 +22,10 @@ import (
 	"github.com/goplus/gop/cl/cltest"
 )
 
+func TestTestgop(t *testing.T) {
+	cltest.FromDir(t, "", "./_testgop")
+}
+
 func TestTestc(t *testing.T) {
 	cltest.FromDir(t, "", "./_testc")
 }
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index 40a079d3..b1a679e0 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022 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 cl_test
 
 import (
diff --git a/cmd/chore/goptestgo/goptestgo.go b/cmd/chore/goptestgo/goptestgo.go
index 43f426e4..ce216ad2 100644
--- a/cmd/chore/goptestgo/goptestgo.go
+++ b/cmd/chore/goptestgo/goptestgo.go
@@ -28,7 +28,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/goplus/gop"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopenv"
 )
@@ -43,7 +43,7 @@ func fileIsDirty(srcMod time.Time, destFile string) bool {
 
 func runGoFile(dir, file, fname string) {
 	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
+	conf := &tool.Config{Gop: gopEnv}
 	confCmd := &gocmd.BuildConfig{Gop: gopEnv}
 	fi, err := os.Stat(file)
 	if err != nil {
@@ -53,7 +53,7 @@ func runGoFile(dir, file, fname string) {
 	hash := sha1.Sum([]byte(absFile))
 	outFile := dir + "g" + base64.RawURLEncoding.EncodeToString(hash[:]) + fname
 	if fileIsDirty(fi.ModTime(), outFile) {
-		err = gop.RunFiles(outFile, []string{file}, nil, conf, confCmd)
+		err = tool.RunFiles(outFile, []string{file}, nil, conf, confCmd)
 		if err != nil {
 			os.Remove(outFile)
 			switch e := err.(type) {
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index e9bf58c8..add1de0b 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -24,9 +24,9 @@ import (
 	"reflect"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopprojs"
 )
@@ -69,9 +69,9 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", 0)
+	conf, err := tool.NewDefaultConf(".", 0, pass.Tags())
 	if err != nil {
-		log.Panicln("gop.NewDefaultConf:", err)
+		log.Panicln("tool.NewDefaultConf:", err)
 	}
 	defer conf.UpdateCache()
 
@@ -82,23 +82,23 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 }
 
-func test(proj gopprojs.Proj, conf *gop.Config, test *gocmd.TestConfig) {
-	const flags = gop.GenFlagPrompt
+func test(proj gopprojs.Proj, conf *tool.Config, test *gocmd.TestConfig) {
+	const flags = tool.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.TestDir(obj, conf, test, flags)
+		err = tool.TestDir(obj, conf, test, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.TestPkgPath("", v.Path, conf, test, flags)
+		err = tool.TestPkgPath("", v.Path, conf, test, flags)
 	case *gopprojs.FilesProj:
-		err = gop.TestFiles(v.Files, conf, test)
+		err = tool.TestFiles(v.Files, conf, test)
 	default:
 		log.Panicln("`gop test` doesn't support", reflect.TypeOf(v))
 	}
-	if gop.NotFound(err) {
+	if tool.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop test %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/format/formatutil/_testdata/format/basic/in.data b/format/formatutil/_testdata/format/basic/in.data
new file mode 100644
index 00000000..7f3e2a0e
--- /dev/null
+++ b/format/formatutil/_testdata/format/basic/in.data
@@ -0,0 +1,20 @@
+goto "a"
+
+// this is a comment
+
+// x comment 1
+// x comment 2
+// x comment 3
+func x() {
+}
+
+// call
+func(a func()) {}(nil)
+
+/*
+ y comment
+*/
+func y() {
+}
+
+echo "Hi"
diff --git a/format/formatutil/_testdata/format/basic/out.expect b/format/formatutil/_testdata/format/basic/out.expect
new file mode 100644
index 00000000..b5b0bc36
--- /dev/null
+++ b/format/formatutil/_testdata/format/basic/out.expect
@@ -0,0 +1,20 @@
+// this is a comment
+
+// x comment 1
+// x comment 2
+// x comment 3
+func x() {
+}
+
+/*
+ y comment
+*/
+func y() {
+}
+
+goto "a"
+
+// call
+func(a func()) {}(nil)
+
+echo "Hi"
diff --git a/format/formatutil/_testdata/format/nondecl/in.data b/format/formatutil/_testdata/format/nondecl/in.data
new file mode 100644
index 00000000..2b7225e6
--- /dev/null
+++ b/format/formatutil/_testdata/format/nondecl/in.data
@@ -0,0 +1,1 @@
+const a = 0
diff --git a/format/formatutil/_testdata/format/nondecl/out.expect b/format/formatutil/_testdata/format/nondecl/out.expect
new file mode 100644
index 00000000..2b7225e6
--- /dev/null
+++ b/format/formatutil/_testdata/format/nondecl/out.expect
@@ -0,0 +1,1 @@
+const a = 0
diff --git a/format/formatutil/_testdata/rearrange/noeol/in.data b/format/formatutil/_testdata/rearrange/noeol/in.data
new file mode 100644
index 00000000..f6c8d2be
--- /dev/null
+++ b/format/formatutil/_testdata/rearrange/noeol/in.data
@@ -0,0 +1,1 @@
+var a int
\ No newline at end of file
diff --git a/format/formatutil/_testdata/rearrange/noeol/out.expect b/format/formatutil/_testdata/rearrange/noeol/out.expect
new file mode 100644
index 00000000..f6c8d2be
--- /dev/null
+++ b/format/formatutil/_testdata/rearrange/noeol/out.expect
@@ -0,0 +1,1 @@
+var a int
\ No newline at end of file
diff --git a/format/formatutil/_testdata/rearrange/nondecl/in.data b/format/formatutil/_testdata/rearrange/nondecl/in.data
new file mode 100644
index 00000000..a333d186
--- /dev/null
+++ b/format/formatutil/_testdata/rearrange/nondecl/in.data
@@ -0,0 +1,12 @@
+// this is a comment
+
+// x comment 1
+// x comment 2
+// x comment 3
+func x() {
+}
+
+/*
+ y comment
+*/
+var y int
diff --git a/format/formatutil/_testdata/rearrange/nondecl/out.expect b/format/formatutil/_testdata/rearrange/nondecl/out.expect
new file mode 100644
index 00000000..a333d186
--- /dev/null
+++ b/format/formatutil/_testdata/rearrange/nondecl/out.expect
@@ -0,0 +1,12 @@
+// this is a comment
+
+// x comment 1
+// x comment 2
+// x comment 3
+func x() {
+}
+
+/*
+ y comment
+*/
+var y int
diff --git a/format/formatutil/_testdata/splitstmts/basic/in.data b/format/formatutil/_testdata/splitstmts/basic/in.data
new file mode 100644
index 00000000..7f3e2a0e
--- /dev/null
+++ b/format/formatutil/_testdata/splitstmts/basic/in.data
@@ -0,0 +1,20 @@
+goto "a"
+
+// this is a comment
+
+// x comment 1
+// x comment 2
+// x comment 3
+func x() {
+}
+
+// call
+func(a func()) {}(nil)
+
+/*
+ y comment
+*/
+func y() {
+}
+
+echo "Hi"
diff --git a/format/formatutil/_testdata/splitstmts/basic/out.expect b/format/formatutil/_testdata/splitstmts/basic/out.expect
new file mode 100644
index 00000000..6ef6ad46
--- /dev/null
+++ b/format/formatutil/_testdata/splitstmts/basic/out.expect
@@ -0,0 +1,5 @@
+goto
+FUNC
+FNCALL
+FUNC
+IDENT
diff --git a/format/formatutil/format_test.go b/format/formatutil/format_test.go
new file mode 100644
index 00000000..7765322c
--- /dev/null
+++ b/format/formatutil/format_test.go
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2025 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 formatutil
+
+import (
+	"bytes"
+	"log"
+	"os"
+	"path"
+	"runtime"
+	"strings"
+	"testing"
+
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gop/token"
+)
+
+func TestSeekAfter(t *testing.T) {
+	if seekAfter(nil, 0, 0) != nil {
+		t.Fatal("seekAfter failed")
+	}
+}
+
+func TestTokOf(t *testing.T) {
+	words := []aWord{{tok: token.COMMENT}}
+	if tok, _ := tokOf(words); token.COMMENT != tok {
+		t.Fatal("tokOf failed:", tok)
+	}
+	if startWith(words, token.VAR) {
+		t.Fatal("startWith failed")
+	}
+}
+
+func doSplitStmts(src []byte) (ret []string) {
+	fset := token.NewFileSet()
+	base := fset.Base()
+	f := fset.AddFile("", base, len(src))
+
+	var s scanner.Scanner
+	s.Init(f, src, nil, scanner.ScanComments)
+	stmts := splitStmts(&s)
+
+	ret = make([]string, len(stmts))
+	for i, stmt := range stmts {
+		ret[i] = stmtKind(stmt)
+	}
+	return
+}
+
+func stmtKind(s aStmt) string {
+	tok := s.tok
+	if tok == token.FUNC {
+		if isFuncDecl(s.words[s.at+1:]) {
+			return "FUNC"
+		}
+		return "FNCALL"
+	}
+	return tok.String()
+}
+
+func testFrom(t *testing.T, pkgDir, sel string, doIt func(in []byte) ([]byte, error)) {
+	if sel != "" && !strings.Contains(pkgDir, sel) {
+		return
+	}
+	t.Helper()
+	log.Println("Parsing", pkgDir)
+	in, err := os.ReadFile(pkgDir + "/in.data")
+	if err != nil {
+		t.Fatal("Parsing", pkgDir, "-", err)
+	}
+	out, err := os.ReadFile(pkgDir + "/out.expect")
+	if err != nil {
+		t.Fatal("Parsing", pkgDir, "-", err)
+	}
+	ret, err := doIt(in)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(ret, out) {
+		t.Fatal("Parsing", pkgDir, "- failed:\n"+string(ret))
+	}
+}
+
+func testFromDir(t *testing.T, sel, relDir string, doIt func(in []byte) ([]byte, error)) {
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir = path.Join(dir, relDir)
+	fis, err := os.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, doIt)
+		})
+	}
+}
+
+func TestSplitStmts(t *testing.T) {
+	testFromDir(t, "", "./_testdata/splitstmts", func(in []byte) ([]byte, error) {
+		ret := strings.Join(doSplitStmts(in), "\n") + "\n"
+		return []byte(ret), nil
+	})
+}
+
+func TestRearrangeFuncs(t *testing.T) {
+	if runtime.GOOS == "windows" { // skip temporarily
+		return
+	}
+	testFromDir(t, "", "./_testdata/rearrange", func(in []byte) ([]byte, error) {
+		return RearrangeFuncs(in)
+	})
+}
+
+func TestFormat(t *testing.T) {
+	testFromDir(t, "", "./_testdata/format", func(in []byte) ([]byte, error) {
+		return SourceEx(in, false, "")
+	})
+}
diff --git a/parser/_testdata/goto1/goto.gop b/parser/_testdata/goto1/goto.gop
new file mode 100644
index 00000000..1d7fac5a
--- /dev/null
+++ b/parser/_testdata/goto1/goto.gop
@@ -0,0 +1,3 @@
+goto "a"
+goto 1, 2
+goto (1+2)*3
diff --git a/parser/_testdata/goto1/parser.expect b/parser/_testdata/goto1/parser.expect
new file mode 100644
index 00000000..bb64e8d4
--- /dev/null
+++ b/parser/_testdata/goto1/parser.expect
@@ -0,0 +1,64 @@
+package main
+
+file goto.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 2
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BinaryExpr:
+                  X:
+                    ast.ParenExpr:
+                      X:
+                        ast.BinaryExpr:
+                          X:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 1
+                          Op: +
+                          Y:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 2
+                  Op: *
+                  Y:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 3
diff --git a/parser/_testdata/goto2/goto.gop b/parser/_testdata/goto2/goto.gop
new file mode 100644
index 00000000..a8f11b6f
--- /dev/null
+++ b/parser/_testdata/goto2/goto.gop
@@ -0,0 +1,2 @@
+goto x+y
+goto x, y
diff --git a/parser/_testdata/goto2/parser.expect b/parser/_testdata/goto2/parser.expect
new file mode 100644
index 00000000..4beebaff
--- /dev/null
+++ b/parser/_testdata/goto2/parser.expect
@@ -0,0 +1,41 @@
+package main
+
+file goto.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: x
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: y
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: goto
+              Args:
+                ast.Ident:
+                  Name: x
+                ast.Ident:
+                  Name: y
diff --git a/parser/_testdata/unit/parser.expect b/parser/_testdata/unit/parser.expect
new file mode 100644
index 00000000..5f01ff09
--- /dev/null
+++ b/parser/_testdata/unit/parser.expect
@@ -0,0 +1,26 @@
+package main
+
+file step.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: wait
+              Args:
+                ast.NumberUnitLit:
+                  Kind: INT
+                  Value: 1
+                  Unit: µs
diff --git a/parser/_testdata/unit/step.gop b/parser/_testdata/unit/step.gop
new file mode 100644
index 00000000..b0f1553d
--- /dev/null
+++ b/parser/_testdata/unit/step.gop
@@ -0,0 +1,1 @@
+wait 1µs
diff --git a/parser/parser_test.go b/parser/parser_test.go
index e0815f3b..94e1dbab 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -339,4 +339,10 @@ func (a T) +{}
 `, `/foo/bar.gop:1:6: overload operator can only have one parameter`, ``)
 }
 
+func TestNumberUnitLit(t *testing.T) {
+	var p parser
+	p.checkExpr(&ast.NumberUnitLit{})
+	p.toIdent(&ast.NumberUnitLit{})
+}
+
 // -----------------------------------------------------------------------------
diff --git a/x/format/_testdata/collection/format.expect b/x/format/_testdata/collection/format.expect
index d4f69322..900ab074 100644
--- a/x/format/_testdata/collection/format.expect
+++ b/x/format/_testdata/collection/format.expect
@@ -84,23 +84,17 @@ func Map(vs []string, f func(string) string) []string {
 // Here we try out our various collection functions.
 var strs = []string{"peach", "apple", "pear", "plum"}
 
-println Index(strs, "pear")
+echo Index(strs, "pear")
 
-println Include(strs, "grape")
+echo Include(strs, "grape")
 
-println Any(strs, func(v string) bool {
-	return strings.hasPrefix(v, "p")
-})
+echo Any(strs, v => strings.hasPrefix(v, "p"))
 
-println All(strs, func(v string) bool {
-	return strings.hasPrefix(v, "p")
-})
+echo All(strs, v => strings.hasPrefix(v, "p"))
 
-println Filter(strs, func(v string) bool {
-	return strings.contains(v, "e")
-})
+echo Filter(strs, v => 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)
+echo Map(strs, strings.ToUpper)
diff --git a/x/format/_testdata/syntax/format.expect b/x/format/_testdata/syntax/format.expect
index 52884fd9..74399e16 100644
--- a/x/format/_testdata/syntax/format.expect
+++ b/x/format/_testdata/syntax/format.expect
@@ -7,7 +7,7 @@ m := make(map[string]chan interface{})
 for range arr[1:] {
 foo:
 	*(p) = 1
-	println f(arr...)
+	echo f(arr...)
 	m["a"] <- true
 }
 for i := 0; i < 10; i++ {
diff --git a/x/format/gopstyle_test.go b/x/format/gopstyle_test.go
index e2bb3e99..cf01dde4 100644
--- a/x/format/gopstyle_test.go
+++ b/x/format/gopstyle_test.go
@@ -47,7 +47,7 @@ func main() {
 `, `// this is main
 
 // say hello
-println "Hello world"
+echo "Hello world"
 `)
 	testFormat(t, "hello world 2", `package main
 
@@ -64,7 +64,7 @@ func f() {
 `, `// this is main
 func main() {
 	// say hello
-	println "Hello world"
+	echo "Hello world"
 }
 
 func f() {
@@ -120,7 +120,7 @@ func f() {
 	fmt.Println("hello")
 }
 `, `func f() {
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -136,7 +136,7 @@ func f() {
 	fmt.Println("hello")
 }
 `, `func f() {
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -155,7 +155,7 @@ func f() {
 
 func f() {
 	errorf "%w", New("hello")
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -178,7 +178,7 @@ func f() {
 
 func f() {
 	errorf "%w", errors.new("hello")
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -197,7 +197,7 @@ func f() {
 
 func f() {
 	_ = errorf("%w", errors.new("hello"))
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -220,7 +220,7 @@ func f() {
 
 func f() {
 	_ = errorf("%w", errors.new("hello"))
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -238,7 +238,7 @@ func f() {
 
 func f() {
 	_ = fmt.Stringer
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -256,7 +256,7 @@ func f() {
 
 func f() {
 	var _ fmt.Stringer
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -280,7 +280,7 @@ func f() {
 		fmt.Stringer
 		fn func()
 	}
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -294,7 +294,7 @@ func f() {
 	fmt1.Println("hello")
 }
 `, `func f() {
-	println "hello"
+	echo "hello"
 }
 `)
 }
@@ -312,8 +312,8 @@ func f() {
 	fmt2.Println(2)
 }
 `, `func f() {
-	println 1
-	println 2
+	echo 1
+	echo 2
 }
 `)
 }
@@ -337,8 +337,8 @@ func f() {
 
 func f() {
 	var _ fmt.Stringer
-	println 1
-	println 2
+	echo 1
+	echo 2
 }
 `)
 }
@@ -426,7 +426,7 @@ func f() {
 	_ = fmt
 }
 `, `func f() {
-	println 1
+	echo 1
 	var fmt Foo
 	_ = fmt
 }
@@ -478,3 +478,58 @@ func f() {
 }
 `)
 }
+
+func TestLambdaFromFuncLit(t *testing.T) {
+	testFormat(t, "funclit to lambda", `package main
+println(demo(func(n int) int {
+	return n+100
+}))
+println(demo(func(n int) int {
+	return n+100
+}),200)
+demo1(100, func(n int) {
+	println(n)
+})
+demo1(100, func(int) {
+	println(100)
+})
+demo2(300, func(n1, n2 int) int {
+	return(n1 + n2)
+})
+demo2(300, func(int, int) int {
+	return -600
+})
+demo3(100,func(n1,n2 int)(int) {
+	return n1+n2,n1-n2
+},100)
+demo3(100,func(n1,n2 int)(v int) {
+	return n1+n2,n1-n2
+},100)
+demo4(100,func(n1,n2 int)(a,b int) {
+	println(a,b)
+	return n1+n2,n1-n2
+},100)
+`, `println demo(n => n + 100)
+println demo(n => n + 100), 200
+demo1 100, n => {
+	println n
+}
+demo1 100, _ => {
+	println 100
+}
+demo2 300, (n1, n2) => (n1 + n2)
+
+demo2 300, (_, _) => -600
+
+demo3 100, (n1, n2) => {
+	return n1 + n2, n1 - n2
+}, 100
+demo3 100, func(n1, n2 int) (v int) {
+	return n1 + n2, n1 - n2
+}, 100
+demo4 100, func(n1, n2 int) (a, b int) {
+	println a, b
+	return n1 + n2, n1 - n2
+}, 100
+`)
+}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index ef1ca10e..5f34a42b 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -16,11 +16,11 @@ import (
 	"unsafe"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/format"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
+	"github.com/goplus/gop/tool"
 	"github.com/goplus/gop/x/typesutil"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
@@ -77,7 +77,7 @@ func parseMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string,
 	}
 
 	conf := &types.Config{}
-	conf.Importer = gop.NewImporter(nil, &env.Gop{Root: "../..", Version: "1.0"}, fset)
+	conf.Importer = tool.NewImporter(nil, &env.Gop{Root: "../..", Version: "1.0"}, fset)
 	chkOpts := &typesutil.Config{
 		Types:                 types.NewPackage("main", f.Name.Name),
 		Fset:                  fset,