Skip to content

Commit

Permalink
Merge pull request #1529 from xushiwei/typesutil
Browse files Browse the repository at this point in the history
gop/parser: ParseFSEntry/ParseEntry
  • Loading branch information
xushiwei committed Nov 8, 2023
2 parents 2742383 + cf99bf8 commit e02b880
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 30 deletions.
32 changes: 32 additions & 0 deletions parser/classfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package parser

import (
"path/filepath"
)

// ClassFileExt returns the classfile extension
func ClassFileExt(path string) (ext string) {
ext = filepath.Ext(path)
if ext == ".gox" {
if c := filepath.Ext(path[:len(path)-4]); c != "" {
return c
}
}
return
}
14 changes: 14 additions & 0 deletions parser/fsx/fsys.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ type FileSystem interface {
ReadDir(dirname string) ([]fs.DirEntry, error)
ReadFile(filename string) ([]byte, error)
Join(elem ...string) string

// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of separators, Base returns a single separator.
Base(filename string) string
}

// -----------------------------------------------------------------------------
Expand All @@ -47,6 +53,14 @@ func (p localFS) Join(elem ...string) string {
return filepath.Join(elem...)
}

// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of separators, Base returns a single separator.
func (p localFS) Base(filename string) string {
return filepath.Base(filename)
}

var Local FileSystem = localFS{}

// -----------------------------------------------------------------------------
4 changes: 4 additions & 0 deletions parser/fsx/memfs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (p *FileFS) Join(elem ...string) string {
return filepath.Join(elem...)
}

func (p *FileFS) Base(filename string) string {
return filepath.Base(filename)
}

func readSource(src interface{}) ([]byte, error) {
switch s := src.(type) {
case string:
Expand Down
8 changes: 8 additions & 0 deletions parser/fsx/memfs/memfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ func (p *FS) Join(elem ...string) string {
return path.Join(elem...)
}

// Base returns the last element of path.
// Trailing slashes are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of slashes, Base returns "/".
func (p *FS) Base(filename string) string {
return path.Base(filename)
}

// -----------------------------------------------------------------------------

// SingleFile creates a file system that only contains a single file.
Expand Down
91 changes: 66 additions & 25 deletions parser/parser_gop.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ package parser
import (
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

goast "go/ast"
Expand All @@ -51,6 +49,10 @@ func SetDebug(dbgFlags int) {
debugParseError = (dbgFlags & DbgFlagParseError) != 0
}

var (
ErrUnknownFileKind = errors.New("unknown file kind")
)

type FileSystem = fsx.FileSystem

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -95,15 +97,9 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
return ParseFSDir(fset, fsx.Local, path, conf)
}

// ClassFileExt returns the classfile extension
func ClassFileExt(path string) (ext string) {
ext = filepath.Ext(path)
if ext == ".gox" {
if c := filepath.Ext(path[:len(path)-4]); c != "" {
return c
}
}
return
// ParseEntry calls ParseFSEntry by passing a local filesystem.
func ParseEntry(fset *token.FileSet, filename string, src interface{}, conf Config) (f *ast.File, err error) {
return ParseFSEntry(fset, fsx.Local, filename, src, conf)
}

// ParseFSDir calls ParseFile for all files with names ending in ".gop" in the
Expand All @@ -118,8 +114,8 @@ func ClassFileExt(path string) (ext string) {
// If the directory couldn't be read, a nil map and the respective error are
// returned. If a parse error occurred, a non-nil but incomplete map and the
// first error encountered are returned.
func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
list, err := fs.ReadDir(path)
func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pkgs map[string]*ast.Package, first error) {
list, err := fs.ReadDir(dir)
if err != nil {
return nil, err
}
Expand All @@ -133,7 +129,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
}
fname := d.Name()
fnameRmGox := fname
ext := filepath.Ext(fname)
ext := path.Ext(fname)
var isProj, isClass, isNormalGox, useGoParser, rmGox bool
switch ext {
case ".gop":
Expand All @@ -145,19 +141,20 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
case ".gox":
isClass = true
t := fname[:len(fname)-4]
if c := filepath.Ext(t); c != "" {
ext, fnameRmGox, rmGox = c, t, true
if c := path.Ext(t); c != "" {
fnameRmGox, rmGox = t, true
} else {
isNormalGox = true
}
fallthrough
default:
if !isNormalGox {
if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass {
if rmGox {
return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname)
if !rmGox { // unknown fileKind
continue
}
continue
// not found Go+ class by ext, but is a .gox file
isClass, isNormalGox = true, true
}
}
}
Expand All @@ -166,7 +163,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
mode |= ParseGoPlusClass
}
if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || filter(d, conf.Filter)) {
filename := fs.Join(path, fname)
filename := fs.Join(dir, fname)
if useGoParser {
if filedata, err := fs.ReadFile(filename); err == nil {
if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode)); err == nil {
Expand All @@ -181,11 +178,11 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
} else {
first = err
}
} else if src, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
src.IsProj, src.IsClass = isProj, isClass
src.IsNormalGox = isNormalGox
pkg := reqPkg(pkgs, src.Name.Name)
pkg.Files[filename] = src
} else if f, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
f.IsProj, f.IsClass = isProj, isClass
f.IsNormalGox = isNormalGox
pkg := reqPkg(pkgs, f.Name.Name)
pkg.Files[filename] = f
} else if first == nil {
first = err
}
Expand All @@ -194,6 +191,50 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
return
}

// ParseFSEntry parses the source code of a single Go+ source file and returns the corresponding ast.File node.
// Compared to ParseFSFile, ParseFSEntry detects fileKind by its filename.
func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src interface{}, conf Config) (f *ast.File, err error) {
fname := fs.Base(filename)
fnameRmGox := fname
ext := path.Ext(fname)
var isProj, isClass, isNormalGox, rmGox bool
switch ext {
case ".gop", ".go":
case ".gox":
isClass = true
t := fname[:len(fname)-4]
if c := path.Ext(t); c != "" {
fnameRmGox, rmGox = t, true
} else {
isNormalGox = true
}
fallthrough
default:
if !isNormalGox {
if conf.ClassKind == nil {
conf.ClassKind = defaultClassKind
}
if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass {
if !rmGox {
return nil, ErrUnknownFileKind
}
// not found Go+ class by ext, but is a .gox file
isClass, isNormalGox = true, true
}
}
}
mode := conf.Mode
if isClass {
mode |= ParseGoPlusClass
}
f, err = ParseFSFile(fset, fs, filename, src, mode)
if err == nil {
f.IsProj, f.IsClass = isProj, isClass
f.IsNormalGox = isNormalGox
}
return
}

func filter(d fs.DirEntry, fn func(fs.FileInfo) bool) bool {
fi, err := d.Info()
return err != nil || fn(fi)
Expand Down
60 changes: 55 additions & 5 deletions parser/parserdir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package parser

import (
"bytes"
"io/ioutil"
"os"
"path"
"reflect"
Expand Down Expand Up @@ -113,7 +112,7 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
t.Fatal("ParseDir failed:", err, reflect.TypeOf(err), len(pkgs))
}
for _, pkg := range pkgs {
b, err := ioutil.ReadFile(pkgDir + "/parser.expect")
b, err := os.ReadFile(pkgDir + "/parser.expect")
if err != nil {
t.Fatal("Parsing", pkgDir, "-", err)
}
Expand Down Expand Up @@ -144,6 +143,57 @@ func TestParseGoFiles(t *testing.T) {
}
}

func TestParseEntry(t *testing.T) {
fset := token.NewFileSet()
src, err := os.ReadFile("./_testdata/functype/functype.go")
if err != nil {
t.Fatal("os.ReadFile:", err)
}
conf := Config{}
t.Run(".gop file", func(t *testing.T) {
f, err := ParseEntry(fset, "./functype.gop", src, conf)
if err != nil {
t.Fatal("ParseEntry failed:", err)
}
if f.IsClass || f.IsProj || f.IsNormalGox {
t.Fatal("ParseEntry functype.gop:", f.IsClass, f.IsProj, f.IsNormalGox)
}
})
t.Run(".gox file", func(t *testing.T) {
f, err := ParseEntry(fset, "./functype.gox", src, conf)
if err != nil {
t.Fatal("ParseEntry failed:", err)
}
if !f.IsClass || f.IsProj || !f.IsNormalGox {
t.Fatal("ParseEntry functype.gox:", f.IsClass, f.IsProj, f.IsNormalGox)
}
})
t.Run(".foo.gox file", func(t *testing.T) {
f, err := ParseEntry(fset, "./functype.foo.gox", src, conf)
if err != nil {
t.Fatal("ParseEntry failed:", err)
}
if !f.IsClass || f.IsProj || !f.IsNormalGox {
t.Fatal("ParseEntry functype.foo.gox:", f.IsClass, f.IsProj, f.IsNormalGox)
}
})
t.Run(".foo file", func(t *testing.T) {
_, err := ParseEntry(fset, "./functype.foo", src, conf)
if err != ErrUnknownFileKind {
t.Fatal("ParseEntry failed:", err)
}
})
t.Run(".spx file", func(t *testing.T) {
f, err := ParseEntry(fset, "./main.spx", src, conf)
if err != nil {
t.Fatal("ParseEntry failed:", err)
}
if !f.IsClass || !f.IsProj || f.IsNormalGox {
t.Fatal("ParseEntry main.spx:", f.IsClass, f.IsProj, f.IsNormalGox)
}
})
}

func TestGopAutoGen(t *testing.T) {
fset := token.NewFileSet()
fs := memfs.SingleFile("/foo", "gop_autogen.go", ``)
Expand Down Expand Up @@ -190,8 +240,8 @@ func TestErrParse(t *testing.T) {

fs = memfs.SingleFile("/foo", "test.abc.gox", `package foo`)
_, err = ParseFSDir(fset, fs, "/foo", Config{})
if err == nil {
t.Fatal("ParseFSDir test.gop: no error?")
if err != nil {
t.Fatal("ParseFSDir failed:", err)
}
}

Expand All @@ -201,7 +251,7 @@ func testFromDir(t *testing.T, sel, relDir string) {
t.Fatal("Getwd failed:", err)
}
dir = path.Join(dir, relDir)
fis, err := ioutil.ReadDir(dir)
fis, err := os.ReadDir(dir)
if err != nil {
t.Fatal("ReadDir failed:", err)
}
Expand Down

0 comments on commit e02b880

Please sign in to comment.