Repository: goplus/gop
Pull request #1434 (2023-09-30 11:11:52)

gop doc; gop/cl/outline


Patch:
diff --git a/ast/ast.go b/ast/ast.go
index b14227e4..12845859 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1122,10 +1122,11 @@ type FileType = int16
 // and Comment comments directly associated with nodes, the remaining comments
 // are "free-floating" (see also issues #18593, #20744).
 type File struct {
-	Doc          *CommentGroup   // associated documentation; or nil
-	Package      token.Pos       // position of "package" keyword
-	Name         *Ident          // package name
-	Decls        []Decl          // top-level declarations; or nil
+	Doc     *CommentGroup // associated documentation; or nil
+	Package token.Pos     // position of "package" keyword
+	Name    *Ident        // package name
+	Decls   []Decl        // top-level declarations; or nil
+
 	Scope        *Scope          // package scope (this file only)
 	Imports      []*ImportSpec   // imports in this file
 	Unresolved   []*Ident        // unresolved identifiers in this file
diff --git a/ast/fromgo/gopast.go b/ast/fromgo/gopast.go
index f15b3b3e..02220fb2 100644
--- a/ast/fromgo/gopast.go
+++ b/ast/fromgo/gopast.go
@@ -245,6 +245,7 @@ func gopFieldList(v *ast.FieldList) *gopast.FieldList {
 
 func gopFuncDecl(v *ast.FuncDecl) *gopast.FuncDecl {
 	return &gopast.FuncDecl{
+		Doc:  v.Doc,
 		Recv: gopFieldList(v.Recv),
 		Name: gopIdent(v.Name),
 		Type: gopFuncType(v.Type),
@@ -294,6 +295,7 @@ func gopGenDecl(v *ast.GenDecl) *gopast.GenDecl {
 		}
 	}
 	return &gopast.GenDecl{
+		Doc:    v.Doc,
 		TokPos: v.TokPos,
 		Tok:    goptoken.Token(v.Tok),
 		Lparen: v.Lparen,
@@ -338,6 +340,7 @@ func ASTFile(f *ast.File, mode int) *gopast.File {
 		log.Panicln("ASTFile: doesn't support keeping cgo now")
 	}
 	return &gopast.File{
+		Doc:     f.Doc,
 		Package: f.Package,
 		Name:    gopIdent(f.Name),
 		Decls:   gopDecls(f.Decls),
diff --git a/cl/compile.go b/cl/compile.go
index 0249bd0f..e5262e67 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -428,8 +428,10 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		preloadGopFile(p, ctx, fpath, f, conf)
 	}
 
+	gofiles := make([]*ast.File, 0, len(pkg.GoFiles))
 	for fpath, gof := range pkg.GoFiles {
 		f := fromgo.ASTFile(gof, 0)
+		gofiles = append(gofiles, f)
 		ctx := &blockCtx{
 			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
 			imports: make(map[string]*gox.PkgRef),
@@ -442,7 +444,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		*ast.File
 		path string
 	}
-	var sfiles []*File
+	sfiles := make([]*File, 0, len(files))
 	for fpath, f := range files {
 		sfiles = append(sfiles, &File{f, fpath})
 	}
@@ -462,6 +464,11 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 			loadFile(ctx, f.File)
 		}
 	}
+	if conf.Outline {
+		for _, f := range gofiles {
+			loadFile(ctx, f)
+		}
+	}
 	for _, ld := range ctx.tylds {
 		ld.load()
 	}
diff --git a/cl/internal/dummy/dummy.go b/cl/internal/dummy/dummy.go
deleted file mode 100644
index 418051ca..00000000
--- a/cl/internal/dummy/dummy.go
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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/outline/outline.go b/cl/outline/outline.go
new file mode 100644
index 00000000..a4c88067
--- /dev/null
+++ b/cl/outline/outline.go
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2023 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 outline
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/mod/modfile"
+)
+
+// -----------------------------------------------------------------------------
+
+type Project = modfile.Project
+
+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
+
+	// 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 *Project, ok bool)
+
+	// An Importer resolves import paths to Packages.
+	Importer types.Importer
+}
+
+type Package struct {
+	objs []types.Object
+	Pkg  *types.Package
+}
+
+// NewPackage creates a Go/Go+ outline package.
+func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err error) {
+	ret, err := cl.NewPackage(pkgPath, pkg, &cl.Config{
+		Fset:           conf.Fset,
+		WorkingDir:     conf.WorkingDir,
+		C2goBase:       conf.C2goBase,
+		LookupPub:      conf.LookupPub,
+		LookupClass:    conf.LookupClass,
+		Importer:       conf.Importer,
+		NoFileLine:     true,
+		NoAutoGenMain:  true,
+		NoSkipConstant: true,
+		Outline:        true,
+	})
+	if err != nil {
+		return
+	}
+	return From(ret), nil
+}
+
+func From(pkg *gox.Package) Package {
+	scope := pkg.Types.Scope()
+	names := scope.Names()
+	objs := make([]types.Object, len(names))
+	for i, name := range names {
+		objs[i] = scope.Lookup(name)
+	}
+	return Package{objs, pkg.Types}
+}
+
+func (p Package) Valid() bool {
+	return p.Pkg != nil
+}
+
+// -----------------------------------------------------------------------------
+
+type All struct {
+	Consts []Const
+	Vars   []Var
+	Funcs  []Func
+	Types  []*TypeName
+
+	named map[*types.TypeName]*TypeName
+}
+
+func (p *All) initNamed(objs []types.Object, all bool) {
+	for _, o := range objs {
+		if t, ok := o.(*types.TypeName); ok {
+			named := &TypeName{TypeName: t}
+			p.named[t] = named
+			if all || o.Exported() {
+				p.Types = append(p.Types, named)
+			}
+		}
+	}
+}
+
+func (p Package) Outline(withUnexported ...bool) (ret *All) {
+	ret = &All{
+		named: make(map[*types.TypeName]*TypeName),
+	}
+	all := (withUnexported != nil && withUnexported[0])
+	ret.initNamed(p.objs, all)
+	for _, o := range p.objs {
+		if _, ok := o.(*types.TypeName); ok || !(all || o.Exported()) {
+			continue
+		}
+		switch v := o.(type) {
+		case *gox.Func:
+			sig := v.Type().(*types.Signature)
+			kind, t := sigKind(p.Pkg, sig)
+			switch kind {
+			case sigNormal:
+				ret.Funcs = append(ret.Funcs, Func{v})
+			case sigCreator:
+				if named, ok := ret.named[t.Obj()]; ok {
+					named.Creators = append(named.Creators, Func{v})
+				}
+			}
+		case *types.Const:
+			if t := checkLocal(p.Pkg, v.Type()); t != nil {
+				if named, ok := ret.named[t.Obj()]; ok {
+					named.Consts = append(named.Consts, Const{v})
+				}
+			} else {
+				ret.Consts = append(ret.Consts, Const{v})
+			}
+		case *types.Var:
+			ret.Vars = append(ret.Vars, Var{v})
+		}
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+type sigKindType int
+
+const (
+	sigNormal sigKindType = iota
+	sigCreator
+)
+
+func sigKind(pkg *types.Package, sig *types.Signature) (kind sigKindType, t *types.Named) {
+	rets := sig.Results()
+	if rets.Len() > 0 {
+		if t := checkLocal(pkg, rets.At(0).Type()); t != nil {
+			return sigCreator, t
+		}
+	}
+	return sigNormal, nil
+}
+
+func checkLocal(pkg *types.Package, first types.Type) *types.Named {
+	if t, ok := indirect(first).(*types.Named); ok {
+		if t.Obj().Pkg() == pkg {
+			return t
+		}
+	}
+	return nil
+}
+
+func indirect(typ types.Type) types.Type {
+	if t, ok := typ.(*types.Pointer); ok {
+		return t.Elem()
+	}
+	return typ
+}
+
+// -----------------------------------------------------------------------------
+
+type Const struct {
+	*types.Const
+}
+
+func (p Const) Obj() types.Object {
+	return p.Const
+}
+
+func (p Const) Doc() string {
+	return ""
+}
+
+type Var struct {
+	*types.Var
+}
+
+func (p Var) Obj() types.Object {
+	return p.Var
+}
+
+func (p Var) Doc() string {
+	return ""
+}
+
+type Func struct {
+	*gox.Func
+}
+
+func (p Func) Obj() types.Object {
+	return &p.Func.Func
+}
+
+func (p Func) Doc() string {
+	return p.Comments().Text()
+}
+
+// -----------------------------------------------------------------------------
+
+type TypeName struct {
+	*types.TypeName
+	Creators []Func
+	Consts   []Const
+}
+
+func (p *TypeName) Type() Type {
+	return Type{p.TypeName.Type()}
+}
+
+// -----------------------------------------------------------------------------
+
+type Type struct {
+	types.Type
+}
+
+func (p Type) CheckNamed() (_ Named, ok bool) {
+	ret, ok := p.Type.(*types.Named)
+	if ok {
+		return Named{ret}, true
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+type Named struct {
+	*types.Named
+}
+
+func (p Named) Methods() []Func {
+	n := p.NumMethods()
+	ret := make([]Func, n)
+	for i := 0; i < n; i++ {
+		fn := p.Method(i)
+		ret[i] = Func{gox.MethodFrom(fn)}
+	}
+	return ret
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index 68e6bcdc..9e106151 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -29,6 +29,7 @@ import (
 	"github.com/goplus/gop/cmd/internal/build"
 	"github.com/goplus/gop/cmd/internal/c2go"
 	"github.com/goplus/gop/cmd/internal/clean"
+	"github.com/goplus/gop/cmd/internal/doc"
 	"github.com/goplus/gop/cmd/internal/env"
 	"github.com/goplus/gop/cmd/internal/gengo"
 	"github.com/goplus/gop/cmd/internal/gopfmt"
@@ -58,6 +59,7 @@ func init() {
 		gopget.Cmd,
 		gengo.Cmd,
 		mod.Cmd,
+		doc.Cmd,
 		clean.Cmd,
 		// list.Cmd,
 		// deps.Cmd,
diff --git a/cmd/internal/doc/doc.go b/cmd/internal/doc/doc.go
new file mode 100644
index 00000000..92960ab6
--- /dev/null
+++ b/cmd/internal/doc/doc.go
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2023 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 doc
+
+import (
+	"fmt"
+	"go/types"
+	"log"
+	"os"
+	"reflect"
+	"strconv"
+	"strings"
+	"syscall"
+
+	"github.com/goplus/gop"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/cl/outline"
+	"github.com/goplus/gop/cmd/internal/base"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/gop/x/gopprojs"
+	"github.com/goplus/gox"
+)
+
+// -----------------------------------------------------------------------------
+
+// gop doc
+var Cmd = &base.Command{
+	UsageLine: "gop doc [-u -all -debug] [pkgPath]",
+	Short:     "Show documentation for package or symbol",
+}
+
+var (
+	flag    = &Cmd.Flag
+	withDoc = flag.Bool("all", false, "Show all the documentation for the package.")
+	debug   = flag.Bool("debug", false, "Print debug information.")
+	unexp   = flag.Bool("u", false, "Show documentation for unexported as well as exported symbols, methods, and fields.")
+)
+
+func init() {
+	Cmd.Run = runCmd
+}
+
+func runCmd(cmd *base.Command, args []string) {
+	err := flag.Parse(args)
+	if err != nil {
+		log.Fatalln("parse input arguments failed:", err)
+	}
+
+	pattern := flag.Args()
+	if len(pattern) == 0 {
+		pattern = []string{"."}
+	}
+
+	proj, _, err := gopprojs.ParseOne(pattern...)
+	if err != nil {
+		log.Panicln("gopprojs.ParseOne:", err)
+	}
+
+	if *debug {
+		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
+		cl.SetDebug(cl.DbgFlagAll)
+		cl.SetDisableRecover(true)
+	}
+
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	outlinePkg(proj, conf)
+}
+
+func outlinePkg(proj gopprojs.Proj, conf *gop.Config) {
+	var obj string
+	var out outline.Package
+	var err error
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		obj = v.Dir
+		out, err = gop.Outline(obj, conf)
+	case *gopprojs.PkgPathProj:
+		obj = v.Path
+		out, err = gop.OutlinePkgPath("", obj, conf, true)
+	default:
+		log.Panicln("`gop doc` doesn't support", reflect.TypeOf(v))
+	}
+	if err == syscall.ENOENT {
+		fmt.Fprintf(os.Stderr, "gop doc %v: not Go/Go+ files found\n", obj)
+	} else if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+	} else {
+		outlineDoc(out.Pkg, out.Outline(*unexp), *unexp, *withDoc)
+	}
+}
+
+const (
+	indent = "    "
+	ln     = "\n"
+)
+
+func outlineDoc(pkg *types.Package, out *outline.All, all, withDoc bool) {
+	fmt.Printf("package %s // import %s\n\n", pkg.Name(), strconv.Quote(pkg.Path()))
+	if withDoc && len(out.Consts) > 0 {
+		fmt.Print("CONSTANTS\n\n")
+	}
+	for _, o := range out.Consts {
+		printObject(pkg, o, withDoc)
+	}
+	if withDoc && len(out.Vars) > 0 {
+		fmt.Print("VARIABLES\n\n")
+	}
+	for _, o := range out.Vars {
+		printObject(pkg, o, withDoc)
+	}
+	if withDoc && len(out.Funcs) > 0 {
+		fmt.Print("FUNCTIONS\n\n")
+	}
+	for _, fn := range out.Funcs {
+		printObject(pkg, fn, withDoc)
+	}
+	if withDoc && len(out.Types) > 0 {
+		fmt.Print("TYPES\n\n")
+	}
+	for _, t := range out.Types {
+		fmt.Println(typeString(pkg, t.TypeName, withDoc))
+		for _, o := range t.Consts {
+			fmt.Print(indent, constShortString(o.Const), ln)
+		}
+		if withDoc {
+			fmt.Println()
+		}
+		for _, fn := range t.Creators {
+			if withDoc {
+				printObject(pkg, fn, true)
+			} else {
+				fmt.Print(indent, objectString(pkg, fn.Obj()), ln)
+			}
+		}
+		typ := t.Type()
+		if named, ok := typ.CheckNamed(); ok {
+			for _, fn := range named.Methods() {
+				if o := fn.Obj(); all || o.Exported() {
+					if withDoc {
+						fmt.Print(objectString(pkg, o), ln)
+						printDoc(fn)
+					} else {
+						fmt.Print(indent, objectString(pkg, o), ln)
+					}
+				}
+			}
+		}
+	}
+}
+
+type object interface {
+	Obj() types.Object
+	Doc() string
+}
+
+func printObject(pkg *types.Package, o object, withDoc bool) {
+	fmt.Print(objectString(pkg, o.Obj()), ln)
+	if withDoc {
+		printDoc(o)
+	}
+}
+
+func printDoc(o object) {
+	if doc := o.Doc(); doc != "" {
+		fmt.Print(indent, strings.ReplaceAll(doc, "\n", "\n"+indent), ln)
+	} else {
+		fmt.Println()
+	}
+}
+
+func typeString(pkg *types.Package, t *types.TypeName, withDoc bool) string {
+	alias := ""
+	if t.IsAlias() {
+		alias = " ="
+	}
+	underlying := typeShortString(pkg, t.Type().Underlying())
+	return fmt.Sprint("type ", t.Name(), alias, " ", underlying)
+}
+
+func objectString(pkg *types.Package, obj types.Object) string {
+	return types.ObjectString(obj, qualifier(pkg))
+}
+
+func typeShortString(pkg *types.Package, typ types.Type) string {
+	switch t := typ.(type) {
+	default:
+		return types.TypeString(t, qualifier(pkg))
+	}
+}
+
+func constShortString(obj *types.Const) string {
+	return "const " + obj.Name()
+}
+
+func qualifier(pkg *types.Package) types.Qualifier {
+	return func(other *types.Package) string {
+		if pkg == other {
+			return "" // same package; unqualified
+		}
+		return other.Name()
+	}
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cmd/internal/env/env.go b/cmd/internal/env/env.go
index bb77bb58..0fcb86a7 100644
--- a/cmd/internal/env/env.go
+++ b/cmd/internal/env/env.go
@@ -34,7 +34,7 @@ import (
 // Cmd - gop env
 var Cmd = &base.Command{
 	UsageLine: "gop env [-json] [var ...]",
-	Short:     "Prints Go+ environment information.",
+	Short:     "Prints Go+ environment information",
 }
 
 var (
diff --git a/cmd/internal/version/ver.go b/cmd/internal/version/ver.go
index 64fc51ac..57ac1468 100644
--- a/cmd/internal/version/ver.go
+++ b/cmd/internal/version/ver.go
@@ -29,7 +29,7 @@ import (
 // Cmd - gop version
 var Cmd = &base.Command{
 	UsageLine: "gop version [-v]",
-	Short:     "Version prints the build information for Gop executables",
+	Short:     "Print Go+ version",
 }
 
 var (
diff --git a/cmd/internal/watch/watch.go b/cmd/internal/watch/watch.go
index 8b095162..8451dd17 100644
--- a/cmd/internal/watch/watch.go
+++ b/cmd/internal/watch/watch.go
@@ -47,7 +47,9 @@ func init() {
 
 func runCmd(cmd *base.Command, args []string) {
 	err := flag.Parse(args)
-	check(err)
+	if err != nil {
+		log.Fatalln("parse input arguments failed:", err)
+	}
 
 	if *debug {
 		fsnotify.SetDebug(fsnotify.DbgFlagAll)
@@ -75,10 +77,4 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 }
 
-func check(err error) {
-	if err != nil {
-		log.Fatalln(err)
-	}
-}
-
 // -----------------------------------------------------------------------------
diff --git a/go.mod b/go.mod
index 670fe995..9d468a1d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,8 @@ go 1.17
 
 require (
 	github.com/fsnotify/fsnotify v1.6.0
-	github.com/goplus/c2go v0.7.13
-	github.com/goplus/gox v1.11.38
-	github.com/goplus/libc v0.3.13
+	github.com/goplus/c2go v0.7.15
+	github.com/goplus/gox v1.12.0
 	github.com/goplus/mod v0.11.5
 	github.com/qiniu/x v1.13.0
 )
@@ -15,7 +14,6 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a // indirect
 	golang.org/x/mod v0.12.0 // indirect
 	golang.org/x/sys v0.12.0 // indirect
 	golang.org/x/tools v0.13.0 // indirect
diff --git a/go.sum b/go.sum
index 4d519eb9..940dd210 100644
--- a/go.sum
+++ b/go.sum
@@ -4,16 +4,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.10/go.mod h1:ryOieVZqh0jmlPp45AMh9/sXIreTnyjDREP0EMX+pF0=
-github.com/goplus/c2go v0.7.13 h1:188dkHVVS3Qn/cwFtFwNgvlgjY2tBrZRjugv8icolt4=
-github.com/goplus/c2go v0.7.13/go.mod h1:s5NULWzUKi3erd3l4ahvpq+3qDGOvMjGb00a5o3h6zk=
-github.com/goplus/gox v1.11.21/go.mod h1:wRCRSNukie4cDqADF4w0Btc2Gk6V3p3V6hI5+rsVqa8=
-github.com/goplus/gox v1.11.32/go.mod h1:hdKq5ghywtKWnBJNQNVBkPITmWCqCFRwwd2LTYTfg2U=
-github.com/goplus/gox v1.11.38 h1:znqKvaIOx/zpVBPpF5NNFO+DuQ/8UpvO0hIqFb45SlY=
-github.com/goplus/gox v1.11.38/go.mod h1:QQfPGKVmtAzat6fDYE4V4PFo/v95zvrwB9M60tbqdMM=
-github.com/goplus/libc v0.3.13 h1:2yHG8ghezBTK3Da7Fw1lNiitmt+Va+RzwKJ5GuqypCQ=
-github.com/goplus/libc v0.3.13/go.mod h1:xqG4/g3ilKBE/UDn5vkaE7RRQPQPyspj7ecuMuvlQJ8=
-github.com/goplus/mod v0.9.12/go.mod h1:YoPIowz71rnLLROA4YG0AC8bzDtPRyMaQwgTRLr8ri4=
+github.com/goplus/c2go v0.7.15 h1:qdBPro9UWgoLca+mZ4lUV9ZdzSafy1kJFs/CtuVDhE8=
+github.com/goplus/c2go v0.7.15/go.mod h1:SKSY0OvbO9JBkHRjyiJz3vz0rHpwPETjNj6AQ/rQZOg=
+github.com/goplus/gox v1.12.0 h1:VkQrIhVaKTYARQUUN+Q0+k8DtOXQqOvC6zaZP8wyG5E=
+github.com/goplus/gox v1.12.0/go.mod h1:Ek4bXv4xzfBiFuHC6yfkzHXrhUHXfrM9QyXtMusdkGo=
 github.com/goplus/mod v0.11.5 h1:3zcWlkLq/adU+y8GynZRMjSE50DfbnILdkRnToDaq24=
 github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -22,72 +16,56 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
 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.9/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
 github.com/qiniu/x v1.13.0 h1:sJYzcai3asOpp4Ue+P06ibWMTHp8YWF19x1oTeG+Vnw=
 github.com/qiniu/x v1.13.0/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
 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=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 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=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
diff --git a/load.go b/load.go
index 3394b74b..744a3e93 100644
--- a/load.go
+++ b/load.go
@@ -138,13 +138,13 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	for name, pkg := range pkgs {
 		if strings.HasSuffix(name, "_test") {
 			if pkgTest != nil {
-				return nil, nil, errMultiTestPackges
+				return nil, nil, ErrMultiTestPackges
 			}
 			pkgTest = pkg
 			continue
 		}
 		if out != nil {
-			return nil, nil, errMultiPackges
+			return nil, nil, ErrMultiPackges
 		}
 		if len(pkg.Files) == 0 { // no Go+ source files
 			break
@@ -189,7 +189,7 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 		return
 	}
 	if len(pkgs) != 1 {
-		err = errors.NewWith(errMultiPackges, `len(pkgs) != 1`, -1, "!=", len(pkgs), 1)
+		err = errors.NewWith(ErrMultiPackges, `len(pkgs) != 1`, -1, "!=", len(pkgs), 1)
 		return
 	}
 	for _, pkg := range pkgs {
@@ -215,8 +215,8 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 // -----------------------------------------------------------------------------
 
 var (
-	errMultiPackges     = errors.New("multiple packages")
-	errMultiTestPackges = errors.New("multiple test packages")
+	ErrMultiPackges     = errors.New("multiple packages")
+	ErrMultiTestPackges = errors.New("multiple test packages")
 )
 
 // -----------------------------------------------------------------------------
diff --git a/outline.go b/outline.go
new file mode 100644
index 00000000..a872b47a
--- /dev/null
+++ b/outline.go
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2023 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 gop
+
+import (
+	"fmt"
+	"go/token"
+	"io/fs"
+	"path/filepath"
+	"strings"
+	"syscall"
+
+	"github.com/goplus/gop/cl/outline"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/mod/gopmod"
+)
+
+// -----------------------------------------------------------------------------
+
+func Outline(dir string, conf *Config) (out outline.Package, err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
+	fset := conf.Fset
+	if fset == nil {
+		fset = token.NewFileSet()
+	}
+	gop := conf.Gop
+	if gop == nil {
+		gop = gopenv.Get()
+	}
+
+	if dir, err = filepath.Abs(dir); err != nil {
+		return
+	}
+	mod, err := LoadMod(dir, gop, conf)
+	if err != nil {
+		return
+	}
+
+	filterConf := conf.Filter
+	filter := func(fi fs.FileInfo) bool {
+		if filterConf != nil && !filterConf(fi) {
+			return false
+		}
+		fname := fi.Name()
+		if pos := strings.Index(fname, "."); pos > 0 {
+			fname = fname[:pos]
+		}
+		return !strings.HasSuffix(fname, "_test")
+	}
+	pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{
+		ClassKind: mod.ClassKind,
+		Filter:    filter,
+		Mode:      parser.ParseComments,
+	})
+	if err != nil {
+		return
+	}
+	if len(pkgs) == 0 {
+		err = syscall.ENOENT
+		return
+	}
+
+	imp := conf.Importer
+	if imp == nil {
+		imp = NewImporter(mod, gop, fset)
+	}
+
+	for name, pkg := range pkgs {
+		if out.Valid() {
+			err = fmt.Errorf("%w: %s, %s", ErrMultiPackges, name, out.Pkg.Name())
+			return
+		}
+		if len(pkg.Files)+len(pkg.GoFiles) == 0 { // no Go/Go+ source files
+			break
+		}
+		pkgPath, _ := filepath.Rel(mod.Root(), dir)
+		out, err = outline.NewPackage(filepath.ToSlash(pkgPath), pkg, &outline.Config{
+			Fset:        fset,
+			WorkingDir:  dir,
+			Importer:    imp,
+			LookupClass: mod.LookupClass,
+			LookupPub:   lookupPub(mod),
+		})
+		if err != nil {
+			return
+		}
+	}
+	if !out.Valid() {
+		err = syscall.ENOENT
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+func OutlinePkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (out outline.Package, err error) {
+	mod, err := gopmod.Load(workDir, 0)
+	if NotFound(err) && allowExtern {
+		remotePkgPathDo(pkgPath, func(dir string) {
+			out, err = Outline(dir, conf)
+		}, func(e error) {
+			err = e
+		})
+		return
+	} else if err != nil {
+		return
+	}
+
+	pkg, err := mod.Lookup(pkgPath)
+	if err != nil {
+		return
+	}
+	return Outline(pkg.Dir, conf)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/parser/interface.go b/parser/interface.go
index ef62b016..c88c4a43 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -19,6 +19,8 @@
 package parser
 
 import (
+	goparser "go/parser"
+
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 )
@@ -30,21 +32,21 @@ type Mode uint
 
 const (
 	// PackageClauseOnly - stop parsing after package clause
-	PackageClauseOnly Mode = 1 << iota
+	PackageClauseOnly = Mode(goparser.PackageClauseOnly)
 	// ImportsOnly - stop parsing after import declarations
-	ImportsOnly
+	ImportsOnly = Mode(goparser.ImportsOnly)
 	// ParseComments - parse comments and add them to AST
-	ParseComments
+	ParseComments = Mode(goparser.ParseComments)
 	// Trace - print a trace of parsed productions
-	Trace
+	Trace = Mode(goparser.Trace)
 	// DeclarationErrors - report declaration errors
-	DeclarationErrors
+	DeclarationErrors = Mode(goparser.DeclarationErrors)
 	// AllErrors - report all errors (not just the first 10 on different lines)
-	AllErrors
+	AllErrors = Mode(goparser.AllErrors)
 	// ParseGoAsGoPlus - parse Go files by gop/parser
-	ParseGoAsGoPlus
+	ParseGoAsGoPlus Mode = 1 << 16
 	// ParserGoPlusClass - parse Go+ classfile by gop/parser
-	ParseGoPlusClass
+	ParseGoPlusClass Mode = 1 << 17
 )
 
 // ParseFile parses the source code of a single Go source file and returns


Test patch:
diff --git a/cl/compile_test.go b/cl/compile_test.go
index ddd940e5..b2ab4068 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -78,9 +78,11 @@ func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected strin
 	gopClTestFS(t, conf, fs, pkgname, expected)
 }
 
-func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string) {
+func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) {
+	conf := *gblConf
+	conf.Outline = (outline != nil && outline[0])
 	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
-	gopClTestFS(t, gblConf, fs, pkgname, expected)
+	gopClTestFS(t, &conf, fs, pkgname, expected)
 }
 
 func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, expected string) {
@@ -280,7 +282,7 @@ var c foo
 var d int = c.v
 var e = foo3{}
 var x string = c.Str()
-`)
+`, true)
 	gopMixedClTest(t, "main", `package main
 type Point struct {
 	X int
@@ -299,7 +301,7 @@ type T struct {
 func main() {
 	fmt.Println(&T{}, &Point{10, 20})
 }
-`)
+`, false)
 }
 
 func Test_RangeExpressionIf_Issue1243(t *testing.T) {