Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gop/parser: ParseFSEntry/ParseEntry #1529

Merged
merged 5 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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