Browse Source

Added module and import support (#6)

* Added module and import support (eval)

* Only export capitalised bindings

* Fixed bug with modules referencing constants and globals in the module by refactoring the VM's State management
pull/9/head v1.3.0
James Mills 3 years ago
committed by GitHub
parent
commit
c09707eec4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      ast/ast.go
  2. 2
      code/code.go
  3. 10
      compiler/compiler.go
  4. 12
      compiler/compiler_test.go
  5. 14
      compiler/symbol_table.go
  6. 58
      eval/eval.go
  7. 70
      eval/eval_test.go
  8. 19
      object/environment.go
  9. 29
      object/module.go
  10. 3
      object/object.go
  11. 18
      parser/parser.go
  12. 20
      parser/parser_test.go
  13. 40
      repl/repl.go
  14. 3
      testdata/mod.monkey
  15. 3
      token/token.go
  16. 53
      utils/utils.go
  17. 137
      vm/vm.go
  18. 33
      vm/vm_test.go

24
ast/ast.go

@ -320,6 +320,30 @@ func (we *WhileExpression) String() string {
return out.String()
}
// ImportExpression represents an `import` expression and holds the name
// of the module being imported.
type ImportExpression struct {
Token token.Token // The 'import' token
Name Expression
}
func (ie *ImportExpression) expressionNode() {}
// TokenLiteral prints the literal value of the token associated with this node
func (ie *ImportExpression) TokenLiteral() string { return ie.Token.Literal }
// String returns a stringified version of the AST for debugging
func (ie *ImportExpression) String() string {
var out bytes.Buffer
out.WriteString(ie.TokenLiteral())
out.WriteString("(")
out.WriteString(fmt.Sprintf("\"%s\"", ie.Name))
out.WriteString(")")
return out.String()
}
// FunctionLiteral represents a literal functions and holds the function's
// formal parameters and boy of the function as a block statement
type FunctionLiteral struct {

2
code/code.go

@ -79,6 +79,7 @@ const (
LoadLocal
BindLocal
LoadFree
LoadModule
SetSelf
LoadTrue
LoadFalse
@ -128,6 +129,7 @@ var definitions = map[Opcode]*Definition{
LoadLocal: {"LoadLocal", []int{1}},
BindLocal: {"BindLocal", []int{1}},
LoadFree: {"LoadFree", []int{1}},
LoadModule: {"LoadModule", []int{}},
SetSelf: {"SetSelf", []int{1}},
LoadTrue: {"LoadTrue", []int{}},
LoadFalse: {"LoadFalse", []int{}},

10
compiler/compiler.go

@ -386,6 +386,16 @@ func (c *Compiler) Compile(node ast.Node) error {
afterConsequencePos := c.emit(code.LoadNull)
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
case *ast.ImportExpression:
c.l++
err := c.Compile(node.Name)
if err != nil {
return err
}
c.l--
c.emit(code.LoadModule)
case *ast.PrefixExpression:
c.l++
err := c.Compile(node.Right)

12
compiler/compiler_test.go

@ -1114,3 +1114,15 @@ func TestClosures(t *testing.T) {
runCompilerTests2(t, tests)
}
func TestImportExpressions(t *testing.T) {
tests := []compilerTestCase2{
{
input: `import("foo")`,
constants: []interface{}{"foo"},
instructions: "0000 LoadConstant 0\n0003 LoadModule\n0004 Pop\n",
},
}
runCompilerTests2(t, tests)
}

14
compiler/symbol_table.go

@ -18,7 +18,7 @@ type Symbol struct {
type SymbolTable struct {
Outer *SymbolTable
store map[string]Symbol
Store map[string]Symbol
numDefinitions int
FreeSymbols []Symbol
@ -27,7 +27,7 @@ type SymbolTable struct {
func NewSymbolTable() *SymbolTable {
return &SymbolTable{
Outer: nil,
store: make(map[string]Symbol),
Store: make(map[string]Symbol),
FreeSymbols: []Symbol{},
}
}
@ -35,7 +35,7 @@ func NewSymbolTable() *SymbolTable {
func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable {
return &SymbolTable{
Outer: outer,
store: make(map[string]Symbol),
Store: make(map[string]Symbol),
FreeSymbols: []Symbol{},
}
}
@ -46,7 +46,7 @@ func (s *SymbolTable) DefineFree(original Symbol) Symbol {
symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1}
symbol.Scope = FreeScope
s.store[original.Name] = symbol
s.Store[original.Name] = symbol
return symbol
}
@ -58,19 +58,19 @@ func (s *SymbolTable) Define(name string) Symbol {
symbol.Scope = LocalScope
}
s.store[name] = symbol
s.Store[name] = symbol
s.numDefinitions++
return symbol
}
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
s.store[name] = symbol
s.Store[name] = symbol
return symbol
}
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
obj, ok := s.store[name]
obj, ok := s.Store[name]
if !ok && s.Outer != nil {
obj, ok = s.Outer.Resolve(name)
if !ok {

58
eval/eval.go

@ -6,11 +6,15 @@ package eval
import (
"fmt"
"io/ioutil"
"strings"
"github.com/prologic/monkey-lang/ast"
"github.com/prologic/monkey-lang/builtins"
"github.com/prologic/monkey-lang/lexer"
"github.com/prologic/monkey-lang/object"
"github.com/prologic/monkey-lang/parser"
"github.com/prologic/monkey-lang/utils"
)
var (
@ -35,6 +39,32 @@ func newError(format string, a ...interface{}) *object.Error {
return &object.Error{Message: fmt.Sprintf(format, a...)}
}
// EvalModule evaluates the named module and returns a *object.Module object
func EvalModule(name string) object.Object {
filename := utils.FindModule(name)
if filename == "" {
return newError("ImportError: no module named '%s'", name)
}
b, err := ioutil.ReadFile(filename)
if err != nil {
return newError("IOError: error reading module '%s': %s", name, err)
}
l := lexer.New(string(b))
p := parser.New(l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {
return newError("ParseError: %s", p.Errors())
}
env := object.NewEnvironment()
Eval(module, env)
return env.ExportedHash()
}
// Eval evaluates the node and returns an object
func Eval(node ast.Node, env *object.Environment) object.Object {
switch node := node.(type) {
@ -90,6 +120,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return evalIfExpression(node, env)
case *ast.WhileExpression:
return evalWhileExpression(node, env)
case *ast.ImportExpression:
return evalImportExpression(node, env)
case *ast.Identifier:
return evalIdentifier(node, env)
@ -496,9 +528,24 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec
if result != nil {
return result
} else {
return NULL
}
return NULL
}
func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object {
name := Eval(ie.Name, env)
if isError(name) {
return name
}
if s, ok := name.(*object.String); ok {
attrs := EvalModule(s.Value)
if isError(attrs) {
return attrs
}
return &object.Module{Name: s.Value, Attrs: attrs}
}
return newError("ImportError: invalid import path '%s'", name)
}
func isTruthy(obj object.Object) bool {
@ -613,6 +660,8 @@ func evalIndexExpression(left, index object.Object) object.Object {
return evalArrayIndexExpression(left, index)
case left.Type() == object.HASH:
return evalHashIndexExpression(left, index)
case left.Type() == object.MODULE:
return evalModuleIndexExpression(left, index)
default:
return newError("index operator not supported: %s", left.Type())
}
@ -634,6 +683,11 @@ func evalHashIndexExpression(hash, index object.Object) object.Object {
return pair.Value
}
func evalModuleIndexExpression(module, index object.Object) object.Object {
moduleObject := module.(*object.Module)
return evalHashIndexExpression(moduleObject.Attrs, index)
}
func evalArrayIndexExpression(array, index object.Object) object.Object {
arrayObject := array.(*object.Array)
idx := index.(*object.Integer).Value

70
eval/eval_test.go

@ -6,11 +6,49 @@ import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/prologic/monkey-lang/lexer"
"github.com/prologic/monkey-lang/object"
"github.com/prologic/monkey-lang/parser"
"github.com/prologic/monkey-lang/utils"
)
func assertEvaluated(t *testing.T, expected interface{}, actual object.Object) {
t.Helper()
assert := assert.New(t)
switch expected.(type) {
case nil:
if _, ok := actual.(*object.Null); ok {
assert.True(ok)
} else {
assert.Equal(expected, actual)
}
case int:
if i, ok := actual.(*object.Integer); ok {
assert.Equal(int64(expected.(int)), i.Value)
} else {
assert.Equal(expected, actual)
}
case error:
if e, ok := actual.(*object.Integer); ok {
assert.Equal(expected.(error).Error(), e.Value)
} else {
assert.Equal(expected, actual)
}
case string:
if s, ok := actual.(*object.String); ok {
assert.Equal(expected.(string), s.Value)
} else {
assert.Equal(expected, actual)
}
default:
t.Fatalf("unsupported type for expected got=%T", expected)
}
}
func TestEvalExpressions(t *testing.T) {
tests := []struct {
input string
@ -847,6 +885,38 @@ func TestHashIndexExpressions(t *testing.T) {
}
}
func TestImportExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{`mod := import("../testdata/mod"); mod.A`, 5},
{`mod := import("../testdata/mod"); mod.Sum(2, 3)`, 5},
{`mod := import("../testdata/mod"); mod.a`, nil},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
assertEvaluated(t, tt.expected, evaluated)
}
}
func TestImportSearchPaths(t *testing.T) {
utils.AddPath("../testdata")
tests := []struct {
input string
expected interface{}
}{
{`mod := import("mod"); mod.A`, 5},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
assertEvaluated(t, tt.expected, evaluated)
}
}
func TestExamples(t *testing.T) {
matches, err := filepath.Glob("./examples/*.monkey")
if err != nil {

19
object/environment.go

@ -1,5 +1,9 @@
package object
import (
"unicode"
)
// NewEnvironment constructs a new Environment object to hold bindings
// of identifiers to their names
func NewEnvironment() *Environment {
@ -13,6 +17,21 @@ type Environment struct {
parent *Environment
}
// ExportedHash returns a new Hash with the names and values of every publically
// exported binding in the environment. That is every binding that starts with a
// capital letter. This is used by the module import system to wrap up the
// evaulated module into an object.
func (e *Environment) ExportedHash() *Hash {
pairs := make(map[HashKey]HashPair)
for k, v := range e.store {
if unicode.IsUpper(rune(k[0])) {
s := &String{Value: k}
pairs[s.HashKey()] = HashPair{Key: s, Value: v}
}
}
return &Hash{Pairs: pairs}
}
// Clone returns a new Environment with the parent set to the current
// environment (enclosing environment)
func (e *Environment) Clone() *Environment {

29
object/module.go

@ -0,0 +1,29 @@
package object
import (
"fmt"
)
// Module is the module type used to represent a collection of variabels.
type Module struct {
Name string
Attrs Object
}
func (m *Module) Bool() bool {
return true
}
func (m *Module) Compare(other Object) int {
return 1
}
func (m *Module) String() string {
return m.Inspect()
}
// Type returns the type of the object
func (m *Module) Type() Type { return MODULE }
// Inspect returns a stringified version of the object for debugging
func (m *Module) Inspect() string { return fmt.Sprintf("<module '%s'>", m.Name) }

3
object/object.go

@ -44,6 +44,9 @@ const (
// HASH is the Hash object type
HASH = "hash"
// MODULE is the Module object type
MODULE = "module"
)
// Comparable is the interface for comparing two Object and their underlying

18
parser/parser.go

@ -91,6 +91,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
p.registerPrefix(token.IF, p.parseIfExpression)
p.registerPrefix(token.WHILE, p.parseWhileExpression)
p.registerPrefix(token.IMPORT, p.parseImportExpression)
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
p.infixParseFns = make(map[token.Type]infixParseFn)
@ -413,6 +414,23 @@ func (p *Parser) parseWhileExpression() ast.Expression {
return expression
}
func (p *Parser) parseImportExpression() ast.Expression {
expression := &ast.ImportExpression{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
p.nextToken()
expression.Name = p.parseExpression(LOWEST)
if !p.expectPeek(token.RPAREN) {
return nil
}
return expression
}
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
block := &ast.BlockStatement{Token: p.curToken}
block.Statements = []ast.Statement{}

20
parser/parser_test.go

@ -1302,3 +1302,23 @@ func TestParsingHashLiteralsWithExpressions(t *testing.T) {
testFunc(value)
}
}
func TestParsingImportExpressions(t *testing.T) {
assert := assert.New(t)
tests := []struct {
input string
expected string
}{
{`import("mod")`, `import("mod")`},
}
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
program := p.ParseProgram()
checkParserErrors(t, p)
assert.Equal(tt.expected, program.String())
}
}

40
repl/repl.go

@ -11,7 +11,6 @@ import (
"log"
"os"
"github.com/prologic/monkey-lang/builtins"
"github.com/prologic/monkey-lang/compiler"
"github.com/prologic/monkey-lang/eval"
"github.com/prologic/monkey-lang/lexer"
@ -44,25 +43,6 @@ type Options struct {
Interactive bool
}
type VMState struct {
constants []object.Object
globals []object.Object
symbols *compiler.SymbolTable
}
func NewVMState() *VMState {
symbolTable := compiler.NewSymbolTable()
for i, builtin := range builtins.BuiltinsIndex {
symbolTable.DefineBuiltin(i, builtin.Name)
}
return &VMState{
constants: []object.Object{},
globals: make([]object.Object, vm.MaxGlobals),
symbols: symbolTable,
}
}
type REPL struct {
user string
args []string
@ -103,14 +83,14 @@ func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
// Exec parses, compiles and executes the program given by f and returns
// the resulting virtual machine, any errors are printed to stderr
func (r *REPL) Exec(f io.Reader) (state *VMState) {
func (r *REPL) Exec(f io.Reader) (state *vm.VMState) {
b, err := ioutil.ReadAll(f)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
return
}
state = NewVMState()
state = vm.NewVMState()
l := lexer.New(string(b))
p := parser.New(l)
@ -121,7 +101,7 @@ func (r *REPL) Exec(f io.Reader) (state *VMState) {
return
}
c := compiler.NewWithState(state.symbols, state.constants)
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err = c.Compile(program)
if err != nil {
@ -130,9 +110,9 @@ func (r *REPL) Exec(f io.Reader) (state *VMState) {
}
code := c.Bytecode()
state.constants = code.Constants
state.Constants = code.Constants
machine := vm.NewWithGlobalsStore(code, state.globals)
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {
@ -180,11 +160,11 @@ func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environmen
}
// StartExecLoop starts the REPL in a continious exec loop
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *vm.VMState) {
scanner := bufio.NewScanner(in)
if state == nil {
state = NewVMState()
state = vm.NewVMState()
}
for {
@ -205,7 +185,7 @@ func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
continue
}
c := compiler.NewWithState(state.symbols, state.constants)
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err := c.Compile(program)
if err != nil {
@ -214,9 +194,9 @@ func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
}
code := c.Bytecode()
state.constants = code.Constants
state.Constants = code.Constants
machine := vm.NewWithGlobalsStore(code, state.globals)
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {

3
testdata/mod.monkey

@ -0,0 +1,3 @@
a := 1
A := 5
Sum := fn(a, b) { return a + b }

3
token/token.go

@ -136,6 +136,8 @@ const (
RETURN = "RETURN"
// WHILE the `while` keyword (while)
WHILE = "WHILE"
// IMPORT the `import` keyword (import)
IMPORT = "IMPORT"
)
var keywords = map[string]Type{
@ -147,6 +149,7 @@ var keywords = map[string]Type{
"else": ELSE,
"return": RETURN,
"while": WHILE,
"import": IMPORT,
}
// Type represents the type of a token

53
utils/utils.go

@ -0,0 +1,53 @@
package utils
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
var SearchPaths []string
func init() {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("error getting cwd: %s", err)
}
if e := os.Getenv("MONKEYPATH"); e != "" {
tokens := strings.Split(e, ":")
for _, token := range tokens {
AddPath(token) // ignore errors
}
} else {
SearchPaths = append(SearchPaths, cwd)
}
}
func AddPath(path string) error {
path = os.ExpandEnv(filepath.Clean(path))
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
SearchPaths = append(SearchPaths, absPath)
return nil
}
func Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func FindModule(name string) string {
basename := fmt.Sprintf("%s.monkey", name)
for _, p := range SearchPaths {
filename := filepath.Join(p, basename)
if Exists(filename) {
return filename
}
}
return ""
}

137
vm/vm.go

@ -6,13 +6,18 @@ package vm
import (
"fmt"
"io/ioutil"
"log"
"strings"
"unicode"
"github.com/prologic/monkey-lang/builtins"
"github.com/prologic/monkey-lang/code"
"github.com/prologic/monkey-lang/compiler"
"github.com/prologic/monkey-lang/lexer"
"github.com/prologic/monkey-lang/object"
"github.com/prologic/monkey-lang/parser"
"github.com/prologic/monkey-lang/utils"
)
const (
@ -48,18 +53,91 @@ func isTruthy(obj object.Object) bool {
}
}
// ExecModule compiles the named module and returns a *object.Module object
func ExecModule(name string, state *VMState) (object.Object, error) {
filename := utils.FindModule(name)
if filename == "" {
return nil, fmt.Errorf("ImportError: no module named '%s'", name)
}
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err)
}
l := lexer.New(string(b))
p := parser.New(l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {
return nil, fmt.Errorf("ParseError: %s", p.Errors())
}
c := compiler.NewWithState(state.Symbols, state.Constants)
err = c.Compile(module)
if err != nil {
return nil, fmt.Errorf("CompileError: %s", err)
}
code := c.Bytecode()
state.Constants = code.Constants
machine := NewWithState(code, state)
err = machine.Run()
if err != nil {
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
}
return state.ExportedHash(), nil
}
type VMState struct {
Constants []object.Object
Globals []object.Object
Symbols *compiler.SymbolTable
}
func NewVMState() *VMState {
symbolTable := compiler.NewSymbolTable()
for i, builtin := range builtins.BuiltinsIndex {
symbolTable.DefineBuiltin(i, builtin.Name)
}
return &VMState{
Constants: []object.Object{},
Globals: make([]object.Object, MaxGlobals),
Symbols: symbolTable,
}
}
// ExportedHash returns a new Hash with the names and values of every publically
// exported binding in the vm state. That is every binding that starts with a
// capital letter. This is used by the module import system to wrap up the
// compiled and evaulated module into an object.
func (s *VMState) ExportedHash() *object.Hash {
pairs := make(map[object.HashKey]object.HashPair)
for name, symbol := range s.Symbols.Store {
if unicode.IsUpper(rune(name[0])) {
if symbol.Scope == compiler.GlobalScope {
obj := s.Globals[symbol.Index]
s := &object.String{Value: name}
pairs[s.HashKey()] = object.HashPair{Key: s, Value: obj}
}
}
}
return &object.Hash{Pairs: pairs}
}
type VM struct {
Debug bool
constants []object.Object
state *VMState
frames []*Frame
framesIndex int
stack []object.Object
sp int // Always points to the next value. Top of stack is stack[sp-1]
globals []object.Object
}
func (vm *VM) currentFrame() *Frame {
@ -84,20 +162,21 @@ func New(bytecode *compiler.Bytecode) *VM {
frames := make([]*Frame, MaxFrames)
frames[0] = mainFrame
state := NewVMState()
state.Constants = bytecode.Constants
return &VM{
constants: bytecode.Constants,
state: state,
frames: frames,
framesIndex: 1,
stack: make([]object.Object, StackSize),
sp: 0,
globals: make([]object.Object, MaxGlobals),
}
}
func NewWithGlobalsStore(bytecode *compiler.Bytecode, globals []object.Object) *VM {
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0)
@ -106,15 +185,13 @@ func NewWithGlobalsStore(bytecode *compiler.Bytecode, globals []object.Object) *
frames[0] = mainFrame
return &VM{
constants: bytecode.Constants,
state: state,
frames: frames,
framesIndex: 1,
stack: make([]object.Object, StackSize),
sp: 0,
globals: globals,
}
}
@ -353,6 +430,8 @@ func (vm *VM) executeGetItem(left, index object.Object) error {
return vm.executeArrayGetItem(left, index)
case left.Type() == object.HASH:
return vm.executeHashGetItem(left, index)
case left.Type() == object.MODULE:
return vm.executeHashGetItem(left.(*object.Module).Attrs, index)
default:
return fmt.Errorf(
"index operator not supported: left=%s index=%s",
@ -527,7 +606,7 @@ func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
}
func (vm *VM) pushClosure(constIndex, numFree int) error {
constant := vm.constants[constIndex]
constant := vm.state.Constants[constIndex]
function, ok := constant.(*object.CompiledFunction)
if !ok {
return fmt.Errorf("not a function: %+v", constant)
@ -543,6 +622,24 @@ func (vm *VM) pushClosure(constIndex, numFree int) error {
return vm.push(closure)
}
func (vm *VM) loadModule(name object.Object) error {
s, ok := name.(*object.String)
if !ok {
return fmt.Errorf(
"TypeError: import() expected argument #1 to be `str` got `%s`",
name.Type(),
)
}
attrs, err := ExecModule(s.Value, vm.state)
if err != nil {
return err
}
module := &object.Module{Name: s.Value, Attrs: attrs}
return vm.push(module)
}
func (vm *VM) LastPopped() object.Object {
return vm.stack[vm.sp]
}
@ -592,7 +689,7 @@ func (vm *VM) Run() error {
constIndex := code.ReadUint16(ins[ip+1:])
vm.currentFrame().ip += 2
err := vm.push(vm.constants[constIndex])
err := vm.push(vm.state.Constants[constIndex])
if err != nil {
return err
}
@ -600,7 +697,7 @@ func (vm *VM) Run() error {
case code.AssignGlobal:
globalIndex := code.ReadUint16(ins[ip+1:])
vm.currentFrame().ip += 2
vm.globals[globalIndex] = vm.pop()
vm.state.Globals[globalIndex] = vm.pop()
err := vm.push(Null)
if err != nil {
@ -625,9 +722,9 @@ func (vm *VM) Run() error {
ref := vm.pop()
if immutable, ok := ref.(object.Immutable); ok {
vm.globals[globalIndex] = immutable.Clone()
vm.state.Globals[globalIndex] = immutable.Clone()
} else {
vm.globals[globalIndex] = ref
vm.state.Globals[globalIndex] = ref
}
err := vm.push(Null)
@ -639,7 +736,7 @@ func (vm *VM) Run() error {
globalIndex := code.ReadUint16(ins[ip+1:])
vm.currentFrame().ip += 2
err := vm.push(vm.globals[globalIndex])
err := vm.push(vm.state.Globals[globalIndex])
if err != nil {
return err
}
@ -683,6 +780,14 @@ func (vm *VM) Run() error {
return err
}
case code.LoadModule:
name := vm.pop()
err := vm.loadModule(name)
if err != nil {
return err
}
case code.SetSelf:
freeIndex := code.ReadUint8(ins[ip+1:])
vm.currentFrame().ip += 1

33
vm/vm_test.go

@ -13,6 +13,7 @@ import (
"github.com/prologic/monkey-lang/lexer"
"github.com/prologic/monkey-lang/object"
"github.com/prologic/monkey-lang/parser"
"github.com/prologic/monkey-lang/utils"
)
func parse(input string) *ast.Program {
@ -1038,6 +1039,38 @@ func TestCallingRecursiveFunctionsInFunctions(t *testing.T) {
runVmTests(t, tests)
}
func TestImportExpressions(t *testing.T) {
tests := []vmTestCase{
{
input: `mod := import("../testdata/mod"); mod.A`,
expected: 5,
},
{
input: `mod := import("../testdata/mod"); mod.Sum(2, 3)`,
expected: 5,
},
{
input: `mod := import("../testdata/mod"); mod.a`,
expected: nil,
},
}
runVmTests(t, tests)
}
func TestImportSearchPaths(t *testing.T) {
utils.AddPath("../testdata")
tests := []vmTestCase{
{
input: `mod := import("../testdata/mod"); mod.A`,
expected: 5,
},
}
runVmTests(t, tests)
}
func TestExamples(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")

Loading…
Cancel
Save