Repository: goplus/gop
Pull request #953 (2021-11-27 07:58:03)

support class file auto register
cd tutorial/01-Weather
gop mod init

```
module github.com/goplus/spx/tutorial/01-Weather

gop 1.0.22
```

modify gop.mod and local spx gop.mod

```
module github.com/goplus/spx/tutorial/01-Weather

gop 1.0.22

register github.com/goplus/spx

replace github.com/goplus/spx => ../../../spx
```

```
module github.com/goplus/spx

gop 1.0.22

classfile .gmx .spx github.com/goplus/spx math
```

Then execute gop build, you will be prompted to execute go mod download some related installation packages, and finally can be successfully compiled through gop build


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index da331f26..1e5704e9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -19,10 +19,10 @@ jobs:
         go-version: 1.16
 
     - name: Compile gop and related tools
-      run: go run cmd/install.go --build
+      run: go run scripts/install.go --build
 
     - name: Run testcases
-      run: go run cmd/install.go --test
+      run: go run scripts/install.go --test
 
     - name: Codecov
       uses: codecov/codecov-action@v2
diff --git a/README.md b/README.md
index 27c76b1c..2b40fec7 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ For now, we suggest you install Go+ from source code.
 ```bash
 git clone https://github.com/goplus/gop.git
 cd gop
-go run cmd/install.go
+go run scripts/install.go
 ```
 
 ## Code style (important)
diff --git a/README_zh.md b/README_zh.md
index 03c31a39..2e2f4890 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -4,44 +4,41 @@
 [![Go Report Card](https://goreportcard.com/badge/github.com/goplus/gop)](https://goreportcard.com/report/github.com/goplus/gop)
 [![Coverage Status](https://codecov.io/gh/goplus/gop/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/gop)
 [![GitHub release](https://img.shields.io/github/v/tag/goplus/gop.svg?label=release)](https://github.com/goplus/gop/releases)
-[![Tutorials](https://img.shields.io/badge/tutorial-Go+-blue.svg)](https://tutorial.goplus.org/)
 [![Playground](https://img.shields.io/badge/playground-Go+-blue.svg)](https://play.goplus.org/)
 [![VSCode](https://img.shields.io/badge/vscode-Go+-teal.svg)](https://github.com/gopcode/vscode-goplus)
-[![Readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87-teal.svg)](https://github.com/goplus/gop/blob/main/README_zh.md)
+[![Readme](https://img.shields.io/badge/README-EN-teal.svg)](https://github.com/goplus/gop/blob/main/README.md)
+[![GoDoc](https://pkg.go.dev/badge/github.com/goplus/gop.svg)](https://pkg.go.dev/mod/github.com/goplus/gop)
 
-* **对于工程领域而言** ：意味着大家将会使用一门简单、易学、好用的编程语言。
-* **对于 STEM 教育而言** ：掌握一门可能会在将来工作中用到的工程语言将大有裨益。
-* **对于数据科学家而言** ：意味着你们将和工程师使用同一种语言进行合作和交流。
+* **对于工程领域而言**: 意味着大家将会使用一门简单、易学、好用的编程语言。
+* **对于 STEM 教育而言**: 掌握一门可能会在将来工作中用到的工程语言将大有裨益。
+* **对于数据科学家而言**: 意味着你们将和工程师使用同一种语言进行合作和交流。
 
-## 如何安装 Go+?
+## 如何安装 Go+？
 
 当前，我们推荐下载源代码来安装 Go+。
 
 ```bash
 git clone https://github.com/goplus/gop.git
 cd gop
-go run cmd/install.go
+go run scripts/install.go
 ```
 
-## 代码风格（很重要）
-
--   [https://tutorial.goplus.org/hello-world](https://tutorial.goplus.org/hello-world)
-
 ## Go+ 概要
 
 关于 Go+ 的主要印象是什么？
 
--   一种静态类型语言。
--   完全兼容 [Go 语言](https://github.com/golang/go) 。
--   脚本化的风格，以及比 Go 更易读的数据科学代码。
+- 静态类型语言。
+- 与 [Go 语言](https://github.com/golang/go) 完全兼容。
+- 脚本化的风格，以及比 Go 更易于阅读的数据科学代码。
 
 例如，以下是合法的 Go+ 源代码：
 
-```coffee
-println [1, 2, 3.4]
+```go
+a := [1, 2, 3.4]
+println(a)
 ```
 
-我们如何在 Go 语言中做到这一点？
+我们如何用 Go 语言做到这一点？
 
 ```go
 package main
@@ -49,63 +46,54 @@ package main
 import "fmt"
 
 func main() {
-    fmt.Println([]float64{1, 2, 3.4})
+    a := []float64{1, 2, 3.4}
+    fmt.Println(a)
 }
 ```
 
-当然，我们不仅仅做少打字的事情。
 
-例如，我们支持 [列表推导](https://en.wikipedia.org/wiki/List_comprehension) ，这使得数据处理更容易。
+当然，我们不只是做了打字这件事。
+
+例如，我们支持[列表推导](https://en.wikipedia.org/wiki/List_comprehension)，这使得数据处理更容易。
 
 ```go
-println [x*x for x <- 1:6:2] // output: [1 9 25]
+a := [1, 3, 5, 7, 11]
+b := [x*x for x <- a, x > 3]
+println(b) // output: [25 49 121]
 
 mapData := {"Hi": 1, "Hello": 2, "Go+": 3}
 reversedMap := {v: k for k, v <- mapData}
-println reversedMap // output: map[1:Hi 2:Hello 3:Go+]
+println(reversedMap) // output: map[1:Hi 2:Hello 3:Go+]
 ```
 
-我们会保持 Go+ 的简单性。这就是为什么我们称它为 Go+，而不是 Go++。
+我们会保持 Go+ 的简单性。这就是为什么我们称之为 Go+，而不是 Go++。
 
 少即是指数级的多。
 
 Go 是如此，Go+ 亦是如此。
 
-## 教程
-
--   [https://tutorial.goplus.org/](https://tutorial.goplus.org/)
-
-## Playground
-
-基于 Docker 的 Go+ Playground（推荐）：
-
--   [https://play.goplus.org/](https://play.goplus.org/)
-
-基于 GopherJS 的 Go+ Playground（目前仅在 v0.7.x 中可用）：
-
--   [https://jsplay.goplus.org/](https://jsplay.goplus.org/)
-
 ## 与 Go 的兼容性
 
-将支持所有 Go 特性（包括部分支持 `cgo` ，见 [下文](#bytecode-vs-go-code) ）。
+将支持所有 Go 的特性（包括部分支持 `cgo`，[见下文](#bytecode-vs-go-code)）。
 
-**所有 Go 包（即便这些软件包使用 `cgo` ）都可以由 Go+ 导入。**
+Go+ 可以导入所有 Go 软件包（即使是这些软件包使用 cgo）。
 
-```coffee
+```go
 import (
     "fmt"
     "strings"
 )
 
 x := strings.NewReplacer("?", "!").Replace("hello, world???")
-fmt.Println "x:", x
+fmt.Println("x:", x)
 ```
 
-**并且所有 Go+ 包也可以导入到 Go 程序中。您需要做的只是使用 `gop` 命令而不是 `go` 。**
 
-首先，让我们创建一个名为 `14-Using-goplus-in-Go` 的目录。
+所有 Go+ 软件包也可以在 Go 程序中导入。您需要做的就是使用 `gop` 命令，而不是 `go`。
+
+首先，让我们创建一个名为 `tutorial/14-Using-goplus-in-Go` 的目录。
 
-然后在里面编写一个名为 [foo](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go/foo) 的 Go+ 包：
+然后在里面写一个名为 `foo` 的 Go+ 软件包：
 
 ```go
 package foo
@@ -115,7 +103,7 @@ func ReverseMap(m map[string]int) map[int]string {
 }
 ```
 
-然后在 Go 包 [14-Using-goplus-in-Go/gomain](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go/gomain) 中使用它：
+然后在 Go 软件包（14-Using-goplus-in-Go/gomain）中使用它：
 
 ```go
 package main
@@ -123,7 +111,7 @@ package main
 import (
     "fmt"
 
-    "github.com/goplus/tutorial/14-Using-goplus-in-Go/foo"
+    "github.com/goplus/gop/tutorial/14-Using-goplus-in-Go/foo"
 )
 
 func main() {
@@ -138,13 +126,35 @@ func main() {
 gop install -v ./...
 ```
 
-访问 [github.com/goplus/tutorial/14-Using-goplus-in-Go](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go) 来获取源代码。
+或者：
+
+```
+gop run tutorial/14-Using-goplus-in-Go/gomain
+```
+
+从 [tutorial/14-Using-goplus-in-Go](https://github.com/goplus/gop/tree/main/tutorial/14-Using-goplus-in-Go) 获取源代码。
+
+## 游乐场（Playground）
+
+基于 Docker 的 Go+ Playground：
+* https://play.goplus.org/
+
+基于 GopherJS 的 Go+ Playground（目前仅在 v0.7.x 中可用）：
+* https://jsplay.goplus.org/
+
+Go+ Jupyter 内核：
+* https://github.com/wangfenjin/gopyter
+
+## 教程
+
+参见 https://github.com/goplus/gop/tree/main/tutorial
+
 
 ## 字节码与 Go 代码
 
 Go+ 支持字节码后端和 Go 代码生成。
 
-当我们使用 `gop` 命令时，它会生成 Go 代码，将 Go+ 包转换为 Go 包。
+当我们使用 `gop` 命令时，它会生成 Go 代码来将 Go+ 软件包转换为 Go 软件包。
 
 ```bash
 gop run     # Run a Go+ program
@@ -156,29 +166,30 @@ gop clean   # Clean all Go+ auto generated files
 gop go      # Convert Go+ packages into Go packages
 ```
 
-当我们使用 [`igop`](https://github.com/goplus/igop) 命令时，它会执行生成的的字节码。
+当我们使用 [`igop`](https://github.com/goplus/igop) 命令时，它会执行生成的字节码。
 
 ```bash
-igop  # Run a Go+ program
+igop  # 运行一个 Go+ 程序
 ```
 
-在字节码模式下，Go+ 不支持 `cgo` 。但是，在 Go 代码生成模式下，Go+ 完全支持 `cgo` 。
+在字节码模式下，Go+ 不支持 `cgo`。然而，在 Go 代码生成模式下，Go+ 完全支持 `cgo`。
 
 ## Go+ 的特性
 
-### 有理数：bigint、bigrat、bigfloat
 
-我们将有理数作为原生 Go+ 类型引入。我们使用后缀 `r` 来表示有理文字。例如， (1r << 200) 表示一个 big int ，其值等于 2 200 。 4/5r 表示有理常数 4/5。
+### 有理数：bigint，bigrat，bigfloat
+
+我们将有理数作为原生 Go+ 类型引入。我们使用后缀 `r` 来表示有理数字面量。例如，（1r << 200）表示值等于2<sup>200 </sup>的 big int 型整数。4/5r 表示有理常数 4/5。
 
 ```go
 var a bigint = 1r << 65  // bigint, large than int64
 var b bigrat = 4/5r      // bigrat
 c := b - 1/3r + 3 * 1/2r // bigrat
-println a, b, c
+println(a, b, c)
 
 var x *big.Int = 1r << 65 // (1r << 65) is untyped bigint, and can be assigned to *big.Int
 var y *big.Rat = 4/5r
-println x, y
+println(x, y)
 ```
 
 ### 映射字面量
@@ -202,22 +213,7 @@ c := ["xsw", 3] // []interface{}
 empty := [] // []interface{}
 ```
 
-### Lambda 表达式
-
-```go
-func plot(fn func(x float64) float64) {
-    // ...
-}
-
-func plot2(fn func(x float64) (float64, float64)) {
-    // ...
-}
-
-plot x => x * x           // plot(func(x float64) float64 { return x * x })
-plot2 x => (x * x, x + x) // plot2(func(x float64) (float64, float64) { return x * x, x + x })
-```
-
-### 推导结构类型
+### 推断结构类型
 
 ```go
 type Config struct {
@@ -229,12 +225,12 @@ func foo(conf *Config) {
     // ...
 }
 
-foo {Dir: "/foo/bar", Level: 1}
+foo({Dir: "/foo/bar", Level: 1})
 ```
 
-这里 `foo {Dir: "/foo/bar", Level: 1}` 等价于 `foo (&Config {Dir: "/foo/bar", Level: 1})` 。但是，您不能将 `foo (&Config {"/foo/bar", 1})` 替换为 `foo {"/foo/bar", 1}` ，因为将 `{"/foo/bar", 1}` 视为结构字面量会让人摸不着头脑。
+这里 `foo({Dir: "/foo/bar", Level: 1})` 相当于 `foo(&Config{Dir: "/foo/bar", Level: 1})`。但是，不能将 `foo(&Config{"/foo/bar", 1})` 替换为 `foo({"/foo/bar", 1})`，因为将 `{"/foo/bar", 1}` 视为结构字面量会让人摸不着头脑。
 
-您还可以在 return 语句中省略结构类型。例如：
+您还可以在返回语句中省略结构类型。例如：
 
 ```go
 type Result struct {
@@ -246,6 +242,7 @@ func foo() *Result {
 }
 ```
 
+
 ### 列表推导
 
 ```go
@@ -275,8 +272,8 @@ students := [student{"Ken", 90}, student{"Jason", 80}, student{"Lily", 85}]
 unknownScore, ok := {x.score for x <- students, x.name == "Unknown"}
 jasonScore := {x.score for x <- students, x.name == "Jason"}
 
-println unknownScore, ok // output: 0 false
-println jasonScore // output: 80
+println(unknownScore, ok) // output: 0 false
+println(jasonScore) // output: 80
 ```
 
 ### 检查数据是否存在于集合中
@@ -302,25 +299,6 @@ for x <- [1, 3, 5, 7, 11, 13, 17], x > 3 {
 }
 ```
 
-### Range 表达式（ `start:end:step` ）
-
-```go
-for i <- :10 {
-    println i
-}
-
-for i := range :10:2 {
-    println i
-}
-
-for i := range 1:10:3 {
-    println i
-}
-
-for range :10 {
-    println "Range expression"
-}
-```
 
 ### For range of UDT
 
@@ -336,17 +314,18 @@ func (p *Foo) Gop_Enum(proc func(key int, val string)) {
 
 foo := &Foo{}
 for k, v := range foo {
-    println k, v
+    println(k, v)
 }
 
 for k, v <- foo {
-    println k, v
+    println(k, v)
 }
 
-println {v: k for k, v <- foo}
+println({v: k for k, v <- foo})
 ```
 
-**注意：对于 udt.Gop\_Enum（回调）的范围，无法使用 break/continue 或 return 语句。**
+注意：对于 udt.Gop_Enum（回调）的范围，无法使用 break/continue 或 return 语句。
+
 
 ### For range of UDT2
 
@@ -370,14 +349,29 @@ func (p *Foo) Gop_Enum() *FooIter {
 
 foo := &Foo{}
 for k, v := range foo {
-    println k, v
+    println(k, v)
 }
 
 for k, v <- foo {
-    println k, v
+    println(k, v)
 }
 
-println {v: k for k, v <- foo}
+println({v: k for k, v <- foo})
+```
+
+### Lambda 表达式
+
+```go
+func plot(fn func(x float64) float64) {
+    // ...
+}
+
+func plot2(fn func(x float64) (float64, float64)) {
+    // ...
+}
+
+plot(x => x * x)           // plot(func(x float64) float64 { return x * x })
+plot2(x => (x * x, x + x)) // plot2(func(x float64) (float64, float64) { return x * x, x + x })
 ```
 
 ### 重载运算符
@@ -407,13 +401,14 @@ func -(a MyBigInt) MyBigInt { // unary operator
 
 a := Int(1r)
 a += Int(2r)
-println a + Int(3r)
-println -a
+println(a + Int(3r))
+println(-a)
 ```
 
+
 ### 错误处理
 
-我们在 Go+ 中重新发明了错误处理规范。我们称之为 `ErrWrap expressions` ：
+我们在 Go+ 中重新发明了错误处理规范。我们称之为 `ErrWrap 表达式`：
 
 ```go
 expr! // panic if err
@@ -436,15 +431,15 @@ func addSafe(x, y string) int {
     return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
 }
 
-println `add("100", "23"):`, add("100", "23")!
+println(`add("100", "23"):`, add("100", "23")!)
 
 sum, err := add("10", "abc")
-println `add("10", "abc"):`, sum, err
+println(`add("10", "abc"):`, sum, err)
 
-println `addSafe("10", "abc"):`, addSafe("10", "abc")
+println(`addSafe("10", "abc"):`, addSafe("10", "abc"))
 ```
 
-这个例子的输出是：
+此示例的输出是：
 
 ```
 add("100", "23"): 123
@@ -452,98 +447,105 @@ add("10", "abc"): 0 strconv.Atoi: parsing "abc": invalid syntax
 
 ===> errors stack:
 main.add("10", "abc")
-    /Users/xsw/tutorial/15-ErrWrap/err_wrap.gop:6 strconv.Atoi(y)?
+    /Users/xsw/goplus/tutorial/15-ErrWrap/err_wrap.gop:6 strconv.Atoi(y)?
 
 addSafe("10", "abc"): 10
 ```
 
-与相应的 Go 代码相比，它更清晰、更具可读性。
+与相应的 Go 代码相比，更清晰、更易读。
+
+最有趣的是，返回错误包含完整的错误堆栈。当我们出错时，很容易定位根本原因是什么。
 
-最有趣的是，返回错误包含完整的错误堆栈。当我们遇到错误时，很容易定位根本原因是什么。
+这些 `ErrWrap 表达式`是如何工作的？有关更多信息，请参阅[错误处理](https://github.com/goplus/gop/wiki/Error-Handling)。
 
-这些 `ErrWrap expressions` 如何工作的？有关更多信息，请参阅 [错误处理。](https://github.com/goplus/gop/wiki/Error-Handling)
 
-### Auto property
+### 自动属性
 
-让我们看一个用 Go+ 编写的例子：
+让我们看看用 Go+ 编写的示例：
 
 ```go
 import "gop/ast/goptest"
 
 doc := goptest.New(`... Go+ code ...`)!
 
-println doc.Any().FuncDecl().Name()
+println(doc.Any().FuncDecl().Name())
 ```
 
 在许多语言中，有一个名为 `property` 的概念，它具有 `get` 和 `set` 方法。
 
-假设我们有 `get property` ，上面的例子将是：
+假设我们有 `get property`，上述示例是：
 
 ```go
 import "gop/ast/goptest"
 
 doc := goptest.New(`... Go+ code ...`)!
 
-println doc.any.funcDecl.name
+println(doc.any.funcDecl.name)
 ```
 
-在 Go+ 中，我们引入了一个名为 `auto property` 的概念。它是一个 `get property` ，但会自动实现。如果我们有一个名为 `Bar()` 的方法，那么我们将同时拥有一个名为 `bar` 的 `get property` 。
+在 Go+ 中，我们引入了一个名为`自动属性`的概念。这是一个 `get 属性`，但会自动实现。如果我们有一个名为`Bar()` 的方法，那么我们将同时有一个名为 `bar` 的 `get 属性`。
+
 
 ### Unix shebang
 
-您现在可以将 Go+ 程序用作 shell 脚本。例如：
+您现在可以使用 Go+ 程序作为 shell 脚本。例如：
 
 ```go
 #!/usr/bin/env -S gop run
 
-println "Hello, Go+"
+println("Hello, Go+")
 
-println 1r << 129
-println 1/3r + 2/7r*2
+println(1r << 129)
+println(1/3r + 2/7r*2)
 
 arr := [1, 3, 5, 7, 11, 13, 17, 19]
-println arr
-println [x*x for x <- arr, x > 3]
+println(arr)
+println([x*x for x <- arr, x > 3])
 
 m := {"Hi": 1, "Go+": 2}
-println m
-println {v: k for k, v <- m}
-println [k for k, _ <- m]
-println [v for v <- m]
+println(m)
+println({v: k for k, v <- m})
+println([k for k, _ <- m])
+println([v for v <- m])
 ```
 
-访问 [20-Unix-Shebang/shebang](https://github.com/goplus/tutorial/blob/main/20-Unix-Shebang/shebang) 来获取源代码。
+前往 [tutorial/20-Unix-Shebang/shebang](https://github.com/goplus/gop/blob/main/tutorial/20-Unix-Shebang/shebang) 获取源代码。
+
 
 ### Go 特性
 
-将支持所有 Go 特性（包括部分支持 `cgo`）。在字节码模式下，Go+ 不支持 `cgo` 。然而，在 Go 代码生成模式下，Go+ 完全支持 `cgo` 。
+将支持所有 Go 特性（包括部分支持 cgo ）。在字节码模式下，Go+ 不支持 `cgo`。然而，在 Go 代码生成模式下，Go+ 完全支持 `cgo`。
+
 
 ## IDE 插件
 
--   vscode： [https://github.com/gopcode/vscode-goplus](https://github.com/gopcode/vscode-goplus)
+* vscode: https://github.com/gopcode/vscode-goplus
+
 
 ## 贡献
 
 Go+ 项目欢迎所有贡献者。我们感谢您的帮助！
 
-这是 [Go+ 贡献者列表](https://github.com/goplus/gop/wiki/Goplus-Contributors) 。我们为每位贡献者颁发一个电子邮件帐户 ([XXX@goplus.org)。](mailto:XXX@goplus.org) 我们建议您使用此电子邮件帐户提交代码：
+以下是 [Go+ 贡献者列表](https://github.com/goplus/gop/wiki/Goplus-Contributors)。我们为每个贡献者授予一个电子邮件帐户（XXX@goplus.org）。我们建议您使用此电子邮件帐户提交代码：
 
 ```bash
 git config --global user.email XXX@goplus.org
 ```
 
-如果您这样做了，请记住将您的 `XXX@goplus.org` 电子邮件添加到 [https://github.com/settings/emails](https://github.com/settings/emails) 。
+如果您这样做了，请记住将您的 `XXX@goplus.org` 电子邮件添加到 https://github.com/settings/emails 。
+
+成为 `Go+ 贡献者` 意味着什么？您必须满足以下条件之一：
+
+至少一个功能完整的实现的拉取请求。
 
-如何成为一个 `Go+ 贡献者`？您必须满足以下条件之一：
+至少三个功能增强的拉取请求。
 
--   至少一个实现完整功能的拉取请求。
--   至少三个功能增强的拉取请求。
--   至少十个任意类型的 issues 拉取请求。
+至少十个任何类型的 `issues` 拉取请求。
 
 您可以从哪里开始？
 
--   [![Issues](https://img.shields.io/badge/ISSUEs-Go+-blue.svg)](https://github.com/goplus/gop/issues)
--   [![Issues](https://img.shields.io/badge/ISSUEs-NumGo+-blue.svg)](https://github.com/numgoplus/ng/issues)
--   [![Issues](https://img.shields.io/badge/ISSUEs-PandasGo+-blue.svg)](https://github.com/goplus/pandas/issues)
--   [![Issues](https://img.shields.io/badge/ISSUEs-vscode%20Go+-blue.svg)](https://github.com/gopcode/vscode-goplus/issues)
--   [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/goplus/gop)](https://www.tickgit.com/browse?repo=github.com/goplus/gop)
+* [![Issues](https://img.shields.io/badge/ISSUEs-Go+-blue.svg)](https://github.com/goplus/gop/issues)
+* [![Issues](https://img.shields.io/badge/ISSUEs-NumGo+-blue.svg)](https://github.com/numgoplus/ng/issues)
+* [![Issues](https://img.shields.io/badge/ISSUEs-PandasGo+-blue.svg)](https://github.com/goplus/pandas/issues)
+* [![Issues](https://img.shields.io/badge/ISSUEs-vscode%20Go+-blue.svg)](https://github.com/gopcode/vscode-goplus/issues)
+* [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/goplus/gop)](https://www.tickgit.com/browse?repo=github.com/goplus/gop)
diff --git a/all.bash b/all.bash
index d4d28a6e..cf3f4008 100755
--- a/all.bash
+++ b/all.bash
@@ -18,4 +18,4 @@
 
 set -ex
 
-go run cmd/install.go
+go run scripts/install.go
diff --git a/cl/classfile.go b/cl/classfile.go
index ee61472d..15e3646f 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -17,13 +17,12 @@
 package cl
 
 import (
+	goast "go/ast"
 	"go/constant"
 	"go/types"
 	"path/filepath"
 	"strings"
 
-	goast "go/ast"
-
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
@@ -40,8 +39,6 @@ type gmxInfo struct {
 var (
 	gmxTypes = map[string]gmxInfo{
 		".gmx": {".spx", []string{"github.com/goplus/spx", "math"}},
-		".spc": {"", []string{"github.com/qbox/gopla/spc", "github.com/goplus/spx", "math"}},
-		// TODO: dynamic register
 	}
 )
 
diff --git a/cl/compile.go b/cl/compile.go
index 65071302..d89cdefc 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -899,17 +899,7 @@ func loadVars(ctx *blockCtx, v *ast.ValueSpec, global bool) {
 			compileExpr(ctx, v.Values[0], true)
 		} else {
 			for _, val := range v.Values {
-				switch e := val.(type) {
-				case *ast.LambdaExpr, *ast.LambdaExpr2:
-					if len(v.Values) == 1 {
-						sig := checkLambdaFuncType(ctx, e, typ, clLambaAssign, v.Names[0])
-						compileLambda(ctx, e, sig)
-					} else {
-						panic(ctx.newCodeErrorf(e.Pos(), "lambda unsupport multiple assignment"))
-					}
-				default:
-					compileExpr(ctx, val)
-				}
+				compileExpr(ctx, val)
 			}
 		}
 		cb.EndInit(nv)
diff --git a/cl/expr.go b/cl/expr.go
index 67551bce..3ae52203 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -434,11 +434,11 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, flags int) {
 		switch expr := arg.(type) {
 		case *ast.LambdaExpr:
 			fn.initWith(fnt, i, len(expr.Lhs))
-			sig := checkLambdaFuncType(ctx, expr, fn.arg(i, true), clLambaArgument, v.Fun)
+			sig := checkLambdaArgumentType(ctx, expr, v.Fun, fn.arg(i, true), len(expr.Rhs))
 			compileLambdaExpr(ctx, expr, sig)
 		case *ast.LambdaExpr2:
 			fn.initWith(fnt, i, len(expr.Lhs))
-			sig := checkLambdaFuncType(ctx, expr, fn.arg(i, true), clLambaArgument, v.Fun)
+			sig := checkLambdaArgumentType(ctx, expr, v.Fun, fn.arg(i, true), -1)
 			compileLambdaExpr2(ctx, expr, sig)
 		case *ast.CompositeLit:
 			fn.initWith(fnt, i, -1)
@@ -450,42 +450,41 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, flags int) {
 	ctx.cb.CallWith(len(v.Args), ellipsis, v)
 }
 
-type clLambaFlag string
-
-const (
-	clLambaAssign   clLambaFlag = "assignment"
-	clLambaField    clLambaFlag = "field value"
-	clLambaArgument clLambaFlag = "argument"
-)
-
-// check lambda func type
-func checkLambdaFuncType(ctx *blockCtx, lambda ast.Expr, ftyp types.Type, flag clLambaFlag, toNode ast.Node) *types.Signature {
-	typ := ftyp
+// check lambda func argument type, check results len if chkResults >= 0.
+func checkLambdaArgumentType(ctx *blockCtx, lambda ast.Expr, fun ast.Expr, argTyp types.Type, chkResults int) *types.Signature {
+	typ := argTyp
 retry:
 	switch t := typ.(type) {
 	case *types.Signature:
-		if l, ok := lambda.(*ast.LambdaExpr); ok {
-			if len(l.Rhs) != t.Results().Len() {
-				break
-			}
+		if chkResults < 0 || chkResults == t.Results().Len() {
+			return t
 		}
-		return t
 	case *types.Named:
 		typ = t.Underlying()
 		goto retry
 	}
-	src, _ := ctx.LoadExpr(toNode)
-	err := ctx.newCodeErrorf(lambda.Pos(), "cannot use lambda literal as type %v in %v to %v", ftyp, flag, src)
+	pos := ctx.Position(lambda.Pos())
+	src, _ := ctx.LoadExpr(fun)
+	err := newCodeErrorf(&pos, "cannot use lambda literal as type %v in argument to %v", argTyp, src)
 	panic(err)
 }
 
-func compileLambda(ctx *blockCtx, lambda ast.Expr, sig *types.Signature) {
-	switch expr := lambda.(type) {
-	case *ast.LambdaExpr:
-		compileLambdaExpr(ctx, expr, sig)
-	case *ast.LambdaExpr2:
-		compileLambdaExpr2(ctx, expr, sig)
+// check lambda field type, check results len if chkResults >= 0.
+func checkLambdaFieldType(ctx *blockCtx, lambda ast.Expr, fieldType types.Type, chkResults int) *types.Signature {
+	typ := fieldType
+retry:
+	switch t := typ.(type) {
+	case *types.Signature:
+		if chkResults < 0 || chkResults == t.Results().Len() {
+			return t
+		}
+	case *types.Named:
+		typ = t.Underlying()
+		goto retry
 	}
+	pos := ctx.Position(lambda.Pos())
+	err := newCodeErrorf(&pos, "cannot use lambda literal as type %v in field value", fieldType)
+	panic(err)
 }
 
 func compileLambdaParams(ctx *blockCtx, pos token.Pos, lhs []*ast.Ident, in *types.Tuple) []*types.Var {
@@ -614,9 +613,12 @@ func compileStructLitInKeyVal(ctx *blockCtx, elts []ast.Expr, t *types.Struct, t
 			log.Panicln("TODO: struct member not found -", name)
 		}
 		switch expr := kv.Value.(type) {
-		case *ast.LambdaExpr, *ast.LambdaExpr2:
-			sig := checkLambdaFuncType(ctx, expr, t.Field(idx).Type(), clLambaField, kv.Key)
-			compileLambda(ctx, expr, sig)
+		case *ast.LambdaExpr:
+			sig := checkLambdaFieldType(ctx, expr, t.Field(idx).Type(), len(expr.Rhs))
+			compileLambdaExpr(ctx, expr, sig)
+		case *ast.LambdaExpr2:
+			sig := checkLambdaFieldType(ctx, expr, t.Field(idx).Type(), -1)
+			compileLambdaExpr2(ctx, expr, sig)
 		default:
 			compileExpr(ctx, kv.Value)
 		}
diff --git a/cl/stmt.go b/cl/stmt.go
index 08732bec..d79f5c7c 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -81,14 +81,9 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 	commentStmt(ctx, stmt)
 	switch v := stmt.(type) {
 	case *ast.ExprStmt:
-		if obj, ok := isBuiltinAutoCall(ctx, v.X); ok {
-			ctx.cb.Val(obj)
+		compileExpr(ctx, v.X)
+		if canAutoCall(v.X) && isFunc(ctx.cb.InternalStack().Get(-1).Type) {
 			ctx.cb.Call(0)
-		} else {
-			compileExpr(ctx, v.X)
-			if canAutoCall(v.X) && isFunc(ctx.cb.InternalStack().Get(-1).Type) {
-				ctx.cb.Call(0)
-			}
 		}
 	case *ast.AssignStmt:
 		compileAssignStmt(ctx, v)
@@ -147,19 +142,6 @@ retry:
 	return false
 }
 
-func isBuiltinAutoCall(ctx *blockCtx, expr ast.Expr) (types.Object, bool) {
-	if ident, ok := expr.(*ast.Ident); ok {
-		switch ident.Name {
-		case "print", "println":
-			_, builtin := lookupType(ctx, ident.Name)
-			if isBuiltin(builtin) {
-				return builtin, true
-			}
-		}
-	}
-	return nil, false
-}
-
 func compileReturnStmt(ctx *blockCtx, expr *ast.ReturnStmt) {
 	var n = -1
 	var results *types.Tuple
@@ -230,18 +212,7 @@ func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
 		compileExprLHS(ctx, lhs)
 	}
 	for _, rhs := range expr.Rhs {
-		switch e := rhs.(type) {
-		case *ast.LambdaExpr, *ast.LambdaExpr2:
-			if len(expr.Lhs) == 1 && len(expr.Rhs) == 1 {
-				typ := ctx.cb.Get(-1).Type.(interface{ Elem() types.Type }).Elem()
-				sig := checkLambdaFuncType(ctx, e, typ, clLambaAssign, expr.Lhs[0])
-				compileLambda(ctx, e, sig)
-			} else {
-				panic(ctx.newCodeErrorf(e.Pos(), "lambda unsupport multiple assignment"))
-			}
-		default:
-			compileExpr(ctx, rhs, twoValue)
-		}
+		compileExpr(ctx, rhs, twoValue)
 	}
 	if tok == token.ASSIGN {
 		ctx.cb.AssignWith(len(expr.Lhs), len(expr.Rhs), expr)
diff --git a/cmd/gengo/gen.go b/cmd/gengo/gen.go
index 60e50acc..2caef438 100644
--- a/cmd/gengo/gen.go
+++ b/cmd/gengo/gen.go
@@ -139,12 +139,17 @@ func (p *Runner) GenGo(dir string, recursive bool, base *cl.Config) {
 var (
 	extPkgFlags = map[string]int{
 		".gop": PkgFlagGoPlus,
-		".spx": PkgFlagSpx,
-		".gmx": PkgFlagGmx,
 		".go":  PkgFlagGo,
 	}
 )
 
+func RegisterPkgFlags(ext string, flag int) {
+	if flag != PkgFlagGmx && flag != PkgFlagSpx {
+		panic("RegisterPkgFlags: flag should be PkgFlagGmx or PkgFlagSpx")
+	}
+	extPkgFlags[ext] = flag
+}
+
 func (p *Runner) GenGoPkg(pkgDir string, base *cl.Config) (err error) {
 	defer func() {
 		if e := recover(); e != nil {
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index ee266798..875732b0 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -31,6 +31,7 @@ import (
 	"github.com/goplus/gop/cmd/internal/gopfmt"
 	"github.com/goplus/gop/cmd/internal/help"
 	"github.com/goplus/gop/cmd/internal/install"
+	"github.com/goplus/gop/cmd/internal/mod"
 	"github.com/goplus/gop/cmd/internal/run"
 	"github.com/goplus/gop/cmd/internal/test"
 	"github.com/goplus/gop/cmd/internal/version"
@@ -47,6 +48,7 @@ func init() {
 		run.Cmd,
 		gengo.Cmd,
 		gopfmt.Cmd,
+		mod.Cmd,
 		install.Cmd,
 		build.Cmd,
 		clean.Cmd,
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index 74c8b61d..cc72ba4b 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -19,11 +19,13 @@ package build
 
 import (
 	"fmt"
-	"github.com/qiniu/x/log"
 	"os"
 
+	"github.com/qiniu/x/log"
+
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modload"
 	"github.com/goplus/gox"
 )
 
@@ -59,6 +61,7 @@ func runCmd(_ *base.Command, args []string) {
 		cl.SetDebug(cl.DbgFlagAll)
 		cl.SetDisableRecover(true)
 	}
+	modload.Load()
 	base.GenGoForBuild(dir, recursive, func() { fmt.Fprintln(os.Stderr, "GenGo failed, stop building") })
 	base.RunGoCmd(dir, "build", args...)
 }
diff --git a/cmd/internal/env/env.go b/cmd/internal/env/env.go
new file mode 100644
index 00000000..e5bdd5c0
--- /dev/null
+++ b/cmd/internal/env/env.go
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package env
+
+import (
+	"os"
+	"path/filepath"
+)
+
+var (
+	GOMODCACHE = envOr("GOMODCACHE", gopathDir("pkg/mod"))
+)
+
+// envOr returns Getenv(key) if set, or else def.
+func envOr(key, def string) string {
+	val := os.Getenv(key)
+	if val == "" {
+		val = def
+	}
+	return val
+}
+
+// GetGoPath return the gopath
+func GetGoPath() string {
+	return os.Getenv("GOPATH")
+}
+
+func gopathDir(rel string) string {
+	list := filepath.SplitList(GetGoPath())
+	if len(list) == 0 || list[0] == "" {
+		return ""
+	}
+	return filepath.Join(list[0], rel)
+}
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index 9701f300..3ac4b670 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -22,11 +22,13 @@ import (
 	"os"
 	"strings"
 
+	"github.com/qiniu/x/log"
+
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/gengo"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modload"
 	"github.com/goplus/gox"
-	"github.com/qiniu/x/log"
 )
 
 // -----------------------------------------------------------------------------
@@ -98,6 +100,7 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 	dir := flag.Arg(0)
 	dir = strings.TrimSuffix(dir, "/...")
+	modload.Load()
 	runner := new(gengo.Runner)
 	runner.SetAfter(func(p *gengo.Runner, dir string, flags int) error {
 		errs := p.ResetErrors()
diff --git a/cmd/internal/minorver/minorver.go b/cmd/internal/minorver/minorver.go
new file mode 100644
index 00000000..d797dc4e
--- /dev/null
+++ b/cmd/internal/minorver/minorver.go
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package minorver
+
+// Version is the Go+ 1.x version which is currently
+// in development and will eventually get released.
+//
+// It should be updated at the start of each development cycle to be
+// the version of the next Go+ 1.x release. See golang.org/issue/40705.
+
+const Version = 0
diff --git a/cmd/internal/mod/download.go b/cmd/internal/mod/download.go
new file mode 100644
index 00000000..fa44fa09
--- /dev/null
+++ b/cmd/internal/mod/download.go
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// gop mod download
+package mod
+
+import (
+	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modfetch"
+	"github.com/goplus/gop/cmd/internal/modload"
+)
+
+var cmdDownload = &base.Command{
+	UsageLine: "gop mod download [-x] [-json] [modules]",
+	Short:     "download modules to local cache",
+}
+
+func init() {
+	cmdDownload.Run = runDownload // break init cycle
+}
+
+func runDownload(cmd *base.Command, args []string) {
+	modfetch.DownloadArgs(".", args...)
+	modload.SyncGopMod()
+}
diff --git a/cmd/internal/mod/init.go b/cmd/internal/mod/init.go
new file mode 100644
index 00000000..8ba3448b
--- /dev/null
+++ b/cmd/internal/mod/init.go
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// gop mod init
+
+package mod
+
+import (
+	"log"
+
+	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modload"
+)
+
+var cmdInit = &base.Command{
+	UsageLine: "go mod init [module]",
+	Short:     "initialize new module in current directory",
+}
+
+func init() {
+	cmdInit.Run = runInit
+}
+
+func runInit(cmd *base.Command, args []string) {
+	if len(args) > 1 {
+		log.Fatalf("gop mod init: too many arguments")
+	}
+	var modPath string
+	if len(args) == 1 {
+		modPath = args[0]
+	}
+
+	// modfetch.InitArgs(".", args...)
+	modload.CreateModFile(modPath) // does all the hard work
+	modload.LoadModFile()
+	modload.SyncGoMod()
+	modload.SyncGopMod()
+}
diff --git a/cmd/internal/mod/mod.go b/cmd/internal/mod/mod.go
new file mode 100644
index 00000000..58d17279
--- /dev/null
+++ b/cmd/internal/mod/mod.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package mod
+
+import (
+	"github.com/goplus/gop/cmd/internal/base"
+)
+
+var Cmd = &base.Command{
+	UsageLine: "gop mod",
+	Short:     "module maintenance",
+
+	Commands: []*base.Command{
+		cmdInit,
+		cmdDownload,
+		cmdTidy,
+	},
+}
diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go
new file mode 100644
index 00000000..f968e69b
--- /dev/null
+++ b/cmd/internal/mod/tidy.go
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// gop mod tidy
+package mod
+
+import (
+	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modfetch"
+	"github.com/goplus/gop/cmd/internal/modload"
+)
+
+var cmdTidy = &base.Command{
+	UsageLine: "gop mod tidy [-e] [-v]",
+	Short:     "add missing and remove unused modules",
+}
+
+func init() {
+	cmdTidy.Run = runTidy
+}
+
+func runTidy(cmd *base.Command, args []string) {
+	modload.LoadModFile()
+	modload.SyncGoMod()
+	modfetch.TidyArgs(".", args...)
+	modload.SyncGopMod()
+}
diff --git a/cmd/internal/modfetch/cache.go b/cmd/internal/modfetch/cache.go
new file mode 100644
index 00000000..30947bdc
--- /dev/null
+++ b/cmd/internal/modfetch/cache.go
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package modfetch
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"golang.org/x/mod/module"
+	"golang.org/x/mod/semver"
+
+	"github.com/goplus/gop/cmd/internal/env"
+)
+
+func cacheDir(path string) (string, error) {
+	enc, err := module.EscapePath(path)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(env.GOMODCACHE, "cache/download", enc, "/@v"), nil
+}
+
+func CachePath(m module.Version, suffix string) (string, error) {
+	dir, err := cacheDir(m.Path)
+	if err != nil {
+		return "", err
+	}
+	if !semver.IsValid(m.Version) {
+		return "", fmt.Errorf("non-semver module version %q", m.Version)
+	}
+	if module.CanonicalVersion(m.Version) != m.Version {
+		return "", fmt.Errorf("non-canonical module version %q", m.Version)
+	}
+	encVer, err := module.EscapeVersion(m.Version)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(dir, encVer+"."+suffix), nil
+}
diff --git a/cmd/internal/modfetch/fetch.go b/cmd/internal/modfetch/fetch.go
new file mode 100644
index 00000000..d6300ec2
--- /dev/null
+++ b/cmd/internal/modfetch/fetch.go
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package modfetch
+
+import (
+	"errors"
+	"fmt"
+	"io/fs"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/mod/module"
+	"golang.org/x/mod/semver"
+
+	"github.com/goplus/gop/cmd/internal/env"
+)
+
+// DownloadDir returns the directory to which m should have been downloaded.
+// An error will be returned if the module path or version cannot be escaped.
+// An error satisfying errors.Is(err, fs.ErrNotExist) will be returned
+// along with the directory if the directory does not exist or if the directory
+// is not completely populated.
+func DownloadDir(m module.Version) (string, error) {
+	enc, err := module.EscapePath(m.Path)
+	if err != nil {
+		return "", err
+	}
+	if !semver.IsValid(m.Version) {
+		return "", fmt.Errorf("non-semver module version %q", m.Version)
+	}
+	if module.CanonicalVersion(m.Version) != m.Version {
+		return "", fmt.Errorf("non-canonical module version %q", m.Version)
+	}
+	encVer, err := module.EscapeVersion(m.Version)
+	if err != nil {
+		return "", err
+	}
+
+	// Check whether the directory itself exists.
+	dir := filepath.Join(env.GOMODCACHE, enc+"@"+encVer)
+	if fi, err := os.Stat(dir); os.IsNotExist(err) {
+		return dir, err
+	} else if err != nil {
+		return dir, &DownloadDirPartialError{dir, err}
+	} else if !fi.IsDir() {
+		return dir, &DownloadDirPartialError{dir, errors.New("not a directory")}
+	}
+
+	// Check if a .partial file exists. This is created at the beginning of
+	// a download and removed after the zip is extracted.
+	partialPath, err := CachePath(m, "partial")
+	if err != nil {
+		return dir, err
+	}
+	if _, err := os.Stat(partialPath); err == nil {
+		return dir, &DownloadDirPartialError{dir, errors.New("not completely extracted")}
+	} else if !os.IsNotExist(err) {
+		return dir, err
+	}
+
+	// Check if a .ziphash file exists. It should be created before the
+	// zip is extracted, but if it was deleted (by another program?), we need
+	// to re-calculate it.
+	ziphashPath, err := CachePath(m, "ziphash")
+	if err != nil {
+		return dir, err
+	}
+	if _, err := os.Stat(ziphashPath); os.IsNotExist(err) {
+		return dir, &DownloadDirPartialError{dir, errors.New("ziphash file is missing")}
+	} else if err != nil {
+		return dir, err
+	}
+	return dir, nil
+}
+
+func Download(m module.Version) (dir string, err error) {
+	dir, err = DownloadDir(m)
+	if err == nil {
+		// The directory has already been completely extracted (no .partial file exists).
+		return dir, nil
+	} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
+		return "", err
+	}
+
+	DownloadArgs(dir, m.String())
+	return dir, nil
+}
+
+func DownloadArgs(dir string, args ...string) {
+	RunGoCmd(dir, "mod", append([]string{"download"}, args...)...)
+}
+
+func TidyArgs(dir string, args ...string) {
+	RunGoCmd(dir, "mod", append([]string{"tidy"}, args...)...)
+}
+
+func InitArgs(dir string, args ...string) {
+	RunGoCmd(dir, "mod", append([]string{"init"}, args...)...)
+}
+
+// RunGoCmd executes `go` command tools.
+func RunGoCmd(dir string, op string, args ...string) {
+	cmd := exec.Command("go", append([]string{op}, args...)...)
+	cmd.Dir = dir
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.Env = os.Environ()
+	err := cmd.Run()
+	if err != nil {
+		switch e := err.(type) {
+		case *exec.ExitError:
+			os.Exit(e.ExitCode())
+		default:
+			log.Fatalln("RunGoCmd failed:", err)
+		}
+	}
+}
+
+// DownloadDirPartialError is returned by DownloadDir if a module directory
+// exists but was not completely populated.
+//
+// DownloadDirPartialError is equivalent to fs.ErrNotExist.
+type DownloadDirPartialError struct {
+	Dir string
+	Err error
+}
+
+func (e *DownloadDirPartialError) Error() string     { return fmt.Sprintf("%s: %v", e.Dir, e.Err) }
+func (e *DownloadDirPartialError) Is(err error) bool { return err == fs.ErrNotExist }
diff --git a/cmd/internal/modfetch/file.go b/cmd/internal/modfetch/file.go
new file mode 100644
index 00000000..1e368da4
--- /dev/null
+++ b/cmd/internal/modfetch/file.go
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package modfetch
+
+import (
+	"io"
+	"os"
+)
+
+// Edit creates the named file with mode 0666 (before umask),
+// but does not truncate existing contents.
+//
+// If Edit succeeds, methods on the returned File can be used for I/O.
+// The associated file descriptor has mode O_RDWR and the file is write-locked.
+func Edit(name string) (*os.File, error) {
+	return os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
+}
+
+// Transform invokes t with the result of reading the named file, with its lock
+// still held.
+//
+// If t returns a nil error, Transform then writes the returned contents back to
+// the file, making a best effort to preserve existing contents on error.
+//
+// t must not modify the slice passed to it.
+func Transform(name string, t func([]byte) ([]byte, error)) (err error) {
+	f, err := Edit(name)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	old, err := io.ReadAll(f)
+	if err != nil {
+		return err
+	}
+
+	new, err := t(old)
+	if err != nil {
+		return err
+	}
+
+	if len(new) > len(old) {
+		// The overall file size is increasing, so write the tail first: if we're
+		// about to run out of space on the disk, we would rather detect that
+		// failure before we have overwritten the original contents.
+		if _, err := f.WriteAt(new[len(old):], int64(len(old))); err != nil {
+			// Make a best effort to remove the incomplete tail.
+			f.Truncate(int64(len(old)))
+			return err
+		}
+	}
+
+	// We're about to overwrite the old contents. In case of failure, make a best
+	// effort to roll back before we close the file.
+	defer func() {
+		if err != nil {
+			if _, err := f.WriteAt(old, 0); err == nil {
+				f.Truncate(int64(len(old)))
+			}
+		}
+	}()
+
+	if len(new) >= len(old) {
+		if _, err := f.WriteAt(new[:len(old)], 0); err != nil {
+			return err
+		}
+	} else {
+		if _, err := f.WriteAt(new, 0); err != nil {
+			return err
+		}
+		// The overall file size is decreasing, so shrink the file to its final size
+		// after writing. We do this after writing (instead of before) so that if
+		// the write fails, enough filesystem space will likely still be reserved
+		// to contain the previous contents.
+		if err := f.Truncate(int64(len(new))); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// Read opens the named file and returns its contents.
+func Read(name string) ([]byte, error) {
+	f, err := os.Open(name)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	return io.ReadAll(f)
+}
diff --git a/cmd/internal/modload/classfile.go b/cmd/internal/modload/classfile.go
new file mode 100644
index 00000000..b1acf278
--- /dev/null
+++ b/cmd/internal/modload/classfile.go
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package modload
+
+import (
+	"log"
+	"path/filepath"
+
+	"golang.org/x/mod/module"
+
+	"github.com/goplus/gop/cmd/internal/modfetch"
+	"github.com/goplus/gop/x/mod/modfile"
+)
+
+func LoadClassFile() {
+	if modFile.Register == nil {
+		return
+	}
+
+	var dir string
+	var err error
+	var claassMod module.Version
+
+	for _, require := range modFile.Require {
+		if require.Mod.Path == modFile.Register.ClassfileMod {
+			claassMod = require.Mod
+		}
+	}
+	for _, replace := range modFile.Replace {
+		if replace.Old.Path == modFile.Register.ClassfileMod {
+			claassMod = replace.New
+		}
+	}
+
+	if claassMod.Version != "" {
+		dir, err = modfetch.Download(claassMod)
+		if err != nil {
+			log.Fatalf("gop: download classsfile module error %v", err)
+		}
+	} else {
+		dir = claassMod.Path
+	}
+
+	if dir == "" {
+		log.Fatalf("gop: can't find classfile path in require statment")
+	}
+
+	gopmod := filepath.Join(dir, "gop.mod")
+	data, err := modfetch.Read(gopmod)
+	if err != nil {
+		log.Fatalf("gop: %v", err)
+	}
+
+	var fixed bool
+	f, err := modfile.Parse(gopmod, data, fixVersion(&fixed))
+	if err != nil {
+		// Errors returned by modfile.Parse begin with file:line.
+		log.Fatalf("go: errors parsing go.mod:\n%s\n", err)
+	}
+	classModFile = f
+}
diff --git a/cmd/internal/modload/modload.go b/cmd/internal/modload/modload.go
new file mode 100644
index 00000000..41b2e31b
--- /dev/null
+++ b/cmd/internal/modload/modload.go
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package modload
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	gomodfile "golang.org/x/mod/modfile"
+	"golang.org/x/mod/module"
+
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/cmd/gengo"
+	"github.com/goplus/gop/cmd/internal/minorver"
+	"github.com/goplus/gop/cmd/internal/modfetch"
+	"github.com/goplus/gop/cmd/internal/search"
+	"github.com/goplus/gop/x/mod/modfile"
+)
+
+var (
+	modFile, classModFile *modfile.File
+
+	initialized bool
+	modRoot     string
+	Target      module.Version
+	// targetPrefix is the path prefix for packages in Target, without a trailing
+	// slash. For most modules, targetPrefix is just Target.Path, but the
+	// standard-library module "std" has an empty prefix.
+	targetPrefix string
+	buildList    []module.Version
+)
+
+var ErrNoModRoot = errors.New("gop.mod file not found in current directory or any parent directory; see 'gop help modules'")
+
+func findModuleRoot(dir string) (root string) {
+	if dir == "" {
+		panic("dir not set")
+	}
+	dir = filepath.Clean(dir)
+
+	// Look for enclosing gop.mod.
+	for {
+		if fi, err := os.Stat(filepath.Join(dir, "gop.mod")); err == nil && !fi.IsDir() {
+			return dir
+		}
+		d := filepath.Dir(dir)
+		if d == dir {
+			break
+		}
+		dir = d
+	}
+	return ""
+}
+
+func SetModRoot(dir string) {
+	modRoot = dir
+}
+
+func getcwd() string {
+	path, _ := os.Getwd()
+	return path
+}
+
+var gopRoot = getcwd()
+
+// HasModRoot reports whether a main module is present.
+// HasModRoot may return false even if Enabled returns true: for example, 'get'
+// does not require a main module.
+func HasModRoot() bool {
+	Init()
+	return modRoot != ""
+}
+
+// GopModFilePath returns the effective path of the go.mod file. Normally, this
+// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
+// change its location. ModFilePath calls base.Fatalf if there is no main
+// module, even if -modfile is set.
+func GopModFilePath() string {
+	if !HasModRoot() {
+		log.Fatalf("gop: %v", ErrNoModRoot)
+	}
+	return filepath.Join(modRoot, "gop.mod")
+}
+
+// GoModFilePath returns the effective path of the go.mod file. Normally, this
+// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
+// change its location. ModFilePath calls base.Fatalf if there is no main
+// module, even if -modfile is set.
+func GoModFilePath() string {
+	if !HasModRoot() {
+		log.Fatalf("gop: %v", ErrNoModRoot)
+	}
+	return filepath.Join(modRoot, "go.mod")
+}
+
+// Init determines whether module mode is enabled, locates the root of the
+// current module (if any), sets environment variables for Git subprocesses, and
+// configures the cfg, codehost, load, modfetch, and search packages for use
+// with modules.
+func Init() {
+	if initialized {
+		return
+	}
+	initialized = true
+
+	// Disable any prompting for passwords by Git.
+	// Only has an effect for 2.3.0 or later, but avoiding
+	// the prompt in earlier versions is just too hard.
+	// If user has explicitly set GIT_TERMINAL_PROMPT=1, keep
+	// prompting.
+	// See golang.org/issue/9341 and golang.org/issue/12706.
+	if os.Getenv("GIT_TERMINAL_PROMPT") == "" {
+		os.Setenv("GIT_TERMINAL_PROMPT", "0")
+	}
+
+	// Disable any ssh connection pooling by Git.
+	// If a Git subprocess forks a child into the background to cache a new connection,
+	// that child keeps stdout/stderr open. After the Git subprocess exits,
+	// os /exec expects to be able to read from the stdout/stderr pipe
+	// until EOF to get all the data that the Git subprocess wrote before exiting.
+	// The EOF doesn't come until the child exits too, because the child
+	// is holding the write end of the pipe.
+	// This is unfortunate, but it has come up at least twice
+	// (see golang.org/issue/13453 and golang.org/issue/16104)
+	// and confuses users when it does.
+	// If the user has explicitly set GIT_SSH or GIT_SSH_COMMAND,
+	// assume they know what they are doing and don't step on it.
+	// But default to turning off ControlMaster.
+	if os.Getenv("GIT_SSH") == "" && os.Getenv("GIT_SSH_COMMAND") == "" {
+		os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no")
+	}
+
+	if modRoot != "" {
+		// nothing to do
+	} else {
+		modRoot := findModuleRoot(gopRoot)
+		if modRoot != "" {
+			SetModRoot(modRoot)
+		}
+	}
+}
+
+func findModulePath(dir string) (string, error) {
+	// Look for path in GOPATH.
+	var badPathErr error
+	for _, gpdir := range filepath.SplitList(getGoPath()) {
+		if gpdir == "" {
+			continue
+		}
+		if rel := search.InDir(dir, filepath.Join(gpdir, "src")); rel != "" && rel != "." {
+			path := filepath.ToSlash(rel)
+			return path, nil
+		}
+	}
+
+	reason := "outside GOPATH, module path must be specified"
+	if badPathErr != nil {
+		// return a different error message if the module was in GOPATH, but
+		// the module path determined above would be an invalid path.
+		reason = fmt.Sprintf("bad module path inferred from directory in GOPATH: %v", badPathErr)
+	}
+	msg := `cannot determine module path for source directory %s (%s)
+
+Example usage:
+	'go mod init example.com/m' to initialize a v0 or v1 module
+	'go mod init example.com/m/v2' to initialize a v2 module
+
+Run 'go help mod init' for more information.
+`
+	return "", fmt.Errorf(msg, dir, reason)
+}
+
+// CreateModFile initializes a new module by creating a go.mod file.
+//
+// If modPath is empty, CreateModFile will attempt to infer the path from the
+// directory location within GOPATH.
+//
+// If a vendoring configuration file is present, CreateModFile will attempt to
+// translate it to go.mod directives. The resulting build list may not be
+// exactly the same as in the legacy configuration (for example, we can't get
+// packages at multiple versions from the same module).
+func CreateModFile(modPath string) {
+	modRoot = gopRoot
+	Init()
+	modFilePath := GopModFilePath()
+	if _, err := os.Stat(modFilePath); err == nil {
+		log.Fatalf("gop: %s already exists", modFilePath)
+	}
+
+	if modPath == "" {
+		var err error
+		modPath, err = findModulePath(modRoot)
+		if err != nil {
+			log.Fatalf("gop: %v", err)
+		}
+	}
+
+	fmt.Fprintf(os.Stderr, "gop: creating new gop.mod: module %s\n", modPath)
+	modFile = new(modfile.File)
+	modFile.AddModuleStmt(modPath)
+	addGopStmt() // Add the gop directive before converted module requirements.
+	WriteGopMod()
+}
+
+func Load() {
+	LoadModFile()
+	SyncGoMod()
+	if classModFile != nil && classModFile.Classfile != nil {
+		gengo.RegisterPkgFlags(classModFile.Classfile.ProjExt, gengo.PkgFlagGmx)
+		gengo.RegisterPkgFlags(classModFile.Classfile.WorkExt, gengo.PkgFlagSpx)
+		cl.RegisterClassFileType(classModFile.Classfile.ProjExt,
+			classModFile.Classfile.WorkExt, classModFile.Classfile.PkgPaths...)
+	}
+}
+
+// fixVersion returns a modfile.VersionFixer implemented using the Query function.
+//
+// It resolves commit hashes and branch names to versions,
+// canonicalizes versions that appeared in early vgo drafts,
+// and does nothing for versions that already appear to be canonical.
+//
+// The VersionFixer sets 'fixed' if it ever returns a non-canonical version.
+func fixVersion(fixed *bool) modfile.VersionFixer {
+	return func(path, vers string) (resolved string, err error) {
+		// do nothing
+		return vers, nil
+	}
+}
+
+func fixGoVersion(fixed *bool) gomodfile.VersionFixer {
+	return func(path, vers string) (resolved string, err error) {
+		// do nothing
+		return vers, nil
+	}
+}
+
+// LoadModFile sets Target and, if there is a main module, parses the initial
+// build list from its go.mod file.
+//
+// LoadModFile may make changes in memory, like adding a go directive and
+// ensuring requirements are consistent. WriteGoMod should be called later to
+// write changes out to disk or report errors in readonly mode.
+//
+// As a side-effect, LoadModFile may change cfg.BuildMod to "vendor" if
+// -mod wasn't set explicitly and automatic vendoring should be enabled.
+func LoadModFile() {
+	Init()
+	// if modRoot == "" {
+	// 	Target = module.Version{Path: "command-line-arguments"}
+	// 	targetPrefix = "command-line-arguments"
+	// 	buildList = []module.Version{Target}
+	// 	return
+	// }
+
+	gopmod := GopModFilePath()
+	data, err := modfetch.Read(gopmod)
+	if err != nil {
+		log.Fatalf("gop: %v", err)
+	}
+
+	var fixed bool
+	f, err := modfile.Parse(gopmod, data, fixVersion(&fixed))
+	if err != nil {
+		// Errors returned by modfile.Parse begin with file:line.
+		log.Fatalf("gop: errors parsing gop.mod:\n%s\n", err)
+	}
+	modFile = f
+
+	if f.Module == nil {
+		// No module declaration. Must add module path.
+		log.Fatalf("gop: no module declaration in gop.mod. To specify the module path:\n")
+	}
+
+	LoadClassFile()
+}
+
+// addGoStmt adds a gop directive to the gop.mod file if it does not already include one.
+// The 'gop' version added, if any, is the latest version supported by this toolchain.
+func addGopStmt() {
+	if modFile.Gop != nil && modFile.Gop.Version != "" {
+		return
+	}
+	version := "1." + strconv.Itoa(minorver.Version)
+	if !modfile.GopVersionRE.MatchString(version) {
+		log.Fatalf("gop: unrecognized default version %q", version)
+	}
+	if err := modFile.AddGopStmt(version); err != nil {
+		log.Fatalf("gop: internal error: %v", err)
+	}
+}
+
+// WriteGopMod writes the current build list back to gop.mod.
+func WriteGopMod() {
+	// If we aren't in a module, we don't have anywhere to write a go.mod file.
+	if modRoot == "" {
+		return
+	}
+	addGopStmt()
+
+	modFile.Cleanup()
+
+	new, err := modFile.Format()
+	if err != nil {
+		log.Fatalf("gop: %v", err)
+	}
+
+	errNoChange := errors.New("no update needed")
+
+	err = modfetch.Transform(GopModFilePath(), func(old []byte) ([]byte, error) {
+		if bytes.Equal(old, new) {
+			// The go.mod file is already equal to new, possibly as the result of some
+			// other process.
+			return nil, errNoChange
+		}
+		return new, nil
+	})
+
+	if err != nil && err != errNoChange {
+		log.Fatalf("gop: updating gop.mod: %v", err)
+	}
+}
+
+func getGoPath() string {
+	return os.Getenv("GOPATH")
+}
+
+func SyncGoMod() {
+	gomodPath := GoModFilePath()
+	gomod := &gomodfile.File{}
+	if _, err := os.Stat(gomodPath); err == nil {
+		data, err := modfetch.Read(gomodPath)
+		var fixed bool
+		gomod, err = gomodfile.Parse(gomodPath, data, fixGoVersion(&fixed))
+		if err != nil {
+			// Errors returned by modfile.Parse begin with file:line.
+			log.Fatalf("gop: errors parsing gop.mod:\n%s\n", err)
+		}
+	}
+
+	gomod.AddModuleStmt(modFile.Module.Mod.Path)
+	if modFile.Go != nil {
+		gomod.AddGoStmt(modFile.Go.Version)
+	}
+
+	for _, require := range modFile.Require {
+		gomod.AddRequire(require.Mod.Path, require.Mod.Version)
+	}
+
+	for _, replace := range modFile.Replace {
+		gomod.AddReplace(replace.Old.Path, replace.Old.Version, replace.New.Path, replace.New.Version)
+	}
+
+	for _, exclude := range modFile.Exclude {
+		gomod.AddExclude(exclude.Mod.Path, exclude.Mod.Version)
+	}
+
+	for _, retract := range modFile.Retract {
+		gomod.AddRetract(gomodfile.VersionInterval(retract.VersionInterval), retract.Rationale)
+	}
+
+	if classModFile != nil {
+		for _, require := range classModFile.Require {
+			gomod.AddRequire(require.Mod.Path, require.Mod.Version)
+		}
+
+		for _, replace := range classModFile.Replace {
+			gomod.AddReplace(replace.Old.Path, replace.Old.Version, replace.New.Path, replace.New.Version)
+		}
+
+		for _, exclude := range classModFile.Exclude {
+			gomod.AddExclude(exclude.Mod.Path, exclude.Mod.Version)
+		}
+
+		for _, retract := range classModFile.Retract {
+			gomod.AddRetract(gomodfile.VersionInterval(retract.VersionInterval), retract.Rationale)
+		}
+	}
+
+	gomod.Cleanup()
+
+	new, err := gomod.Format()
+	if err != nil {
+		log.Fatalf("gop: %v", err)
+	}
+
+	errNoChange := errors.New("no update needed")
+	err = modfetch.Transform(GoModFilePath(), func(old []byte) ([]byte, error) {
+		if bytes.Equal(old, new) {
+			// The go.mod file is already equal to new, possibly as the result of some
+			// other process.
+			return nil, errNoChange
+		}
+		return new, nil
+	})
+
+	if err != nil && err != errNoChange {
+		log.Fatalf("gop: updating gop.mod: %v", err)
+	}
+}
+
+func SyncGopMod() {
+	gomodPath := GoModFilePath()
+	gomod := &gomodfile.File{}
+	if _, err := os.Stat(gomodPath); err == nil {
+		data, err := modfetch.Read(gomodPath)
+		var fixed bool
+		gomod, err = gomodfile.Parse(gomodPath, data, fixGoVersion(&fixed))
+		if err != nil {
+			// Errors returned by modfile.Parse begin with file:line.
+			log.Fatalf("gop: errors parsing gop.mod:\n%s\n", err)
+		}
+	}
+
+	if gomod.Go != nil {
+		modFile.AddGoStmt(gomod.Go.Version)
+	}
+
+	for _, require := range gomod.Require {
+		modFile.AddRequire(require.Mod.Path, require.Mod.Version)
+	}
+
+	for _, replace := range gomod.Replace {
+		modFile.AddReplace(replace.Old.Path, replace.Old.Version, replace.New.Path, replace.New.Version)
+	}
+
+	for _, exclude := range gomod.Exclude {
+		modFile.AddExclude(exclude.Mod.Path, exclude.Mod.Version)
+	}
+
+	for _, retract := range gomod.Retract {
+		modFile.AddRetract(modfile.VersionInterval(retract.VersionInterval), retract.Rationale)
+	}
+
+	modFile.Cleanup()
+
+	new, err := modFile.Format()
+	if err != nil {
+		log.Fatalf("gop: %v", err)
+	}
+
+	errNoChange := errors.New("no update needed")
+	err = modfetch.Transform(GopModFilePath(), func(old []byte) ([]byte, error) {
+		if bytes.Equal(old, new) {
+			// The go.mod file is already equal to new, possibly as the result of some
+			// other process.
+			return nil, errNoChange
+		}
+		return new, nil
+	})
+
+	if err != nil && err != errNoChange {
+		log.Fatalf("gop: updating gop.mod: %v", err)
+	}
+}
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index c2d535f2..f226c4b0 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -25,16 +25,18 @@ import (
 	"os/exec"
 	"path/filepath"
 
+	"github.com/qiniu/x/log"
+	"golang.org/x/tools/go/packages"
+
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/gengo"
 	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/cmd/internal/modload"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
-	"github.com/qiniu/x/log"
-	"golang.org/x/tools/go/packages"
 )
 
 // -----------------------------------------------------------------------------
@@ -141,6 +143,7 @@ func runCmd(cmd *base.Command, args []string) {
 	if isDir {
 		srcDir = src
 		gofile = src + "/gop_autogen.go"
+		modload.Load()
 		isDirty = true // TODO: check if code changed
 		if isDirty {
 			pkgs, err = parser.ParseDir(fset, src, nil, parserMode)
@@ -149,6 +152,7 @@ func runCmd(cmd *base.Command, args []string) {
 		}
 	} else {
 		srcDir, file = filepath.Split(src)
+		modload.Load()
 		isGo := filepath.Ext(file) == ".go"
 		if isGo {
 			hash := sha1.Sum([]byte(src))
diff --git a/cmd/internal/search/search.go b/cmd/internal/search/search.go
new file mode 100644
index 00000000..9e72beba
--- /dev/null
+++ b/cmd/internal/search/search.go
@@ -0,0 +1,77 @@
+package search
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+// InDir checks whether path is in the file tree rooted at dir.
+// If so, InDir returns an equivalent path relative to dir.
+// If not, InDir returns an empty string.
+// InDir makes some effort to succeed even in the presence of symbolic links.
+// TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
+func InDir(path, dir string) string {
+	if rel := inDirLex(path, dir); rel != "" {
+		return rel
+	}
+	xpath, err := filepath.EvalSymlinks(path)
+	if err != nil || xpath == path {
+		xpath = ""
+	} else {
+		if rel := inDirLex(xpath, dir); rel != "" {
+			return rel
+		}
+	}
+
+	xdir, err := filepath.EvalSymlinks(dir)
+	if err == nil && xdir != dir {
+		if rel := inDirLex(path, xdir); rel != "" {
+			return rel
+		}
+		if xpath != "" {
+			if rel := inDirLex(xpath, xdir); rel != "" {
+				return rel
+			}
+		}
+	}
+	return ""
+}
+
+// inDirLex is like inDir but only checks the lexical form of the file names.
+// It does not consider symbolic links.
+// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
+// return the suffix. Most uses of str.HasFilePathPrefix should probably
+// be calling InDir instead.
+func inDirLex(path, dir string) string {
+	pv := strings.ToUpper(filepath.VolumeName(path))
+	dv := strings.ToUpper(filepath.VolumeName(dir))
+	path = path[len(pv):]
+	dir = dir[len(dv):]
+	switch {
+	default:
+		return ""
+	case pv != dv:
+		return ""
+	case len(path) == len(dir):
+		if path == dir {
+			return "."
+		}
+		return ""
+	case dir == "":
+		return path
+	case len(path) > len(dir):
+		if dir[len(dir)-1] == filepath.Separator {
+			if path[:len(dir)] == dir {
+				return path[len(dir):]
+			}
+			return ""
+		}
+		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
+			if len(path) == len(dir)+1 {
+				return "."
+			}
+			return path[len(dir)+1:]
+		}
+		return ""
+	}
+}
diff --git a/cmd/internal/version/ver.go b/cmd/internal/version/ver.go
index 1459ea30..f5e955e6 100644
--- a/cmd/internal/version/ver.go
+++ b/cmd/internal/version/ver.go
@@ -18,10 +18,10 @@ package version
 
 import (
 	"fmt"
-	"github.com/goplus/gop/build"
 	"runtime"
 
 	"github.com/goplus/gop"
+	"github.com/goplus/gop/build"
 	"github.com/goplus/gop/cmd/internal/base"
 )
 
diff --git a/go.mod b/go.mod
index 9dd541e4..688c40f5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/gox v1.7.20
+	github.com/goplus/gox v1.7.18
 	github.com/qiniu/x v1.11.5
 	golang.org/x/mod v0.5.1
 	golang.org/x/tools v0.1.7
diff --git a/go.sum b/go.sum
index e5acaaf2..67d73239 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/goplus/gox v1.7.20 h1:lzF8X52vTRYTV3tTLWFmP+BtU+VnXHNdqPpLjpNqg24=
-github.com/goplus/gox v1.7.20/go.mod h1:fUMG5YrEvPjSfGqPnP1gfrZvWlVuA1/9lZy3s/5+Aes=
+github.com/goplus/gox v1.7.18 h1:d4Z4yNVqiwRsZwe7z+I75Qqn1jW7CvhCXRo4Cs/ZvtI=
+github.com/goplus/gox v1.7.18/go.mod h1:fUMG5YrEvPjSfGqPnP1gfrZvWlVuA1/9lZy3s/5+Aes=
 github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U=
 github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 832bfcb2..dec7339e 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -160,7 +160,6 @@ var (
 		".gop": ast.FileTypeGop,
 		".spx": ast.FileTypeSpx,
 		".gmx": ast.FileTypeGmx,
-		".spc": ast.FileTypeGmx, // TODO: dynamic register
 	}
 )
 
diff --git a/cmd/install.go b/scripts/install.go
similarity index 100%
rename from cmd/install.go
rename to scripts/install.go
diff --git a/x/mod/modfile/rule.go b/x/mod/modfile/rule.go
index 6061c458..fa490712 100644
--- a/x/mod/modfile/rule.go
+++ b/x/mod/modfile/rule.go
@@ -957,6 +957,9 @@ func parseExt(s *string) (string, error) {
 			Err: err,
 		}
 	}
+	if t == "" {
+		return "", nil
+	}
 	if len(t) < 2 {
 		return "", &InvalidExtError{
 			Ext: *s,
@@ -1049,6 +1052,26 @@ func (f *File) AddGoStmt(version string) error {
 	return nil
 }
 
+func (f *File) AddGopStmt(version string) error {
+	if !GopVersionRE.MatchString(version) {
+		return fmt.Errorf("invalid language version string %q", version)
+	}
+	if f.Gop == nil {
+		var hint Expr
+		if f.Module != nil && f.Module.Syntax != nil {
+			hint = f.Module.Syntax
+		}
+		f.Gop = &Gop{
+			Version: version,
+			Syntax:  f.Syntax.addLine(hint, "gop", version),
+		}
+	} else {
+		f.Gop.Version = version
+		f.Syntax.updateLine(f.Gop.Syntax, "gop", version)
+	}
+	return nil
+}
+
 // AddRequire sets the first require line for path to version vers,
 // preserving any existing comments for that line and removing all
 // other lines for path.


Test patch:
diff --git a/cl/compile_test.go b/cl/compile_test.go
index a1d9c99c..78413739 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -2792,41 +2792,26 @@ func main() {
 	gopClTest(t, `
 type Fn func() (int, error)
 func Do(fn Fn) {
+	v, err := fn()
+	println(v, err)
 }
 
 Do => (100, nil)
 `, `package main
 
+import fmt "fmt"
+
 type Fn func() (int, error)
 
 func Do(fn Fn) {
+	v, err := fn()
+	fmt.Println(v, err)
 }
 func main() {
 	Do(func() (int, error) {
 		return 100, nil
 	})
 }
-`)
-	gopClTest(t, `
-var fn func(int) (int,error) = x => (x*x, nil)
-`, `package main
-
-var fn func(int) (int, error) = func(x int) (int, error) {
-	return x * x, nil
-}
-`)
-	gopClTest(t, `
-var fn func(int) (int,error)
-fn = x => (x*x, nil)
-`, `package main
-
-var fn func(int) (int, error)
-
-func main() {
-	fn = func(x int) (int, error) {
-		return x * x, nil
-	}
-}
 `)
 }
 
@@ -2853,7 +2838,8 @@ func main() {
 `)
 	gopClTest(t, `
 func Do(fn func() (int, error)) {
-	// ...
+	v, err := fn()
+	println(v, err)
 }
 
 Do => {
@@ -2861,7 +2847,11 @@ Do => {
 }
 `, `package main
 
+import fmt "fmt"
+
 func Do(fn func() (int, error)) {
+	v, err := fn()
+	fmt.Println(v, err)
 }
 func main() {
 	Do(func() (int, error) {
@@ -2914,6 +2904,8 @@ func main() {
 	gopClTest(t, `
 type Fn func() (int, error)
 func Do(fn Fn) {
+	v, err := fn()
+	println(v, err)
 }
 
 Do => {
@@ -2921,40 +2913,19 @@ Do => {
 }
 `, `package main
 
+import fmt "fmt"
+
 type Fn func() (int, error)
 
 func Do(fn Fn) {
+	v, err := fn()
+	fmt.Println(v, err)
 }
 func main() {
 	Do(func() (int, error) {
 		return 100, nil
 	})
 }
-`)
-	gopClTest(t, `
-var fn func(int) (int,error) = x => {
-	return x * x, nil
-}
-`, `package main
-
-var fn func(int) (int, error) = func(x int) (int, error) {
-	return x * x, nil
-}
-`)
-	gopClTest(t, `
-var fn func(int) (int,error)
-fn = x => {
-	return x * x, nil
-}
-`, `package main
-
-var fn func(int) (int, error)
-
-func main() {
-	fn = func(x int) (int, error) {
-		return x * x, nil
-	}
-}
 `)
 }
 
@@ -3475,26 +3446,3 @@ func (p *Point) String() string {
 }
 `)
 }
-
-func TestCallPrintln(t *testing.T) {
-	gopClTest(t, `
-print
-print "hello"
-print("hello")
-println
-println "hello"
-println("hello")
-`, `package main
-
-import fmt "fmt"
-
-func main() {
-	fmt.Print()
-	fmt.Print("hello")
-	fmt.Print("hello")
-	fmt.Println()
-	fmt.Println("hello")
-	fmt.Println("hello")
-}
-`)
-}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 86138ac3..6f408652 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -72,7 +72,7 @@ func main() {
 	foo((x, y, z) => {})
 }
 `)
-	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value to Plot", `
+	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value", `
 type Foo struct {
 	Plot int
 }
@@ -80,7 +80,7 @@ foo := &Foo{
 	Plot: x => (x * 2, x * x),
 }
 `)
-	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value to Plot", `
+	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value", `
 type Foo struct {
 	Plot int
 }
@@ -103,32 +103,13 @@ func foo(func()) {
 foo => (100)
 `)
 	codeErrorTest(t,
-		"./bar.gop:6:8: cannot use lambda literal as type func() int in field value to Plot", `
+		"./bar.gop:6:8: cannot use lambda literal as type func() int in field value", `
 type Foo struct {
 	Plot func() int
 }
 foo := &Foo{
 	Plot: x => (x * 2, x * x),
 }
-`)
-
-	codeErrorTest(t,
-		"./bar.gop:2:18: cannot use lambda literal as type func() in assignment to foo", `
-var foo func() = => (100)
-`)
-	codeErrorTest(t,
-		"./bar.gop:3:7: cannot use lambda literal as type func() in assignment to foo", `
-var foo func()
-foo = => (100)
-`)
-	codeErrorTest(t,
-		"./bar.gop:2:29: lambda unsupport multiple assignment", `
-var foo, foo1 func() = nil, => {}
-`)
-	codeErrorTest(t,
-		"./bar.gop:3:15: lambda unsupport multiple assignment", `
-var foo func()
-_, foo = nil, => {}
 `)
 }
 
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 63b3efc4..4c59d526 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -25,11 +25,12 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/qiniu/x/log"
+
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
-	"github.com/qiniu/x/log"
 )
 
 // -----------------------------------------------------------------------------
diff --git a/x/mod/modfile/rule_test.go b/x/mod/modfile/rule_test.go
index 4245c505..04c327ce 100644
--- a/x/mod/modfile/rule_test.go
+++ b/x/mod/modfile/rule_test.go
@@ -707,6 +707,54 @@ var addGoTests = []struct {
 	},
 }
 
+var addGopTests = []struct {
+	desc    string
+	in      string
+	version string
+	out     string
+}{
+	{
+		`module_only`,
+		`module m
+		`,
+		`1.0.14`,
+		`module m
+		gop 1.0.14
+		`,
+	},
+	{
+		`module_before_require`,
+		`module m
+		require x.y/a v1.2.3
+		`,
+		`1.0.14`,
+		`module m
+		gop 1.0.14
+		require x.y/a v1.2.3
+		`,
+	},
+	{
+		`require_before_module`,
+		`require x.y/a v1.2.3
+		module example.com/inverted
+		`,
+		`1.0.14`,
+		`require x.y/a v1.2.3
+		module example.com/inverted
+		gop 1.0.14
+		`,
+	},
+	{
+		`require_only`,
+		`require x.y/a v1.2.3
+		`,
+		`1.0.14`,
+		`require x.y/a v1.2.3
+		gop 1.0.14
+		`,
+	},
+}
+
 var addExcludeTests = []struct {
 	desc    string
 	in      string
@@ -1454,6 +1502,16 @@ func TestAddGo(t *testing.T) {
 	}
 }
 
+func TestAddGop(t *testing.T) {
+	for _, tt := range addGopTests {
+		t.Run(tt.desc, func(t *testing.T) {
+			testEdit(t, tt.in, tt.out, true, func(f *File) error {
+				return f.AddGopStmt(tt.version)
+			})
+		})
+	}
+}
+
 func TestAddExclude(t *testing.T) {
 	for _, tt := range addExcludeTests {
 		t.Run(tt.desc, func(t *testing.T) {
@@ -1637,6 +1695,48 @@ func TestFixVersion(t *testing.T) {
 	}
 }
 
+var addParseExtTests = []struct {
+	desc    string
+	ext     string
+	want    string
+	wantErr string
+}{
+	{
+		"spx ok",
+		".spx",
+		".spx",
+		"",
+	},
+	{
+		"no ext",
+		"",
+		"",
+		"",
+	},
+	{
+		"not a ext",
+		"gmx",
+		"",
+		"ext \"gmx\" invalid: invalid ext format",
+	},
+}
+
+func TestParseExt(t *testing.T) {
+	for _, tt := range addParseExtTests {
+		t.Run(tt.desc, func(t *testing.T) {
+			ext, err := parseExt(&tt.ext)
+			if err != nil {
+				if err.Error() != tt.wantErr {
+					t.Fatalf("wanterr %s,but got %s", tt.wantErr, err)
+				}
+			}
+			if ext != tt.want {
+				t.Fatalf("want %s,but got %s", tt.want, ext)
+			}
+		})
+	}
+}
+
 var gopmod = `
 module spx