Repository: goplus/gop
Pull request #1003 (2021-12-01 08:28:48)

For start end step
close https://github.com/goplus/gop/issues/875


Patch:
diff --git a/.gitignore b/.gitignore
index ab2048b1..020f65ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,5 +43,3 @@ go.json
 .idea
 
 cmd/qfmt/qfmt
-
-bin/
diff --git a/cl/stmt.go b/cl/stmt.go
index 08732bec..6381083d 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -260,6 +260,14 @@ func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
 //    body
 // end
 func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
+	if re, ok := v.X.(*ast.RangeExpr); ok {
+		tok := token.DEFINE
+		if v.Tok == token.ASSIGN {
+			tok = v.Tok
+		}
+		compileForStmt(ctx, toForStmt(v.For, v.Key, v.Body, re, tok))
+		return
+	}
 	cb := ctx.cb
 	comments := cb.Comments()
 	if v.Tok == token.DEFINE {
@@ -304,6 +312,10 @@ func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
 }
 
 func compileForPhraseStmt(ctx *blockCtx, v *ast.ForPhraseStmt) {
+	if re, ok := v.X.(*ast.RangeExpr); ok {
+		compileForStmt(ctx, toForStmt(v.For, v.Value, v.Body, re, token.DEFINE))
+		return
+	}
 	cb := ctx.cb
 	comments := cb.Comments()
 	names := make([]string, 1, 2)
@@ -333,6 +345,82 @@ func compileForPhraseStmt(ctx *blockCtx, v *ast.ForPhraseStmt) {
 	cb.End()
 }
 
+func toForStmt(forPos token.Pos, value ast.Expr, body *ast.BlockStmt, re *ast.RangeExpr, tok token.Token) *ast.ForStmt {
+	nilIdent := value == nil
+	if !nilIdent {
+		if v, ok := value.(*ast.Ident); ok && v.Name == "_" {
+			nilIdent = true
+		}
+	}
+	if nilIdent {
+		value = &ast.Ident{NamePos: forPos, Name: "_gop_k"}
+	}
+	if re.First == nil {
+		re.First = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "0"}
+	}
+	initLhs := []ast.Expr{value}
+	initRhs := []ast.Expr{re.First}
+	replaceValue := false
+	var cond ast.Expr
+	var post ast.Expr
+	switch re.Last.(type) {
+	case *ast.Ident, *ast.BasicLit:
+		cond = re.Last
+	default:
+		replaceValue = true
+		cond = &ast.Ident{NamePos: forPos, Name: "_gop_end"}
+		initLhs = append(initLhs, cond)
+		initRhs = append(initRhs, re.Last)
+	}
+	if re.Expr3 == nil {
+		post = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "1"}
+	} else {
+		switch re.Expr3.(type) {
+		case *ast.Ident, *ast.BasicLit:
+			post = re.Expr3
+		default:
+			replaceValue = true
+			post = &ast.Ident{NamePos: forPos, Name: "_gop_step"}
+			initLhs = append(initLhs, post)
+			initRhs = append(initRhs, re.Expr3)
+		}
+	}
+	if tok == token.ASSIGN && replaceValue {
+		oldValue := value
+		value = &ast.Ident{NamePos: forPos, Name: "_gop_k"}
+		initLhs[0] = value
+		body.List = append([]ast.Stmt{&ast.AssignStmt{
+			Lhs:    []ast.Expr{oldValue},
+			TokPos: forPos,
+			Tok:    token.ASSIGN,
+			Rhs:    []ast.Expr{value},
+		}}, body.List...)
+		tok = token.DEFINE
+	}
+	return &ast.ForStmt{
+		For: forPos,
+		Init: &ast.AssignStmt{
+			Lhs:    initLhs,
+			TokPos: re.To,
+			Tok:    tok,
+			Rhs:    initRhs,
+		},
+		Cond: &ast.BinaryExpr{
+			X:     value,
+			OpPos: re.To,
+			Op:    token.LSS,
+			Y:     cond,
+		},
+		Post: &ast.AssignStmt{
+			Lhs:    []ast.Expr{value},
+			TokPos: re.Colon2,
+			Tok:    token.ADD_ASSIGN,
+			Rhs:    []ast.Expr{post},
+		},
+		Body: body,
+	}
+}
+
 // for init; cond then
 //    body
 //    post
diff --git a/cmd/goprun/goprun.go b/cmd/goprun/goprun.go
deleted file mode 100644
index 9aedf1b5..00000000
--- a/cmd/goprun/goprun.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"log"
-	"os"
-	"os/exec"
-
-	"github.com/goplus/gop/x/gopmod"
-)
-
-func main() {
-	if len(os.Args) < 2 {
-		fmt.Fprint(os.Stderr, "Usage: gorun <file.go> [switch ...]\n\n")
-		return
-	}
-	ctx := gopmod.New("")
-	goProj, err := ctx.OpenProject(0, os.Args[1])
-	if err != nil {
-		fmt.Fprint(os.Stderr, "OpenProject failed:", err)
-		return
-	}
-	goProj.ExecArgs = os.Args[2:]
-	cmd := ctx.GoCommand("run", goProj)
-	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(err)
-		}
-	}
-}
diff --git a/cmd/gorun/gorun.go b/cmd/gorun/gorun.go
new file mode 100644
index 00000000..d311410a
--- /dev/null
+++ b/cmd/gorun/gorun.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+	"crypto/sha1"
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"github.com/goplus/gop/x/gopmod"
+)
+
+type goFile struct {
+	file string
+	code []byte
+}
+
+func newGoProj(file string, args ...string) *gopmod.Project {
+	code, err := os.ReadFile(file)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	src := &goFile{file: file, code: code}
+	return &gopmod.Project{
+		Source:        src,
+		AutoGenFile:   file,
+		FriendlyFname: filepath.Base(file),
+		ExecArgs:      args,
+	}
+}
+
+func (p *goFile) Fingerp() [20]byte { // source code fingerprint
+	return sha1.Sum(p.code)
+}
+
+func (p *goFile) IsDirty(outFile string, temp bool) bool {
+	return true
+}
+
+func (p *goFile) GenGo(outFile string) error {
+	if p.file != outFile {
+		return os.WriteFile(outFile, p.code, 0666)
+	}
+	return nil
+}
+
+func main() {
+	if len(os.Args) < 2 {
+		fmt.Fprint(os.Stderr, "Usage: gorun <file.go> [switch ...]\n\n")
+		return
+	}
+	goProj := newGoProj(os.Args[1], os.Args[2:]...)
+	ctx := gopmod.New("")
+	cmd := ctx.GoCommand("run", goProj)
+	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(err)
+		}
+	}
+}
diff --git a/cmd/install.go b/cmd/install.go
index e9b35cd7..04f9e103 100644
--- a/cmd/install.go
+++ b/cmd/install.go
@@ -35,12 +35,18 @@ func getcwd() string {
 	return path
 }
 
+func getGopLocalLink() string {
+	path, _ := os.UserHomeDir()
+	return filepath.Join(path, "gop")
+}
+
 func checkPathExist(path string) bool {
 	_, err := os.Stat(path)
 	return !os.IsNotExist(err)
 }
 
 var gopRoot = getcwd()
+var gopLocalLink = getGopLocalLink()
 var initCommandExecuteEnv = os.Environ()
 var commandExecuteEnv = initCommandExecuteEnv
 
@@ -102,8 +108,8 @@ func findTag(commit string) string {
 */
 
 func getGopBuildFlags() string {
-	buildFlags := fmt.Sprintf("-X \"github.com/goplus/gop/env.defaultGopRoot=%s\"", gopRoot)
-	buildFlags += fmt.Sprintf(" -X github.com/goplus/gop/env.buildDate=%s", getBuildDateTime())
+	buildFlags := fmt.Sprintf("-X github.com/goplus/gop/env.defaultGopRoot=%s", gopRoot)
+	buildFlags = fmt.Sprintf("-X github.com/goplus/gop/env.buildDate=%s", getBuildDateTime())
 	if commit, ok := getGitInfo(); ok {
 		buildFlags += fmt.Sprintf(" -X github.com/goplus/gop/env.buildCommit=%s", commit)
 		if buildVer := getBuildVer(); buildVer != "" {
@@ -113,25 +119,19 @@ func getGopBuildFlags() string {
 	return buildFlags
 }
 
-/*
-	func detectGoBinPath() string {
-		goBin, ok := os.LookupEnv("GOBIN")
-		if ok {
-			return goBin
-		}
-
-		goPath, ok := os.LookupEnv("GOPATH")
-		if ok {
-			return filepath.Join(goPath, "bin")
-		}
+func detectGoBinPath() string {
+	goBin, ok := os.LookupEnv("GOBIN")
+	if ok {
+		return goBin
+	}
 
-		homeDir, _ := os.UserHomeDir()
-		return filepath.Join(homeDir, "go", "bin")
+	goPath, ok := os.LookupEnv("GOPATH")
+	if ok {
+		return filepath.Join(goPath, "bin")
 	}
-*/
 
-func detectGopBinPath() string {
-	return filepath.Join(gopRoot, "bin")
+	homeDir, _ := os.UserHomeDir()
+	return filepath.Join(homeDir, "go", "bin")
 }
 
 func buildGoplusTools(useGoProxy bool) {
@@ -150,9 +150,6 @@ func buildGoplusTools(useGoProxy bool) {
 		)
 	}
 
-	// Install Go+ binary files under current ./bin directory.
-	commandExecuteEnv = append(commandExecuteEnv, "GOBIN="+detectGopBinPath())
-
 	println("Installing Go+ tools...")
 	os.Chdir(commandsDir)
 	buildOutput, buildErr, err := execCommand("go", "install", "-v", "-ldflags", buildFlags, "./...")
@@ -164,26 +161,32 @@ func buildGoplusTools(useGoProxy bool) {
 	println(buildOutput)
 
 	println("Go+ tools installed successfully!")
-	showHelpPostInstall()
 }
 
-func showHelpPostInstall() {
-	println("Next:")
-	println("We just installed Go+ into the directory: ", detectGopBinPath())
-	message := `
-To setup a better Go+ development environment,
-we recommend you add the above install directory into your PATH environment variable.
-	`
-	println(message)
+func linkGoplusToLocal() {
+	fmt.Printf("Linking %s to %s\n", gopRoot, gopLocalLink)
+
+	os.Chdir(gopRoot)
+	if gopLocalLink != gopRoot && !checkPathExist(gopLocalLink) {
+		err := os.Symlink(gopRoot, gopLocalLink)
+		if err != nil {
+			println(err.Error())
+		}
+	}
+
+	fmt.Printf("%s linked to %s successfully!\n", gopRoot, gopLocalLink)
 }
 
 func runTestcases() {
 	println("Start running testcases.")
 	os.Chdir(gopRoot)
 
+	path, _ := os.LookupEnv("PATH")
+	path = fmt.Sprintf("%s:", detectGoBinPath()) + path
+	commandExecuteEnv = append(commandExecuteEnv, "PATH="+path)
+
 	coverage := "-coverprofile=coverage.txt"
-	gopCommand := filepath.Join(detectGopBinPath(), "gop")
-	testOutput, testErr, err := execCommand(gopCommand, "test", coverage, "-covermode=atomic", "./...")
+	testOutput, testErr, err := execCommand("gop", "test", coverage, "-covermode=atomic", "./...")
 	println(testOutput)
 	println(testErr)
 	if err != nil {
@@ -193,12 +196,22 @@ func runTestcases() {
 	println("End running testcases.")
 }
 
-func uninstall() {
+func localUninstall() {
 	println("Uninstalling Go+ and related tools.")
 
-	gopBinPath := detectGopBinPath()
-	if checkPathExist(gopBinPath) {
-		if err := os.RemoveAll(gopBinPath); err != nil {
+	goBinPath := detectGoBinPath()
+	filesToRemove := []string{
+		gopLocalLink,
+		filepath.Join(goBinPath, "gop"),
+		filepath.Join(goBinPath, "gopfmt"),
+		filepath.Join(goBinPath, "goptestgo"),
+	}
+
+	for _, file := range filesToRemove {
+		if !checkPathExist(file) {
+			continue
+		}
+		if err := os.Remove(file); err != nil {
 			println(err.Error())
 		}
 	}
@@ -221,6 +234,7 @@ func isInChina() bool {
 
 func main() {
 	isInstall := flag.Bool("install", false, "Install Go+")
+	isBuild := flag.Bool("build", false, "Build the Go+")
 	isTest := flag.Bool("test", false, "Run testcases")
 	isUninstall := flag.Bool("uninstall", false, "Uninstall Go+")
 	isGoProxy := flag.Bool("proxy", false, "Set GOPROXY for people in China")
@@ -233,9 +247,14 @@ func main() {
 		useGoProxy = isInChina()
 	}
 	flagActionMap := map[*bool]func(){
-		isInstall:   func() { buildGoplusTools(useGoProxy) },
-		isUninstall: uninstall,
+		isInstall: func() {
+			buildGoplusTools(useGoProxy)
+			linkGoplusToLocal()
+			println("Go+ is now installed.")
+		},
 		isTest:      runTestcases,
+		isBuild:     func() { buildGoplusTools(useGoProxy) },
+		isUninstall: localUninstall,
 	}
 
 	for flag, action := range flagActionMap {
diff --git a/cmd/internal/run/mod.go b/cmd/internal/run/mod.go
index dcca5e28..d43e5cfe 100644
--- a/cmd/internal/run/mod.go
+++ b/cmd/internal/run/mod.go
@@ -1,12 +1,9 @@
 package run
 
 import (
-	"os"
-	"os/exec"
 	"path/filepath"
 
 	"github.com/goplus/gop/env"
-	"github.com/goplus/gop/x/gopmod"
 	"github.com/qiniu/x/log"
 )
 
@@ -26,37 +23,3 @@ func findGoModDir(dir string) (string, bool) {
 	}
 	return filepath.Dir(modfile), nocachefile
 }
-
-func gopRun(source string, args ...string) {
-	ctx := gopmod.New("")
-	flags := 0
-	if *flagGop {
-		flags = gopmod.FlagGoAsGoPlus
-	}
-	goProj, err := ctx.OpenProject(flags, source)
-	if err != nil {
-		log.Fatalln("OpenProject failed:", err)
-	}
-	goProj.ExecArgs = args
-	goProj.FlagNRINC = *flagNorun
-	goProj.FlagRTOE = *flagRTOE
-	if goProj.FlagRTOE {
-		goProj.UseDefaultCtx = true
-	}
-	cmd := ctx.GoCommand("run", goProj)
-	if cmd.IsValid() {
-		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(err)
-			}
-		}
-	}
-}
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index 47def396..4a4cd838 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -18,6 +18,8 @@
 package run
 
 import (
+	"crypto/sha1"
+	"encoding/base64"
 	"fmt"
 	"os"
 	"os/exec"
@@ -111,8 +113,9 @@ func runCmd(cmd *base.Command, args []string) {
 	isDir := fi.IsDir()
 
 	var isDirty bool
-	var srcDir, gofile string
+	var srcDir, file, gofile string
 	var pkgs map[string]*ast.Package
+	onExit = nil
 	if isDir {
 		srcDir = src
 		gofile = src + "/gop_autogen.go"
@@ -124,8 +127,37 @@ func runCmd(cmd *base.Command, args []string) {
 			return
 		}
 	} else {
-		gopRun(src, args...)
-		return
+		srcDir, file = filepath.Split(src)
+		modload.Load()
+		isGo := filepath.Ext(file) == ".go"
+		if isGo {
+			hash := sha1.Sum([]byte(src))
+			dir := os.Getenv("HOME") + "/.gop/run"
+			os.MkdirAll(dir, 0755)
+			gofile = dir + "/g" + base64.RawURLEncoding.EncodeToString(hash[:]) + file
+			if *flagRTOE { // remove tempfile on error
+				onExit = func() {
+					os.Remove(gofile)
+				}
+			}
+		} else if hasMultiFiles(srcDir, ".gop") {
+			gofile = filepath.Join(srcDir, "gop_autogen_"+file+".go")
+		} else {
+			gofile = srcDir + "/gop_autogen.go"
+		}
+		isDirty = fileIsDirty(fi, gofile)
+		if isDirty {
+			if isGo {
+				fmt.Println("==> GenGo to", gofile)
+			}
+			if *flagGop {
+				pkgs, err = parser.Parse(fset, src, nil, parserMode)
+			} else {
+				pkgs, err = parser.Parse(fset, src, nil, 0) // TODO: only to check dependencies
+			}
+		} else if *flagNorun {
+			return
+		}
 	}
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
@@ -164,6 +196,18 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 }
 
+func fileIsDirty(fi os.FileInfo, gofile string) bool {
+	fiDest, err := os.Stat(gofile)
+	if err != nil {
+		return true
+	}
+	return fi.ModTime().After(fiDest.ModTime())
+}
+
+var (
+	onExit func()
+)
+
 func goRun(file string, args []string) {
 	goArgs := make([]string, len(args)+2)
 	goArgs[0] = "run"
@@ -176,6 +220,9 @@ func goRun(file string, args []string) {
 	cmd.Env = os.Environ()
 	err := cmd.Run()
 	if err != nil {
+		if onExit != nil {
+			onExit()
+		}
 		switch e := err.(type) {
 		case *exec.ExitError:
 			os.Exit(e.ExitCode())
@@ -223,4 +270,21 @@ func runGoPkg(src string, args []string, doRun bool) {
 	}
 }
 
+func hasMultiFiles(srcDir string, ext string) bool {
+	var has bool
+	if f, err := os.Open(srcDir); err == nil {
+		defer f.Close()
+		fis, _ := f.ReadDir(-1)
+		for _, fi := range fis {
+			if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
+				if has {
+					return true
+				}
+				has = true
+			}
+		}
+	}
+	return false
+}
+
 // -----------------------------------------------------------------------------
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 9be2ad19..dec7339e 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -176,30 +176,6 @@ func RegisterFileType(ext string, format ast.FileType) {
 
 // -----------------------------------------------------------------------------
 
-func ParseFiles(fset *token.FileSet, files []string, mode Mode) (map[string]*ast.Package, error) {
-	return ParseFSFiles(fset, local, files, mode)
-}
-
-func ParseFSFiles(fset *token.FileSet, fs FileSystem, files []string, mode Mode) (map[string]*ast.Package, error) {
-	ret := map[string]*ast.Package{}
-	for _, file := range files {
-		f, err := ParseFSFile(fset, fs, file, nil, mode)
-		if err != nil {
-			return nil, err
-		}
-		pkgName := f.Name.Name
-		pkg, ok := ret[pkgName]
-		if !ok {
-			pkg = &ast.Package{Name: pkgName, Files: make(map[string]*ast.File)}
-			ret[pkgName] = pkg
-		}
-		pkg.Files[file] = f
-	}
-	return ret, nil
-}
-
-// -----------------------------------------------------------------------------
-
 // ParseFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
 func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
 	return ParseFSFile(fset, local, filename, src, mode)
diff --git a/x/gopmod/gocmd.go b/x/gopmod/gocmd.go
index b405ca85..9068e16f 100644
--- a/x/gopmod/gocmd.go
+++ b/x/gopmod/gocmd.go
@@ -1,24 +1,7 @@
-/*
- * 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 gopmod
 
 import (
 	"fmt"
-	"os"
 	"os/exec"
 	"path/filepath"
 
@@ -53,19 +36,17 @@ func LoadFlags() string {
 
 type GoCmd struct {
 	*exec.Cmd
-	after func(error) error
-}
-
-func (p GoCmd) IsValid() bool {
-	return p.Cmd != nil
+	after func() error
 }
 
 func (p GoCmd) Run() error {
-	err := p.Cmd.Run()
+	if err := p.Cmd.Run(); err != nil {
+		return err
+	}
 	if p.after != nil {
-		return p.after(err)
+		return p.after()
 	}
-	return err
+	return nil
 }
 
 func goCommand(dir, op string, t *goTarget) (ret GoCmd) {
@@ -75,19 +56,12 @@ func goCommand(dir, op string, t *goTarget) (ret GoCmd) {
 	exargs = append(exargs, proj.BuildArgs...) // len(proj.BuildArgs)
 	exargs = appendLdflags(exargs, op)         // 2
 	if op == "run" && t.defctx {               // 2
-		afterDir, goFile, outFile := dir, t.goFile, t.outFile
-		dir, _ = filepath.Split(goFile)
+		afterDir := dir
+		dir, _ = filepath.Split(t.goFile)
 		exargs[0] = "build"
-		exargs = append(exargs, "-o", outFile, goFile)
-		ret.after = func(e error) error {
-			if e == nil {
-				e = runCommand(afterDir, outFile, proj.ExecArgs...)
-				os.Remove(outFile)
-			}
-			if e != nil && t.proj.FlagRTOE { // remove tempfile on error
-				os.Remove(goFile)
-			}
-			return e
+		exargs = append(exargs, "-o", t.outFile, t.goFile)
+		ret.after = func() error {
+			return runCommand(afterDir, t.outFile, proj.ExecArgs...)
 		}
 	} else {
 		exargs = append(exargs, t.goFile)         // 1
diff --git a/x/gopmod/gofile.go b/x/gopmod/gofile.go
deleted file mode 100644
index 35923ab2..00000000
--- a/x/gopmod/gofile.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package gopmod
-
-import (
-	"crypto/sha1"
-	"os"
-	"path/filepath"
-)
-
-// -----------------------------------------------------------------------------
-
-type goFile struct {
-	file string
-}
-
-func openFromGoFile(file string) (proj *Project, err error) {
-	proj = &Project{
-		Source:        &goFile{file: file},
-		AutoGenFile:   file,
-		FriendlyFname: filepath.Base(file),
-	}
-	return
-}
-
-func (p *goFile) Fingerp() (ret *Fingerp, err error) { // source code fingerprint
-	file, err := filepath.Abs(p.file)
-	if err != nil {
-		return
-	}
-	fi, err := os.Stat(file)
-	if err != nil {
-		return
-	}
-	hash := sha1.Sum([]byte(file))
-	return &Fingerp{Hash: hash, ModTime: fi.ModTime()}, nil
-}
-
-func (p *goFile) GenGo(outFile, modFile string) error {
-	if p.file != outFile {
-		code, err := os.ReadFile(p.file)
-		if err != nil {
-			return err
-		}
-		return os.WriteFile(outFile, code, 0666)
-	}
-	return nil
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopmod/gopfiles.go b/x/gopmod/gopfiles.go
deleted file mode 100644
index 961bf512..00000000
--- a/x/gopmod/gopfiles.go
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package gopmod
-
-import (
-	"bytes"
-	"crypto/sha1"
-	"log"
-	"os"
-	"path/filepath"
-	"time"
-
-	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/token"
-	"github.com/goplus/gox"
-)
-
-// -----------------------------------------------------------------------------
-
-type gopFiles struct {
-	files []string
-}
-
-func (p *Context) openFromGopFiles(files []string) (proj *Project, err error) {
-	proj = &Project{
-		Source: &gopFiles{files: files},
-	}
-	if len(files) == 1 {
-		file := files[0]
-		srcDir, fname := filepath.Split(file)
-		var autogen string
-		if hasMultiFiles(srcDir, ".gop") {
-			autogen = "gop_autogen_" + fname + ".go"
-		} else {
-			autogen = "gop_autogen.go"
-		}
-		proj.FriendlyFname = fname
-		proj.AutoGenFile = srcDir + autogen
-	}
-	return
-}
-
-func hasMultiFiles(srcDir string, ext string) bool {
-	var has bool
-	if f, err := os.Open(srcDir); err == nil {
-		defer f.Close()
-		fis, _ := f.ReadDir(-1)
-		for _, fi := range fis {
-			if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
-				if has {
-					return true
-				}
-				has = true
-			}
-		}
-	}
-	return false
-}
-
-func (p *gopFiles) Fingerp() (*Fingerp, error) { // source code fingerprint
-	var buf bytes.Buffer
-	var lastModTime time.Time
-	for _, file := range p.files {
-		absfile, err := filepath.Abs(file)
-		if err != nil {
-			return nil, err
-		}
-		if buf.Len() >= 0 {
-			buf.WriteByte('\n')
-		}
-		buf.WriteString(absfile)
-		fi, err := os.Stat(absfile)
-		if err != nil {
-			return nil, err
-		}
-		modTime := fi.ModTime()
-		if modTime.After(lastModTime) {
-			lastModTime = modTime
-		}
-	}
-	hash := sha1.Sum(buf.Bytes())
-	return &Fingerp{Hash: hash, ModTime: lastModTime}, nil
-}
-
-const (
-	parserMode = parser.ParseComments
-)
-
-func (p *gopFiles) GenGo(outFile, modFile string) error {
-	fset := token.NewFileSet()
-	pkgs, err := parser.ParseFiles(fset, p.files, parserMode)
-	if err != nil {
-		return err
-	}
-	if len(pkgs) != 1 {
-		log.Panicln("TODO: mutli packages -", len(pkgs))
-	}
-	mainPkg, ok := pkgs["main"]
-	if !ok {
-		panic("TODO: main package not found")
-	}
-
-	srcDir, _ := filepath.Split(outFile)
-	modDir, _ := filepath.Split(modFile)
-	conf := &cl.Config{
-		Dir: modDir, TargetDir: srcDir, Fset: fset, CacheLoadPkgs: true, PersistLoadPkgs: true}
-	out, err := cl.NewPackage("", mainPkg, conf)
-	if err != nil {
-		return err
-	}
-	err = gox.WriteFile(outFile, out, false)
-	if err != nil {
-		return err
-	}
-	conf.PkgsLoader.Save()
-	return nil
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopmod/gopmod.go b/x/gopmod/gopmod.go
index 79ae1a7c..26281fde 100644
--- a/x/gopmod/gopmod.go
+++ b/x/gopmod/gopmod.go
@@ -24,23 +24,17 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"runtime"
 	"strings"
-	"time"
 
 	"github.com/goplus/gop/env"
 )
 
 // -----------------------------------------------------------------------------
 
-type Fingerp struct {
-	Hash    [20]byte
-	ModTime time.Time
-}
-
 type Source interface {
-	Fingerp() (*Fingerp, error) // source code fingerprint
-	GenGo(outFile, modFile string) error
+	Fingerp() [20]byte // source code fingerprint
+	IsDirty(outFile string, defctx bool) bool
+	GenGo(outFile string) error
 }
 
 type Project struct {
@@ -49,10 +43,6 @@ type Project struct {
 	FriendlyFname string // friendly fname of source
 	BuildArgs     []string
 	ExecArgs      []string
-	UseDefaultCtx bool
-	ForceToGen    bool
-	FlagNRINC     bool // do not run if not changed
-	FlagRTOE      bool // remove tempfile on error
 }
 
 type Context struct {
@@ -62,52 +52,32 @@ type Context struct {
 }
 
 func New(dir string) *Context {
+	defctx := false
 	modfile, err := env.GOPMOD(dir)
 	if err != nil {
-		return NewDefault(dir)
-	}
-	return &Context{modfile: modfile, dir: dir}
-}
-
-func NewDefault(dir string) *Context {
-	modfile := env.HOME() + "/.gop/run/go.mod"
-	if _, err := os.Stat(modfile); os.IsNotExist(err) {
-		genDefaultGopMod(modfile)
+		modfile = env.HOME() + "/.gop/cmd/go.mod"
+		if _, err := os.Stat(modfile); os.IsNotExist(err) {
+			genDefaultGopMod(modfile)
+		}
+		defctx = true
 	}
-	return &Context{modfile: modfile, dir: dir, defctx: true}
+	return &Context{modfile: modfile, dir: dir, defctx: defctx}
 }
 
 func (p *Context) GoCommand(op string, src *Project) GoCmd {
-	if src.UseDefaultCtx {
-		p = NewDefault(p.dir)
-	}
-	fp, err := src.Fingerp()
-	if err != nil {
-		log.Panicln(err)
-	}
-	out := p.out(src, fp.Hash[:])
-	if src.ForceToGen || fileIsDirty(fp.ModTime, out.goFile) {
+	out := p.out(src)
+	if src.IsDirty(out.goFile, p.defctx) {
 		if p.defctx {
 			dir, _ := filepath.Split(out.goFile)
 			os.Mkdir(dir, 0755)
 		}
-		if err := src.GenGo(out.goFile, p.modfile); err != nil {
+		if err := src.GenGo(out.goFile); err != nil {
 			log.Panicln(err)
 		}
-	} else if src.FlagNRINC { // do not run if not changed
-		return GoCmd{}
 	}
 	return goCommand(p.dir, op, &out)
 }
 
-func fileIsDirty(srcMod time.Time, destFile string) bool {
-	fiDest, err := os.Stat(destFile)
-	if err != nil {
-		return true
-	}
-	return srcMod.After(fiDest.ModTime())
-}
-
 type goTarget struct {
 	goFile  string
 	outFile string
@@ -115,30 +85,24 @@ type goTarget struct {
 	defctx  bool
 }
 
-func (p *Context) out(src *Project, hash []byte) (ret goTarget) {
+func (p *Context) out(src *Project) (ret goTarget) {
+	hash := src.Fingerp()
 	fname := src.FriendlyFname
 	if !strings.HasSuffix(fname, ".go") {
 		fname += ".go"
 	}
 	dir, _ := filepath.Split(p.modfile)
-	ret.outFile = dir + "g" + base64.RawURLEncoding.EncodeToString(hash)
+	ret.outFile = dir + "g" + base64.RawURLEncoding.EncodeToString(hash[:])
 	ret.proj = src
 	ret.defctx = p.defctx
-	if ret.defctx || src.AutoGenFile == "" {
+	if ret.defctx {
 		ret.goFile = ret.outFile + fname
 	} else {
 		ret.goFile = src.AutoGenFile
 	}
-	if inWindows {
-		ret.outFile += ".exe"
-	}
 	return
 }
 
-const (
-	inWindows = (runtime.GOOS == "windows")
-)
-
 // -----------------------------------------------------------------------------
 
 const (
@@ -164,16 +128,8 @@ replace (
 
 func genGomodFile(modfile string) {
 	var buf bytes.Buffer
-	var err error
-	var gopRoot = GOPROOT
-	if inWindows {
-		if gopRoot, err = filepath.Rel(filepath.Dir(modfile), gopRoot); err != nil {
-			log.Panicln(err)
-		}
-		gopRoot = filepath.ToSlash(gopRoot)
-	}
-	fmt.Fprintf(&buf, gomodFormat, GOPVERSION, gopRoot)
-	err = os.WriteFile(modfile, buf.Bytes(), 0666)
+	fmt.Fprintf(&buf, gomodFormat, GOPVERSION, GOPROOT)
+	err := os.WriteFile(modfile, buf.Bytes(), 0666)
 	if err != nil {
 		log.Panicln(err)
 	}
diff --git a/x/gopmod/gopproj.go b/x/gopmod/gopproj.go
deleted file mode 100644
index 5ca47e11..00000000
--- a/x/gopmod/gopproj.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package gopmod
-
-import (
-	"os"
-	"path/filepath"
-)
-
-// -----------------------------------------------------------------------------
-
-const (
-	FlagGoAsGoPlus = 1 << iota
-)
-
-func (p *Context) OpenProject(flags int, args ...string) (proj *Project, err error) {
-	if len(args) != 1 {
-		return p.openFromGopFiles(args)
-	}
-	src := args[0]
-	fi, err := os.Stat(src)
-	if err != nil {
-		return
-	}
-	if fi.IsDir() {
-		return p.openFromDir(src)
-	}
-	if (flags&FlagGoAsGoPlus) == 0 && filepath.Ext(src) == ".go" {
-		return openFromGoFile(src)
-	}
-	return p.openFromGopFiles(args)
-}
-
-// -----------------------------------------------------------------------------
-
-func (p *Context) openFromDir(dir string) (proj *Project, err error) {
-	panic("todo")
-}
-
-// -----------------------------------------------------------------------------


Test patch:
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 0c1d1322..4fe15af4 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -19,6 +19,7 @@ package cl_test
 import (
 	"bytes"
 	"os"
+	"strings"
 	"sync"
 	"syscall"
 	"testing"
@@ -3253,24 +3254,26 @@ func newRepo() Repo {
 	}
 }
 
+func testRangeExpr(t *testing.T, codeTpl, expect string) {
+	for k, s := range []string{" <- ", " := range ", " = range "} {
+		if k == 2 {
+			codeTpl = "i:=0\n" + codeTpl
+			expect = strings.Replace(expect, "for i := ", "i := 0\n\tfor i = ", -1)
+		}
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
 func TestRangeExpr(t *testing.T) {
-	gopClTest(t, `
-for i := range :10 {
+	testRangeExpr(t, `
+for i $ :10 {
 	println(i)
 }`, `package main
 
-import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
-)
+import fmt "fmt"
 
 func main() {
-	for _gop_it := builtin.NewRange__0(0, 10, 1).Gop_Enum(); ; {
-		var _gop_ok bool
-		i, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
+	for i := 0; i < 10; i += 1 {
 		fmt.Println(i)
 	}
 }
@@ -3278,23 +3281,15 @@ func main() {
 }
 
 func TestRangeExpr2(t *testing.T) {
-	gopClTest(t, `
-for i := range 1:10:2 {
+	testRangeExpr(t, `
+for i $ 1:10:2 {
 	println(i)
 }`, `package main
 
-import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
-)
+import fmt "fmt"
 
 func main() {
-	for _gop_it := builtin.NewRange__0(1, 10, 2).Gop_Enum(); ; {
-		var _gop_ok bool
-		i, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
+	for i := 1; i < 10; i += 2 {
 		fmt.Println(i)
 	}
 }
@@ -3302,23 +3297,15 @@ func main() {
 }
 
 func TestRangeExpr3(t *testing.T) {
-	gopClTest(t, `
-for i := range 1:10 {
+	testRangeExpr(t, `
+for i $ 1:10 {
 	println(i)
 }`, `package main
 
-import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
-)
+import fmt "fmt"
 
 func main() {
-	for _gop_it := builtin.NewRange__0(1, 10, 1).Gop_Enum(); ; {
-		var _gop_ok bool
-		i, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
+	for i := 1; i < 10; i += 1 {
 		fmt.Println(i)
 	}
 }
@@ -3326,23 +3313,15 @@ func main() {
 }
 
 func TestRangeExpr4(t *testing.T) {
-	gopClTest(t, `
-for i := range :10:2 {
+	testRangeExpr(t, `
+for i $ :10:2 {
 	println(i)
 }`, `package main
 
-import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
-)
+import fmt "fmt"
 
 func main() {
-	for _gop_it := builtin.NewRange__0(0, 10, 2).Gop_Enum(); ; {
-		var _gop_ok bool
-		i, _gop_ok := _gop_it.Next()
-		if !_gop_ok {
-			break
-		}
+	for i := 0; i < 10; i += 2 {
 		fmt.Println(i)
 	}
 }
@@ -3355,19 +3334,149 @@ for range :10 {
 	println("Hi")
 }`, `package main
 
+import fmt "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr6(t *testing.T) {
+	gopClTest(t, `
+for _ <- :10 {
+	println("Hi")
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr7(t *testing.T) {
+	gopClTest(t, `
+println [x for x <- 0:3:1]
+`, `package main
+
 import (
 	fmt "fmt"
 	builtin "github.com/goplus/gop/builtin"
 )
 
 func main() {
-	for _gop_it := builtin.NewRange__0(0, 10, 1).Gop_Enum(); ; {
-		var _gop_ok bool
-		_, _gop_ok = _gop_it.Next()
-		if !_gop_ok {
-			break
+	fmt.Println(func() (_gop_ret []int) {
+		for _gop_it := builtin.NewRange__0(0, 3, 1).Gop_Enum(); ; {
+			var _gop_ok bool
+			x, _gop_ok := _gop_it.Next()
+			if !_gop_ok {
+				break
+			}
+			_gop_ret = append(_gop_ret, x)
 		}
-		fmt.Println("Hi")
+		return
+	}())
+}
+`)
+}
+
+func testRangeExpr8(t *testing.T, codeTpl, expect string) {
+	for _, s := range []string{" <- ", " := range "} {
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
+func TestRangeExpr8(t *testing.T) {
+	testRangeExpr8(t, `
+	type T struct{}
+
+	func (t T)start()int{
+		return 0
+	}
+	func (t T)end()int{
+		return 3 
+	}
+	func (t T)step()int{
+		return 1 
+	}
+	
+	t:=T{}
+	
+	for i <- t.start():t.end():t.step(){
+		println i
+	}
+	`, `package main
+
+import fmt "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	for i, _gop_end, _gop_step := t.start(), t.end(), t.step(); i < _gop_end; i += _gop_step {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr9(t *testing.T) {
+	testRangeExpr8(t, `
+	type T struct{}
+
+	func (t T)start()int{
+		return 0
+	}
+	func (t T)end()int{
+		return 3 
+	}
+	func (t T)step()int{
+		return 1 
+	}
+	
+	t:=T{}
+	i:=0
+	for i =range t.start():t.end():t.step(){
+		println i
+	}
+	`, `package main
+
+import fmt "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	i := 0
+	for _gop_k, _gop_end, _gop_step := t.start(), t.end(), t.step(); _gop_k < _gop_end; _gop_k += _gop_step {
+		i = _gop_k
+		fmt.Println(i)
 	}
 }
 `)
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 9db148cb..4c59d526 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -54,10 +54,10 @@ func TestReadSource(t *testing.T) {
 	}
 }
 
-func TestParseFiles(t *testing.T) {
+func TestParseFile(t *testing.T) {
 	fset := token.NewFileSet()
-	if _, err := ParseFiles(fset, []string{"/foo/bar/not-exists"}, PackageClauseOnly); err == nil {
-		t.Fatal("ParseFiles failed: no error?")
+	if _, err := ParseFile(fset, "/foo/bar/not-exists", nil, PackageClauseOnly); err == nil {
+		t.Fatal("ParseFile failed: no error?")
 	}
 }
 
@@ -120,17 +120,6 @@ func TestParseGo(t *testing.T) {
 	}
 }
 
-func TestParseGoFiles(t *testing.T) {
-	fset := token.NewFileSet()
-	pkgs, err := ParseFiles(fset, []string{"./_testdata/functype/functype.go"}, Trace)
-	if err != nil {
-		t.Fatal("TestParseGoFiles: ParseFiles failed -", err)
-	}
-	if len(pkgs) != 1 {
-		t.Fatal("TestParseGoFiles failed: len(pkgs) =", len(pkgs))
-	}
-}
-
 func TestFromTestdata(t *testing.T) {
 	sel := ""
 	dir, err := os.Getwd()