diff --git a/Makefile b/Makefile index 793bc12..d461b66 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ lint: test: generate lint go test -race -cover ./... + go run ./cmd/tengo -resolve ./testdata/cli/test.tengo fmt: go fmt ./... diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index afddd68..a8e1498 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -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 diff --git a/compiler.go b/compiler.go index 757a810..cb1c8f3 100644 --- a/compiler.go +++ b/compiler.go @@ -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 } diff --git a/script.go b/script.go index 906771d..4f9608c 100644 --- a/script.go +++ b/script.go @@ -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 } diff --git a/testdata/cli/one.tengo b/testdata/cli/one.tengo new file mode 100644 index 0000000..30ee366 --- /dev/null +++ b/testdata/cli/one.tengo @@ -0,0 +1,7 @@ + +export { + fn: func(a) { + two := import("two/two") + return two.fn(a, "one") + } +} \ No newline at end of file diff --git a/testdata/cli/test.tengo b/testdata/cli/test.tengo new file mode 100755 index 0000000..41c221a --- /dev/null +++ b/testdata/cli/test.tengo @@ -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) diff --git a/testdata/cli/three.tengo b/testdata/cli/three.tengo new file mode 100644 index 0000000..5f21a80 --- /dev/null +++ b/testdata/cli/three.tengo @@ -0,0 +1,6 @@ +export { + fn: func(a, b, c) { + four := import("./two/four/four.tengo") + return four.fn(a, b, c, "three") + } +} \ No newline at end of file diff --git a/testdata/cli/two/five/five.tengo b/testdata/cli/two/five/five.tengo new file mode 100644 index 0000000..dd5f7bd --- /dev/null +++ b/testdata/cli/two/five/five.tengo @@ -0,0 +1,7 @@ +export { + fn: func(...args) { + text := import("text") + args = append(args, "five") + return text.join(args, " ") + } +} \ No newline at end of file diff --git a/testdata/cli/two/four/four.tengo b/testdata/cli/two/four/four.tengo new file mode 100644 index 0000000..59c79bc --- /dev/null +++ b/testdata/cli/two/four/four.tengo @@ -0,0 +1,6 @@ +export { + fn: func(a, b, c, d) { + five := import("../five/five") + return five.fn(a, b, c, d, "four") + } +} \ No newline at end of file diff --git a/testdata/cli/two/two.tengo b/testdata/cli/two/two.tengo new file mode 100644 index 0000000..7fc9800 --- /dev/null +++ b/testdata/cli/two/two.tengo @@ -0,0 +1,6 @@ +export { + fn: func(a, b) { + three := import("../three") + return three.fn(a, b, "two") + } +} \ No newline at end of file