Repository: goplus/gop
Pull request #1204 (2022-05-19 18:45:53)

support C


Patch:
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/cl/c.go b/cl/c.go
new file mode 100644
index 00000000..4c520634
--- /dev/null
+++ b/cl/c.go
@@ -0,0 +1,69 @@
+/*
+ * 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 (
+	"log"
+	"strings"
+
+	"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) *gox.PkgRef {
+	cpkg, err := ctx.cpkgs.Import(realPath)
+	if err != nil {
+		log.Panicln("loadC2goPkg failed:", err)
+	}
+	ctx.clookups = append(ctx.clookups, cpkg)
+	return cpkg.Pkg()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/compile.go b/cl/compile.go
index 58fee873..7b673060 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,16 @@ 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
+		}
+		pkg = loadC2goPkg(ctx, realPath)
+	} 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/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/cl/internal/libc/libc.go b/cl/internal/libc/libc.go
new file mode 100644
index 00000000..4ff8dbc3
--- /dev/null
+++ b/cl/internal/libc/libc.go
@@ -0,0 +1,41 @@
+/*
+ * 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 libc
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+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/go.mod b/go.mod
index 992deeef..c88435c0 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.11.6
+	github.com/goplus/gox v1.11.7
 	github.com/goplus/mod v0.9.3
 	github.com/qiniu/x v1.11.5
 )
diff --git a/go.sum b/go.sum
index ee355176..a71f418e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-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/goplus/gox v1.11.7 h1:YbTrET69TPdZFwT7ESKeDmwq7wpXRb3DUZ474bCC9Og=
+github.com/goplus/gox v1.11.7/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM=
 github.com/goplus/mod v0.9.3 h1:kXN2kavdUacqy39Lh/pIdT0zvBTKevb4u96ZbapPbcg=
 github.com/goplus/mod v0.9.3/go.mod h1:NHU13OjeNV3ez1f+R9FLJIlIun0KNSuZb0jnmP3N3o8=
 github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U=
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..f8ab325b 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,24 @@ func TestErrDeclFunc(t *testing.T) {
 	})
 }
 
+func TestErrLoadC2goPkg(t *testing.T) {
+	testPanic(t, "loadC2goPkg failed: not found\n", func() {
+		pkg := &pkgCtx{cpkgs: cpackages.NewImporter(
+			&cpackages.Config{LookupPub: func(pkgPath string) (pubfile string, err error) {
+				return "", errors.New("not found")
+			}})}
+		ctx := &blockCtx{pkgCtx: pkg}
+		loadC2goPkg(ctx, "notfound")
+	})
+}
+
+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_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/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"