summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/d5/tengo/compiler/compiler_module.go
blob: 8f63abb35e978b2a0bb6c19fb687d83c60c7cb87 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package compiler

import (
	"io/ioutil"
	"strings"

	"github.com/d5/tengo/compiler/ast"
	"github.com/d5/tengo/compiler/parser"
	"github.com/d5/tengo/objects"
)

func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) {
	compiledModule, exists := c.loadCompiledModule(expr.ModuleName)
	if exists {
		return compiledModule, nil
	}

	moduleName := expr.ModuleName

	// read module source from loader
	var moduleSrc []byte
	if c.moduleLoader == nil {
		// default loader: read from local file
		if !strings.HasSuffix(moduleName, ".tengo") {
			moduleName += ".tengo"
		}

		if err := c.checkCyclicImports(expr, moduleName); err != nil {
			return nil, err
		}

		var err error
		moduleSrc, err = ioutil.ReadFile(moduleName)
		if err != nil {
			return nil, c.errorf(expr, "module file read error: %s", err.Error())
		}
	} else {
		if err := c.checkCyclicImports(expr, moduleName); err != nil {
			return nil, err
		}

		var err error
		moduleSrc, err = c.moduleLoader(moduleName)
		if err != nil {
			return nil, err
		}
	}

	compiledModule, err := c.doCompileModule(moduleName, moduleSrc)
	if err != nil {
		return nil, err
	}

	c.storeCompiledModule(moduleName, compiledModule)

	return compiledModule, nil
}

func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error {
	if c.moduleName == moduleName {
		return c.errorf(node, "cyclic module import: %s", moduleName)
	} else if c.parent != nil {
		return c.parent.checkCyclicImports(node, moduleName)
	}

	return nil
}

func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) {
	modFile := c.file.Set().AddFile(moduleName, -1, len(src))
	p := parser.NewParser(modFile, src, nil)
	file, err := p.ParseFile()
	if err != nil {
		return nil, err
	}

	symbolTable := NewSymbolTable()

	// inherit builtin functions
	for idx, fn := range objects.Builtins {
		s, _, ok := c.symbolTable.Resolve(fn.Name)
		if ok && s.Scope == ScopeBuiltin {
			symbolTable.DefineBuiltin(idx, fn.Name)
		}
	}

	// no global scope for the module
	symbolTable = symbolTable.Fork(false)

	// compile module
	moduleCompiler := c.fork(modFile, moduleName, symbolTable)
	if err := moduleCompiler.Compile(file); err != nil {
		return nil, err
	}

	// add OpReturn (== export undefined) if export is missing
	if !moduleCompiler.lastInstructionIs(OpReturnValue) {
		moduleCompiler.emit(nil, OpReturn)
	}

	compiledFunc := moduleCompiler.Bytecode().MainFunction
	compiledFunc.NumLocals = symbolTable.MaxSymbols()

	return compiledFunc, nil
}

func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) {
	if c.parent != nil {
		return c.parent.loadCompiledModule(moduleName)
	}

	mod, ok = c.compiledModules[moduleName]

	return
}

func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) {
	if c.parent != nil {
		c.parent.storeCompiledModule(moduleName, module)
	}

	c.compiledModules[moduleName] = module
}