enable relative imports (#285)

* enable relative imports

* update per 1st review

* remove symlink stuffs

* fix cli run in make file

* make resolving import path explicit

* fix importDir
This commit is contained in:
Ozan Hacıbekiroğlu 2020-05-22 23:57:38 +03:00 committed by GitHub
parent e059953c35
commit 4ed75764ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 20 deletions

View file

@ -6,6 +6,7 @@ lint:
test: generate lint
go test -race -cover ./...
go run ./cmd/tengo -resolve ./testdata/cli/test.tengo
fmt:
go fmt ./...

View file

@ -25,6 +25,7 @@ var (
compileOutput string
showHelp bool
showVersion bool
resolvePath bool // TODO Remove this flag at version 3
version = "dev"
)
@ -32,6 +33,8 @@ func init() {
flag.BoolVar(&showHelp, "help", false, "Show help")
flag.StringVar(&compileOutput, "o", "", "Compile output file")
flag.BoolVar(&showVersion, "version", false, "Show version")
flag.BoolVar(&resolvePath, "resolve", false,
"Resolve relative import paths")
flag.Parse()
}
@ -55,10 +58,20 @@ func main() {
inputData, err := ioutil.ReadFile(inputFile)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr,
"Error reading input file: %s", err.Error())
"Error reading input file: %s\n", err.Error())
os.Exit(1)
}
inputFile, err = filepath.Abs(inputFile)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error file path: %s\n", err)
os.Exit(1)
}
if len(inputData) > 1 && string(inputData[:2]) == "#!" {
copy(inputData, "//")
}
if compileOutput != "" {
err := CompileOnly(modules, inputData, inputFile,
compileOutput)
@ -67,9 +80,6 @@ func main() {
os.Exit(1)
}
} else if filepath.Ext(inputFile) == sourceFileExt {
if len(inputData) > 1 && string(inputData[:2]) == "#!" {
copy(inputData, "//")
}
err := CompileAndRun(modules, inputData, inputFile)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
@ -90,7 +100,7 @@ func CompileOnly(
data []byte,
inputFile, outputFile string,
) (err error) {
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
bytecode, err := compileSrc(modules, data, inputFile)
if err != nil {
return
}
@ -125,7 +135,7 @@ func CompileAndRun(
data []byte,
inputFile string,
) (err error) {
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
bytecode, err := compileSrc(modules, data, inputFile)
if err != nil {
return
}
@ -215,10 +225,10 @@ func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) {
func compileSrc(
modules *tengo.ModuleMap,
src []byte,
filename string,
inputFile string,
) (*tengo.Bytecode, error) {
fileSet := parser.NewFileSet()
srcFile := fileSet.AddFile(filename, -1, len(src))
srcFile := fileSet.AddFile(filepath.Base(inputFile), -1, len(src))
p := parser.NewParser(srcFile, src, nil)
file, err := p.ParseFile()
@ -228,6 +238,9 @@ func compileSrc(
c := tengo.NewCompiler(srcFile, nil, nil, modules, nil)
c.EnableFileImport(true)
if resolvePath {
c.SetImportDir(filepath.Dir(inputFile))
}
if err := c.Compile(file); err != nil {
return nil, err

View file

@ -44,6 +44,7 @@ type Compiler struct {
file *parser.SourceFile
parent *Compiler
modulePath string
importDir string
constants []Object
symbolTable *SymbolTable
scopes []compilationScope
@ -520,7 +521,7 @@ func (c *Compiler) Compile(node parser.Node) error {
switch v := v.(type) {
case []byte: // module written in Tengo
compiled, err := c.compileModule(node,
node.ModuleName, node.ModuleName, v)
node.ModuleName, v, false)
if err != nil {
return err
}
@ -537,24 +538,20 @@ func (c *Compiler) Compile(node parser.Node) error {
moduleName += ".tengo"
}
modulePath, err := filepath.Abs(moduleName)
modulePath, err := filepath.Abs(
filepath.Join(c.importDir, moduleName))
if err != nil {
return c.errorf(node, "module file path error: %s",
err.Error())
}
if err := c.checkCyclicImports(node, modulePath); err != nil {
return err
}
moduleSrc, err := ioutil.ReadFile(moduleName)
moduleSrc, err := ioutil.ReadFile(modulePath)
if err != nil {
return c.errorf(node, "module file read error: %s",
err.Error())
}
compiled, err := c.compileModule(node,
moduleName, modulePath, moduleSrc)
compiled, err := c.compileModule(node, modulePath, moduleSrc, true)
if err != nil {
return err
}
@ -634,6 +631,11 @@ func (c *Compiler) EnableFileImport(enable bool) {
c.allowFileImport = enable
}
// SetImportDir sets the initial import directory path for file imports.
func (c *Compiler) SetImportDir(dir string) {
c.importDir = dir
}
func (c *Compiler) compileAssign(
node parser.Node,
lhs, rhs []parser.Expr,
@ -957,8 +959,9 @@ func (c *Compiler) checkCyclicImports(
func (c *Compiler) compileModule(
node parser.Node,
moduleName, modulePath string,
modulePath string,
src []byte,
isFile bool,
) (*CompiledFunction, error) {
if err := c.checkCyclicImports(node, modulePath); err != nil {
return nil, err
@ -969,7 +972,7 @@ func (c *Compiler) compileModule(
return compiledModule, nil
}
modFile := c.file.Set().AddFile(moduleName, -1, len(src))
modFile := c.file.Set().AddFile(modulePath, -1, len(src))
p := parser.NewParser(modFile, src, nil)
file, err := p.ParseFile()
if err != nil {
@ -986,7 +989,7 @@ func (c *Compiler) compileModule(
symbolTable = symbolTable.Fork(false)
// compile module
moduleCompiler := c.fork(modFile, modulePath, symbolTable)
moduleCompiler := c.fork(modFile, modulePath, symbolTable, isFile)
if err := moduleCompiler.Compile(file); err != nil {
return nil, err
}
@ -1084,11 +1087,16 @@ func (c *Compiler) fork(
file *parser.SourceFile,
modulePath string,
symbolTable *SymbolTable,
isFile bool,
) *Compiler {
child := NewCompiler(file, symbolTable, nil, c.modules, c.trace)
child.modulePath = modulePath // module file path
child.parent = c // parent to set to current compiler
child.allowFileImport = c.allowFileImport
child.importDir = c.importDir
if isFile && c.importDir != "" {
child.importDir = filepath.Dir(modulePath)
}
return child
}

View file

@ -3,6 +3,7 @@ package tengo
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/d5/tengo/v2/parser"
@ -16,6 +17,7 @@ type Script struct {
maxAllocs int64
maxConstObjects int
enableFileImport bool
importDir string
}
// NewScript creates a Script instance with an input script.
@ -56,6 +58,16 @@ func (s *Script) SetImports(modules *ModuleMap) {
s.modules = modules
}
// SetImportDir sets the initial import directory for script files.
func (s *Script) SetImportDir(dir string) error {
dir, err := filepath.Abs(dir)
if err != nil {
return err
}
s.importDir = dir
return nil
}
// SetMaxAllocs sets the maximum number of objects allocations during the run
// time. Compiled script will return ErrObjectAllocLimit error if it
// exceeds this limit.
@ -93,6 +105,7 @@ func (s *Script) Compile() (*Compiled, error) {
c := NewCompiler(srcFile, symbolTable, nil, s.modules, nil)
c.EnableFileImport(s.enableFileImport)
c.SetImportDir(s.importDir)
if err := c.Compile(file); err != nil {
return nil, err
}

7
testdata/cli/one.tengo vendored Normal file
View file

@ -0,0 +1,7 @@
export {
fn: func(a) {
two := import("two/two")
return two.fn(a, "one")
}
}

15
testdata/cli/test.tengo vendored Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env tengo
os := import("os")
one := import("one")
fmt := import("fmt")
text := import("text")
expected := ["test", "one", "two", "three", "four", "five"]
expected = text.join(expected, " ")
if v := one.fn("test"); v != expected {
fmt.printf("relative import test error:\n\texpected: %v\n\tgot : %v\n",
expected, v)
os.exit(1)
}
args := text.join(os.args(), " ")
fmt.println("ok\t", args)

6
testdata/cli/three.tengo vendored Normal file
View file

@ -0,0 +1,6 @@
export {
fn: func(a, b, c) {
four := import("./two/four/four.tengo")
return four.fn(a, b, c, "three")
}
}

7
testdata/cli/two/five/five.tengo vendored Normal file
View file

@ -0,0 +1,7 @@
export {
fn: func(...args) {
text := import("text")
args = append(args, "five")
return text.join(args, " ")
}
}

6
testdata/cli/two/four/four.tengo vendored Normal file
View file

@ -0,0 +1,6 @@
export {
fn: func(a, b, c, d) {
five := import("../five/five")
return five.fn(a, b, c, d, "four")
}
}

6
testdata/cli/two/two.tengo vendored Normal file
View file

@ -0,0 +1,6 @@
export {
fn: func(a, b) {
three := import("../three")
return three.fn(a, b, "two")
}
}