Repository: goplus/gop
Pull request #1212 (2022-05-21 16:12:10)

v1.1.0-rc1


Patch:
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 7b7783fe..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.0-beta5
diff --git a/all.bat b/all.bat
index 5395a855..0812ec4b 100644
--- a/all.bat
+++ b/all.bat
@@ -1,1 +1,1 @@
-go run cmd/make.go --install --autoproxy
+go run cmd\make.go --install --autoproxy
diff --git a/ast/ast.go b/ast/ast.go
index 9c74b4e0..bec832a8 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -181,7 +181,7 @@ type (
 	// A BasicLit node represents a literal of basic type.
 	BasicLit struct {
 		ValuePos token.Pos   // literal position
-		Kind     token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING
+		Kind     token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING or token.CSTRING
 		Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
 	}
 
diff --git a/build_install_run.go b/build_install_run.go
index ea06f000..706ac959 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -17,6 +17,7 @@
 package gop
 
 import (
+	"errors"
 	"log"
 	"os"
 
@@ -26,7 +27,7 @@ import (
 // -----------------------------------------------------------------------------
 
 func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	err = GenGo(dir, conf)
+	_, _, err = GenGo(dir, conf)
 	if err != nil {
 		return
 	}
@@ -34,13 +35,20 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err
 }
 
 func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, true)
+	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true)
 	if err != nil {
 		return
 	}
 	old := chdir(localDir)
 	defer os.Chdir(old)
-	return gocmd.Install(".", install)
+	return gocmd.Install(cwdParam(recursively), install)
+}
+
+func cwdParam(recursively bool) string {
+	if recursively {
+		return "./..."
+	}
+	return "."
 }
 
 func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) {
@@ -66,7 +74,7 @@ func chdir(dir string) string {
 // -----------------------------------------------------------------------------
 
 func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	err = GenGo(dir, conf)
+	_, _, err = GenGo(dir, conf)
 	if err != nil {
 		return
 	}
@@ -74,13 +82,13 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 }
 
 func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
 		return
 	}
 	old := chdirAndMod(localDir)
 	defer restoreDirAndMod(old)
-	return gocmd.Build(".", build)
+	return gocmd.Build(cwdParam(recursively), build)
 }
 
 func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
@@ -104,7 +112,7 @@ func restoreDirAndMod(old string) {
 // -----------------------------------------------------------------------------
 
 func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
-	err = GenGo(dir, conf)
+	_, _, err = GenGo(dir, conf)
 	if err != nil {
 		return
 	}
@@ -112,10 +120,13 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err
 }
 
 func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
-	localDir, err := GenGoPkgPath("", pkgPath, conf, true)
+	localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true)
 	if err != nil {
 		return
 	}
+	if recursively {
+		return errors.New("can't use ... pattern for `gop run` command")
+	}
 	if chDir {
 		old := chdir(localDir)
 		defer os.Chdir(old)
@@ -135,7 +146,7 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 // -----------------------------------------------------------------------------
 
 func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
-	err = GenGo(dir, conf)
+	_, _, err = GenGo(dir, conf)
 	if err != nil {
 		return
 	}
@@ -143,13 +154,13 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 }
 
 func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
-	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
 		return
 	}
 	old := chdirAndMod(localDir)
 	defer restoreDirAndMod(old)
-	return gocmd.Test(".", test)
+	return gocmd.Test(cwdParam(recursively), test)
 }
 
 func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) {
diff --git a/cl/c.go b/cl/c.go
new file mode 100644
index 00000000..39b4c989
--- /dev/null
+++ b/cl/c.go
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cl
+
+import (
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gox"
+)
+
+const (
+	c2goInvalid = iota
+	c2goStandard
+	c2goUserDef
+)
+
+func checkC2go(pkgPath string) (realPkgPath string, kind int) {
+	if strings.HasPrefix(pkgPath, "C") {
+		if len(pkgPath) == 1 {
+			return "libc", c2goStandard
+		}
+		if pkgPath[1] == '/' {
+			realPkgPath = pkgPath[2:]
+			if strings.IndexByte(realPkgPath, '/') < 0 {
+				kind = c2goStandard
+			} else {
+				kind = c2goUserDef
+			}
+		}
+	}
+	return
+}
+
+func c2goBase(base string) string {
+	if base == "" {
+		base = "github.com/goplus/"
+	} else if !strings.HasSuffix(base, "/") {
+		base += "/"
+	}
+	return base
+}
+
+// -----------------------------------------------------------------------------
+
+func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) *gox.PkgRef {
+	cpkg, err := ctx.cpkgs.Import(realPath)
+	if err != nil {
+		pos := ctx.Position(src.Pos())
+		ctx.handleCodeErrorf(&pos,
+			"%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath)
+		return nil
+	}
+	ctx.clookups = append(ctx.clookups, cpkg)
+	return cpkg.Pkg()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/compile.go b/cl/compile.go
index 58fee873..099f3ad8 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -31,6 +31,7 @@ import (
 	"github.com/goplus/gop/ast/fromgo"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
 	"github.com/goplus/gox/packages"
 	"github.com/goplus/mod/modfile"
 )
@@ -77,6 +78,10 @@ type Class = modfile.Classfile
 
 // Config of loading Go+ packages.
 type Config struct {
+	// Fset provides source position information for syntax trees and types.
+	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
+	Fset *token.FileSet
+
 	// WorkingDir is the directory in which to run gop compiler.
 	WorkingDir string
 
@@ -86,9 +91,12 @@ type Config struct {
 	// GopRoot specifies the Go+ root directory.
 	GopRoot string
 
-	// Fset provides source position information for syntax trees and types.
-	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
-	Fset *token.FileSet
+	// C2goBase specifies base of standard c2go packages.
+	// Default is github.com/goplus/.
+	C2goBase string
+
+	// LookupPub lookups the c2go package pubfile (named c2go.a.pub).
+	LookupPub func(pkgPath string) (pubfile string, err error)
 
 	// LookupClass lookups a class by specified file extension.
 	LookupClass func(ext string) (c *Class, ok bool)
@@ -244,6 +252,7 @@ func doInitMethods(ld *typeLoader) {
 type pkgCtx struct {
 	*nodeInterp
 	*gmxSettings
+	cpkgs *cpackages.Importer
 	syms  map[string]loader
 	inits []func()
 	tylds []*typeLoader
@@ -257,6 +266,8 @@ type blockCtx struct {
 	fset         *token.FileSet
 	imports      map[string]*gox.PkgRef
 	lookups      []*gox.PkgRef
+	clookups     []*cpackages.PkgRef
+	c2goBase     string // default is `github.com/goplus/`
 	targetDir    string
 	classRecv    *ast.FieldList // available when gmxSettings != nil
 	fileLine     bool
@@ -379,8 +390,12 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		imp = packages.NewImporter(fset, workingDir)
 	}
 	files := pkg.Files
-	interp := &nodeInterp{fset: fset, files: files, workingDir: workingDir}
-	ctx := &pkgCtx{syms: make(map[string]loader), nodeInterp: interp}
+	interp := &nodeInterp{
+		fset: fset, files: files, workingDir: workingDir,
+	}
+	ctx := &pkgCtx{
+		syms: make(map[string]loader), nodeInterp: interp,
+	}
 	confGox := &gox.Config{
 		Fset:            fset,
 		Importer:        newGopImporter(conf.GopRoot, imp),
@@ -391,6 +406,9 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		DefaultGoFile:   defaultGoFile,
 	}
 	p = gox.NewPackage(pkgPath, pkg.Name, confGox)
+	ctx.cpkgs = cpackages.NewImporter(&cpackages.Config{
+		Pkg: p, LookupPub: conf.LookupPub,
+	})
 	for file, gmx := range files {
 		if gmx.IsProj {
 			ctx.gmxSettings = newGmx(p, file, conf)
@@ -402,7 +420,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		ctx := &blockCtx{
 			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
 			fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass,
-			imports: make(map[string]*gox.PkgRef),
+			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]*gox.PkgRef),
 		}
 		preloadGopFile(p, ctx, fpath, f, conf)
 	}
@@ -954,8 +972,18 @@ func simplifyGopPackage(pkgPath string) string {
 }
 
 func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
-	pkgPath := toString(spec.Path)
-	pkg := ctx.pkg.Import(simplifyGopPackage(pkgPath))
+	var pkg *gox.PkgRef
+	var pkgPath = toString(spec.Path)
+	if realPath, kind := checkC2go(pkgPath); kind != c2goInvalid {
+		if kind == c2goStandard {
+			realPath = ctx.c2goBase + realPath
+		}
+		if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg == nil {
+			return
+		}
+	} else {
+		pkg = ctx.pkg.Import(simplifyGopPackage(pkgPath))
+	}
 	var name string
 	if spec.Name != nil {
 		name = spec.Name.Name
diff --git a/cl/expr.go b/cl/expr.go
index 4535527c..62680981 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -29,6 +29,7 @@ import (
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
 )
 
 /*-----------------------------------------------------------------------------
@@ -62,7 +63,13 @@ const (
 	clCallWithTwoValue
 )
 
-func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
+const (
+	objNormal = iota
+	objPkgRef
+	objCPkgRef
+)
+
+func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef, kind int) {
 	fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0
 	name := ident.Name
 	if name == "_" {
@@ -70,7 +77,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
 			panic(ctx.newCodeError(ident.Pos(), "cannot use _ as value"))
 		}
 		ctx.cb.VarRef(nil)
-		return nil
+		return
 	}
 
 	scope := ctx.pkg.Types.Scope()
@@ -87,7 +94,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
 			if recv := sig.Recv(); recv != nil {
 				ctx.cb.Val(recv)
 				if compileMember(ctx, ident, name, flags) == nil { // class member object
-					return nil
+					return
 				}
 				ctx.cb.InternalStack().PopN(1)
 			}
@@ -104,14 +111,17 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
 
 	// pkgRef object
 	if (flags & clIdentSelectorExpr) != 0 {
+		if name == "C" && len(ctx.clookups) > 0 {
+			return nil, objCPkgRef
+		}
 		if pr, ok := ctx.findImport(name); ok {
-			return pr
+			return pr, objPkgRef
 		}
 	}
 
 	// object from import . "xxx"
-	if compilePkgRef(ctx, nil, ident, flags) {
-		return nil
+	if compilePkgRef(ctx, nil, ident, flags, objPkgRef) {
+		return
 	}
 
 	// universe object
@@ -134,7 +144,7 @@ find:
 	} else {
 		ctx.cb.VarRef(o, ident)
 	}
-	return nil
+	return
 }
 
 func isBuiltin(o types.Object) bool {
@@ -297,7 +307,7 @@ func compileSliceExpr(ctx *blockCtx, v *ast.SliceExpr) { // x[i:j:k]
 func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr) {
 	switch x := v.X.(type) {
 	case *ast.Ident:
-		if at := compileIdent(ctx, x, clIdentLHS|clIdentSelectorExpr); at != nil {
+		if at, kind := compileIdent(ctx, x, clIdentLHS|clIdentSelectorExpr); kind != objNormal {
 			ctx.cb.VarRef(at.Ref(v.Sel.Name))
 			return
 		}
@@ -310,8 +320,8 @@ func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr) {
 func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 	switch x := v.X.(type) {
 	case *ast.Ident:
-		if at := compileIdent(ctx, x, flags|clIdentSelectorExpr); at != nil {
-			if compilePkgRef(ctx, at, v.Sel, flags) {
+		if at, kind := compileIdent(ctx, x, flags|clIdentSelectorExpr); kind != objNormal {
+			if compilePkgRef(ctx, at, v.Sel, flags, kind) {
 				return
 			}
 			if token.IsExported(v.Sel.Name) {
@@ -338,25 +348,39 @@ func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
 	return at.TryRef(name), false
 }
 
-func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident) (o types.Object, alias bool) {
+func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) {
 	if pkg != nil {
 		return pkgRef(pkg, x.Name)
 	}
-	for _, at := range ctx.lookups {
-		if o2, alias2 := pkgRef(at, x.Name); o2 != nil {
-			if o != nil {
-				panic(ctx.newCodeErrorf(
-					x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
-					x.Name, at.Types.Path(), pkg.Types.Path()))
+	if pkgKind == objPkgRef {
+		for _, at := range ctx.lookups {
+			if o2, alias2 := pkgRef(at, x.Name); o2 != nil {
+				if o != nil {
+					panic(ctx.newCodeErrorf(
+						x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
+						x.Name, at.Types.Path(), pkg.Types.Path()))
+				}
+				pkg, o, alias = at, o2, alias2
+			}
+		}
+	} else {
+		var cpkg *cpackages.PkgRef
+		for _, at := range ctx.clookups {
+			if o2 := at.Lookup(x.Name); o2 != nil {
+				if o != nil {
+					panic(ctx.newCodeErrorf(
+						x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
+						x.Name, at.Pkg().Types.Path(), cpkg.Pkg().Types.Path()))
+				}
+				cpkg, o = at, o2
 			}
-			pkg, o, alias = at, o2, alias2
 		}
 	}
 	return
 }
 
-func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags int) bool {
-	if v, alias := lookupPkgRef(ctx, at, x); v != nil {
+func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind int) bool {
+	if v, alias := lookupPkgRef(ctx, at, x, pkgKind); v != nil {
 		cb := ctx.cb
 		if (flags & clIdentLHS) != 0 {
 			cb.VarRef(v, x)
@@ -561,13 +585,28 @@ func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
 }
 
 func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
-	if v.Kind == token.RAT {
+	cb := ctx.cb
+	switch v.Kind {
+	case token.RAT:
 		val := v.Value
 		bi, _ := new(big.Int).SetString(val[:len(val)-1], 10) // remove r suffix
-		ctx.cb.UntypedBigInt(bi, v)
-		return
+		cb.UntypedBigInt(bi, v)
+	case token.CSTRING:
+		s, err := strconv.Unquote(v.Value)
+		if err != nil {
+			log.Panicln("compileBasicLit:", err)
+		}
+		n := len(s)
+		tyInt8 := types.Typ[types.Int8]
+		typ := types.NewArray(tyInt8, int64(n+1))
+		cb.Typ(types.NewPointer(tyInt8)).Typ(types.Typ[types.UnsafePointer])
+		for i := 0; i < n; i++ {
+			cb.Val(rune(s[i]))
+		}
+		cb.Val(rune(0)).ArrayLit(typ, n+1).UnaryOp(gotoken.AND).Call(1).Call(1)
+	default:
+		cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
 	}
-	ctx.cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
 }
 
 const (
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index df83facd..277c163e 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -173,7 +173,7 @@ func toIdentType(ctx *blockCtx, ident *ast.Ident) types.Type {
 	if t, ok := v.(*types.TypeName); ok {
 		return t.Type()
 	}
-	if v, _ := lookupPkgRef(ctx, nil, ident); v != nil {
+	if v, _ := lookupPkgRef(ctx, nil, ident, objPkgRef); v != nil {
 		if t, ok := v.(*types.TypeName); ok {
 			return t.Type()
 		}
diff --git a/cl/internal/dummy/dummy.go b/cl/internal/dummy/dummy.go
new file mode 100644
index 00000000..418051ca
--- /dev/null
+++ b/cl/internal/dummy/dummy.go
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dummy
+
+import (
+	_ "github.com/goplus/libc"
+)
diff --git a/cl/internal/libc/c2go.a.pub b/cl/internal/libc/c2go.a.pub
new file mode 100644
index 00000000..f3be370b
--- /dev/null
+++ b/cl/internal/libc/c2go.a.pub
@@ -0,0 +1,1 @@
+printf
diff --git a/cmd/internal/base/gocmd.go b/cl/internal/libc/libc.go
similarity index 53%
rename from cmd/internal/base/gocmd.go
rename to cl/internal/libc/libc.go
index 1390acd1..4ff8dbc3 100644
--- a/cmd/internal/base/gocmd.go
+++ b/cl/internal/libc/libc.go
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,29 +14,28 @@
  * limitations under the License.
  */
 
-package base
+package libc
 
 import (
-	"log"
-	"os"
-	"os/exec"
+	"fmt"
+	"unsafe"
 )
 
-// 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)
+func gostring(s *int8) string {
+	n, arr := 0, (*[1 << 20]byte)(unsafe.Pointer(s))
+	for arr[n] != 0 {
+		n++
+	}
+	return string(arr[:n])
+}
+
+func Printf(format *int8, args ...interface{}) int32 {
+	goformat := gostring(format)
+	for i, arg := range args {
+		if v, ok := arg.(*int8); ok {
+			args[i] = gostring(v)
 		}
 	}
+	fmt.Printf(goformat, args...)
+	return 0
 }
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index 897c0cb0..5befb4ca 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -23,6 +23,7 @@ import (
 	"strings"
 
 	"github.com/goplus/gop/cmd/internal/bug"
+	"github.com/goplus/gop/cmd/internal/c2go"
 	"github.com/goplus/gop/cmd/internal/gopget"
 	"github.com/qiniu/x/log"
 
@@ -46,31 +47,31 @@ func mainUsage() {
 }
 
 func init() {
-	base.Usage = mainUsage
+	flag.Usage = mainUsage
 	base.Gop.Commands = []*base.Command{
 		run.Cmd,
-		gengo.Cmd,
 		install.Cmd,
 		build.Cmd,
-		mod.Cmd,
+		test.Cmd,
 		gopfmt.Cmd,
 		gopget.Cmd,
-		bug.Cmd,
+		gengo.Cmd,
+		mod.Cmd,
 		clean.Cmd,
-		env.Cmd,
-		test.Cmd,
 		// list.Cmd,
 		// deps.Cmd,
+		env.Cmd,
+		c2go.Cmd,
+		bug.Cmd,
 		version.Cmd,
 	}
 }
 
 func main() {
-	flag.Usage = base.Usage
 	flag.Parse()
 	args := flag.Args()
 	if len(args) < 1 {
-		base.Usage()
+		flag.Usage()
 	}
 	log.SetFlags(log.Ldefault &^ log.LstdFlags)
 
diff --git a/cmd/internal/base/base.go b/cmd/internal/base/base.go
index a5ebca0c..5d529625 100644
--- a/cmd/internal/base/base.go
+++ b/cmd/internal/base/base.go
@@ -98,7 +98,8 @@ func (c *Command) Runnable() bool {
 
 // Usage is the usage-reporting function, filled in by package main
 // but here for reference by other packages.
-var Usage func()
+//
+// flag.Usage func()
 
 // CmdName - "build", "install", "list", "mod tidy", etc.
 var CmdName string
diff --git a/cmd/internal/bug/bug.go b/cmd/internal/bug/bug.go
index e61d4723..02bd0786 100644
--- a/cmd/internal/bug/bug.go
+++ b/cmd/internal/bug/bug.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"io"
 	"log"
-	urlpkg "net/url"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -29,6 +28,8 @@ import (
 	"runtime"
 	"time"
 
+	urlpkg "net/url"
+
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/env"
 )
diff --git a/cmd/internal/c2go/c2go.go b/cmd/internal/c2go/c2go.go
new file mode 100644
index 00000000..e7fef502
--- /dev/null
+++ b/cmd/internal/c2go/c2go.go
@@ -0,0 +1,26 @@
+package c2go
+
+import (
+	"os"
+
+	"github.com/goplus/gop/cmd/internal/base"
+
+	c2go "github.com/goplus/c2go/cmd/c2go/impl"
+)
+
+// gop c2go
+var Cmd = &base.Command{
+	UsageLine: "gop " + c2go.ShortUsage,
+	Short:     "Run c2go (convert C to Go) tools",
+}
+
+func init() {
+	Cmd.Flag.Usage = func() {
+		Cmd.Usage(os.Stderr)
+	}
+	Cmd.Run = runCmd
+}
+
+func runCmd(cmd *base.Command, args []string) {
+	c2go.Main(&Cmd.Flag, args)
+}
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index 3d85f5c2..473188ba 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -67,9 +67,9 @@ func runCmd(cmd *base.Command, args []string) {
 	for _, proj := range projs {
 		switch v := proj.(type) {
 		case *gopprojs.DirProj:
-			err = gop.GenGo(v.Dir, nil)
+			_, _, err = gop.GenGo(v.Dir, nil)
 		case *gopprojs.PkgPathProj:
-			_, err = gop.GenGoPkgPath("", v.Path, nil, true)
+			_, _, err = gop.GenGoPkgPath("", v.Path, nil, true)
 		default:
 			log.Panicln("`gop go` doesn't support", reflect.TypeOf(v))
 		}
diff --git a/gengo.go b/gengo.go
index 6bc7ef22..8d3077f7 100644
--- a/gengo.go
+++ b/gengo.go
@@ -40,18 +40,21 @@ const (
 
 // -----------------------------------------------------------------------------
 
-func GenGo(dir string, conf *Config) (err error) {
+func GenGo(dir string, conf *Config) (string, bool, error) {
 	recursively := strings.HasSuffix(dir, "/...")
 	if recursively {
 		dir = dir[:len(dir)-4]
 	}
-	return genGoDir(dir, conf, recursively)
+	return dir, recursively, genGoDir(dir, conf, recursively)
 }
 
 func genGoDir(dir string, conf *Config, recursively bool) (err error) {
 	if recursively {
 		return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
 			if err == nil && d.IsDir() {
+				if strings.HasPrefix(d.Name(), "_") { // skip _
+					return filepath.SkipDir
+				}
 				err = genGoIn(path, conf, true)
 				if err != nil {
 					fmt.Fprintln(os.Stderr, err)
@@ -98,8 +101,8 @@ func genGoIn(dir string, conf *Config, prompt bool) (err error) {
 
 // -----------------------------------------------------------------------------
 
-func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, err error) {
-	recursively := strings.HasSuffix(pkgPath, "/...")
+func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) {
+	recursively = strings.HasSuffix(pkgPath, "/...")
 	if recursively {
 		pkgPath = pkgPath[:len(pkgPath)-4]
 	}
diff --git a/go.mod b/go.mod
index 992deeef..6dfe4272 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,9 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/gox v1.11.6
+	github.com/goplus/c2go v0.7.1
+	github.com/goplus/gox v1.11.7
+	github.com/goplus/libc v0.3.5
 	github.com/goplus/mod v0.9.3
 	github.com/qiniu/x v1.11.5
 )
diff --git a/go.sum b/go.sum
index ee355176..2bb851d5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,30 @@
-github.com/goplus/gox v1.11.6 h1:gCJmuuZz0QIW2c5zT+yCItis9npc5Ud/WQydOYU3XXw=
-github.com/goplus/gox v1.11.6/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/goplus/c2go v0.7.1 h1:F/ZofTnR9hONoIpTHXhNVNX0m1UBD/jDp5xRbROKq+s=
+github.com/goplus/c2go v0.7.1/go.mod h1:1om0h0m9wOzUqgxtLvaPOd2B1raurJmZ3ghGP576zZA=
+github.com/goplus/gox v1.11.7 h1:YbTrET69TPdZFwT7ESKeDmwq7wpXRb3DUZ474bCC9Og=
+github.com/goplus/gox v1.11.7/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM=
+github.com/goplus/libc v0.3.5 h1:dWyOxT2Smdz93daPXbgbEr6QuyFClXsu9XMWBZ2LZSM=
+github.com/goplus/libc v0.3.5/go.mod h1:Tylp4skmMW4TZsGKpajx1TARMtExegAqRX0bzAEGZW8=
 github.com/goplus/mod v0.9.3 h1:kXN2kavdUacqy39Lh/pIdT0zvBTKevb4u96ZbapPbcg=
 github.com/goplus/mod v0.9.3/go.mod h1:NHU13OjeNV3ez1f+R9FLJIlIun0KNSuZb0jnmP3N3o8=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=
+github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
diff --git a/load.go b/load.go
index 93bc61fd..f10b3fe3 100644
--- a/load.go
+++ b/load.go
@@ -21,6 +21,7 @@ import (
 	"go/token"
 	"go/types"
 	"io/fs"
+	"path/filepath"
 	"strings"
 	"syscall"
 
@@ -45,6 +46,39 @@ type Config struct {
 
 // -----------------------------------------------------------------------------
 
+func loadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err error) {
+	mod, err = gopmod.Load(dir, gop)
+	if err != nil && err != syscall.ENOENT {
+		return
+	}
+	if mod != nil {
+		err = mod.RegisterClasses()
+		if err != nil {
+			return
+		}
+		if conf.UpdateGoMod {
+			err = mod.UpdateGoMod(gop, conf.CheckModChanged)
+		}
+		return
+	}
+	return new(gopmod.Module), nil
+}
+
+func lookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) {
+	return func(pkgPath string) (pubfile string, err error) {
+		if mod.File == nil { // no go.mod/gop.mod file
+			return "", syscall.ENOENT
+		}
+		pkg, err := mod.Lookup(pkgPath)
+		if err == nil {
+			pubfile = filepath.Join(pkg.Dir, "c2go.a.pub")
+		}
+		return
+	}
+}
+
+// -----------------------------------------------------------------------------
+
 func LoadDir(dir string, conf *Config) (out, test *gox.Package, err error) {
 	if conf == nil {
 		conf = new(Config)
@@ -57,25 +91,10 @@ func LoadDir(dir string, conf *Config) (out, test *gox.Package, err error) {
 	if gop == nil {
 		gop = gopenv.Get()
 	}
-
-	mod, err := gopmod.Load(dir, gop)
-	if err != nil && err != syscall.ENOENT {
+	mod, err := loadMod(dir, gop, conf)
+	if err != nil {
 		return
 	}
-	if mod != nil {
-		err = mod.RegisterClasses()
-		if err != nil {
-			return
-		}
-		if conf.UpdateGoMod {
-			err = mod.UpdateGoMod(gop, conf.CheckModChanged)
-			if err != nil {
-				return
-			}
-		}
-	} else {
-		mod = new(gopmod.Module)
-	}
 
 	pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{
 		IsClass: mod.IsClass,
@@ -93,6 +112,7 @@ func LoadDir(dir string, conf *Config) (out, test *gox.Package, err error) {
 		Fset:        fset,
 		Importer:    conf.Importer,
 		LookupClass: mod.LookupClass,
+		LookupPub:   lookupPub(mod),
 	}
 	for name, pkg := range pkgs {
 		if strings.HasSuffix(name, "_test") {
@@ -136,6 +156,10 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 	if gop == nil {
 		gop = gopenv.Get()
 	}
+	mod, err := loadMod("", gop, conf)
+	if err != nil {
+		return
+	}
 
 	pkgs, err := parser.ParseFiles(fset, files, parser.ParseComments)
 	if err != nil {
@@ -146,9 +170,11 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 	}
 	for _, pkg := range pkgs {
 		out, err = cl.NewPackage("", pkg, &cl.Config{
-			GopRoot:  gop.Root,
-			Fset:     fset,
-			Importer: conf.Importer,
+			GopRoot:     gop.Root,
+			Fset:        fset,
+			Importer:    conf.Importer,
+			LookupClass: mod.LookupClass,
+			LookupPub:   lookupPub(mod),
 		})
 		break
 	}
diff --git a/parser/parser.go b/parser/parser.go
index 304387a6..6985133e 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1360,7 +1360,7 @@ func (p *parser) isCommand(x ast.Expr) bool {
 	}
 	switch p.tok {
 	case token.IDENT, token.RARROW,
-		token.STRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
+		token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
 		token.FUNC, token.GOTO, token.MAP, token.INTERFACE, token.CHAN, token.STRUCT:
 		return true
 	case token.SUB, token.NOT, token.AND, token.MUL, token.ARROW, token.XOR, token.ADD:
@@ -1393,7 +1393,7 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) ast.Expr {
 		}
 		return x
 
-	case token.STRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
+	case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
 		x := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
 		if debugParseOutput {
 			log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", p.tok, p.lit)
@@ -2903,7 +2903,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 		s = &ast.DeclStmt{Decl: p.parseGenDecl(p.tok, p.parseValueSpec)}
 	case
 		// tokens that may start an expression
-		token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operands
+		token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.FUNC, token.LPAREN, // operands
 		token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT, // unary operators
 		token.LBRACK, token.STRUCT, token.CHAN, token.INTERFACE: // composite types
 		allowCmd = false
diff --git a/printer/printer.go b/printer/printer.go
index 0090801c..4088e860 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -317,6 +317,9 @@ func (p *printer) writeString(pos token.Position, s string, isLit bool) {
 		// tabwriter.Escape bytes since they do not appear in legal
 		// UTF-8 sequences.
 		p.output = append(p.output, tabwriter.Escape)
+		if p.lastTok == token.CSTRING {
+			p.output = append(p.output, 'C')
+		}
 	}
 
 	if debug {
diff --git a/scanner/scanner.go b/scanner/scanner.go
index cc8d29a6..5143470b 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -830,6 +830,11 @@ scanAgain:
 			case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
 				insertSemi = true
 			}
+		} else if lit == "C" && s.ch == '"' { // C"..."
+			s.next()
+			insertSemi = true
+			tok = token.CSTRING
+			lit = s.scanString()
 		} else {
 			insertSemi = true
 			tok = token.IDENT
diff --git a/token/token.go b/token/token.go
index 7a32cff2..2e07c4fc 100644
--- a/token/token.go
+++ b/token/token.go
@@ -137,6 +137,11 @@ const (
 	VAR
 	keyword_end
 
+	additional_beg
+	TILDE // additional tokens, handled in an ad-hoc manner
+	additional_end
+
+	CSTRING  = literal_beg  // C"Hello"
 	RAT      = literal_end  // 123.5r
 	RARROW   = operator_beg // =>
 	QUESTION = operator_end // ?
@@ -148,13 +153,14 @@ var tokens = [...]string{
 	EOF:     "EOF",
 	COMMENT: "COMMENT",
 
-	IDENT:  "IDENT",
-	INT:    "INT",
-	FLOAT:  "FLOAT",
-	IMAG:   "IMAG",
-	CHAR:   "CHAR",
-	STRING: "STRING",
-	RAT:    "RAT",
+	IDENT:   "IDENT",
+	INT:     "INT",
+	FLOAT:   "FLOAT",
+	IMAG:    "IMAG",
+	CHAR:    "CHAR",
+	STRING:  "STRING",
+	CSTRING: "CSTRING",
+	RAT:     "RAT",
 
 	ADD: "+",
 	SUB: "-",
@@ -213,6 +219,7 @@ var tokens = [...]string{
 	COLON:     ":",
 	QUESTION:  "?",
 	RARROW:    "=>",
+	TILDE:     "~",
 
 	BREAK:    "break",
 	CASE:     "case",
@@ -318,14 +325,14 @@ func Lookup(ident string) Token {
 // and basic type literals; it returns false otherwise.
 //
 func (tok Token) IsLiteral() bool {
-	return literal_beg < tok && tok <= literal_end
+	return literal_beg <= tok && tok <= literal_end
 }
 
 // IsOperator returns true for tokens corresponding to operators and
 // delimiters; it returns false otherwise.
 //
 func (tok Token) IsOperator() bool {
-	return operator_beg <= tok && tok <= operator_end
+	return operator_beg <= tok && tok <= operator_end || tok == TILDE
 }
 
 // IsKeyword returns true for tokens corresponding to keywords;


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index dd9367e4..2c5b9c6f 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -17,12 +17,14 @@
 package cl
 
 import (
+	"errors"
 	"go/types"
 	"testing"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
 	"github.com/goplus/gox/packages"
 	"github.com/goplus/mod/gopmod"
 )
@@ -144,6 +146,12 @@ func TestGetGoFile(t *testing.T) {
 	}
 }
 
+func TestC2goBase(t *testing.T) {
+	if c2goBase("") != "github.com/goplus/" {
+		t.Fatal("c2goBase failed")
+	}
+}
+
 func TestErrNewType(t *testing.T) {
 	testPanic(t, `bar redeclared in this block
 	previous declaration at <TODO>
@@ -165,6 +173,32 @@ func TestErrDeclFunc(t *testing.T) {
 	})
 }
 
+func TestErrLoadImport(t *testing.T) {
+	testPanic(t, ".: unknownpkg not found or not a valid C package (c2go.a.pub file not found).\n", func() {
+		pkg := &pkgCtx{
+			nodeInterp: &nodeInterp{
+				fset: token.NewFileSet(),
+			},
+			cpkgs: cpackages.NewImporter(
+				&cpackages.Config{LookupPub: func(pkgPath string) (pubfile string, err error) {
+					return "", errors.New("not found")
+				}})}
+		ctx := &blockCtx{pkgCtx: pkg}
+		spec := &ast.ImportSpec{
+			Path: &ast.BasicLit{Kind: token.STRING, Value: `"C/unknownpkg"`},
+		}
+		loadImport(ctx, spec)
+		panic(ctx.errs[0].Error())
+	})
+}
+
+func TestErrCompileBasicLit(t *testing.T) {
+	testPanic(t, "compileBasicLit: invalid syntax\n", func() {
+		ctx := &blockCtx{cb: new(gox.CodeBuilder)}
+		compileBasicLit(ctx, &ast.BasicLit{Kind: token.CSTRING, Value: `\\x`})
+	})
+}
+
 func testPanic(t *testing.T, panicMsg string, doPanic func()) {
 	t.Run(panicMsg, func(t *testing.T) {
 		defer func() {
diff --git a/cl/compile_c2go_test.go b/cl/compile_c2go_test.go
new file mode 100644
index 00000000..c20edcc9
--- /dev/null
+++ b/cl/compile_c2go_test.go
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cl_test
+
+import (
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+func lookupPub(pkgPath string) (pubfile string, err error) {
+	relPath := strings.TrimPrefix(pkgPath, "github.com/goplus/gop/")
+	return filepath.Join(gopRootDir, relPath, "c2go.a.pub"), nil
+}
+
+func TestHelloC2go(t *testing.T) {
+	gopClTest(t, `
+import "C"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestHelloC2go2(t *testing.T) {
+	gopClTest(t, `
+import "C/github.com/goplus/gop/cl/internal/libc"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestHelloC2go3(t *testing.T) {
+	gopClTest(t, `
+import "C/libc"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestErrHelloC2go(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:7:3: confliction: printf declared both in "github.com/goplus/gop/cl/internal/libc" and "github.com/goplus/gop/cl/internal/libc"`, `
+import (
+	"C"
+	"C/libc"
+)
+
+C.printf C"Hello, world!\n"
+`)
+}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 53435033..41d14455 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -19,7 +19,6 @@ package cl_test
 import (
 	"bytes"
 	"os"
-	"path"
 	"testing"
 
 	"github.com/goplus/gop/cl"
@@ -29,15 +28,6 @@ import (
 	"github.com/goplus/mod/gopmod"
 )
 
-func newTwoFileFS(dir string, fname, data string, fname2 string, data2 string) *parsertest.MemFS {
-	return parsertest.NewMemFS(map[string][]string{
-		dir: {fname, fname2},
-	}, map[string]string{
-		path.Join(dir, fname):  data,
-		path.Join(dir, fname2): data2,
-	})
-}
-
 func lookupClass(ext string) (c *gopmod.Class, ok bool) {
 	switch ext {
 	case ".tgmx", ".tspx":
@@ -77,7 +67,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 		cl.SetDisableRecover(true)
 		defer cl.SetDisableRecover(false)
 
-		fs := newTwoFileFS("/foo", spxfile, spxcode, gmxfile, gmx)
+		fs := parsertest.NewTwoFilesFS("/foo", spxfile, spxcode, gmxfile, gmx)
 		pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
 		if err != nil {
 			scanner.PrintError(os.Stderr, err)
@@ -101,7 +91,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 }
 
 func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) {
-	fs := newTwoFileFS("/foo", spxfile, spxcode, gmxfile, gmx)
+	fs := parsertest.NewTwoFilesFS("/foo", spxfile, spxcode, gmxfile, gmx)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 86c3ab75..3be0f9dc 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -32,6 +32,10 @@ import (
 	"github.com/goplus/gox/packages"
 )
 
+const (
+	gopRootDir = ".."
+)
+
 var (
 	gblFset *token.FileSet
 	gblConf *cl.Config
@@ -46,7 +50,9 @@ func init() {
 		Fset:          gblFset,
 		Importer:      imp,
 		LookupClass:   lookupClass,
-		GopRoot:       "..",
+		LookupPub:     lookupPub,
+		C2goBase:      "github.com/goplus/gop/cl/internal",
+		GopRoot:       gopRootDir,
 		NoFileLine:    true,
 		NoAutoGenMain: true,
 	}
diff --git a/env/modtest/gop.mod b/env/modtest/gop.mod
deleted file mode 100644
index b1656c25..00000000
--- a/env/modtest/gop.mod
+++ /dev/null
@@ -1,4 +0,0 @@
-package modtest
-
-go 1.6
-gop 1.0
diff --git a/parser/_testdata/c2gohello/hello.gop b/parser/_testdata/c2gohello/hello.gop
new file mode 100644
index 00000000..fb671ed3
--- /dev/null
+++ b/parser/_testdata/c2gohello/hello.gop
@@ -0,0 +1,3 @@
+import "C"
+
+C.printf C"Hello, world!\n"
diff --git a/parser/_testdata/c2gohello/parser.expect b/parser/_testdata/c2gohello/parser.expect
new file mode 100644
index 00000000..54782abf
--- /dev/null
+++ b/parser/_testdata/c2gohello/parser.expect
@@ -0,0 +1,38 @@
+package main
+
+file hello.gop
+noEntrypoint
+ast.GenDecl:
+  Tok: import
+  Specs:
+    ast.ImportSpec:
+      Path:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "C"
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.SelectorExpr:
+                  X:
+                    ast.Ident:
+                      Name: C
+                  Sel:
+                    ast.Ident:
+                      Name: printf
+              Args:
+                ast.BasicLit:
+                  Kind: CSTRING
+                  Value: "Hello, world!\n"
diff --git a/testdata/helloc2go/hello.gop b/testdata/helloc2go/hello.gop
new file mode 100644
index 00000000..b857fa56
--- /dev/null
+++ b/testdata/helloc2go/hello.gop
@@ -0,0 +1,4 @@
+import "C"
+
+C.printf C"Hello, c2go!\n"
+C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14