module support
This commit is contained in:
22
ast/ast.go
22
ast/ast.go
@@ -428,3 +428,25 @@ func (c *Comment) String() string {
|
|||||||
|
|
||||||
return out.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) TokenLiteral() string {
|
||||||
|
return ie.Token.Literal
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
func (ie *ImportExpression) expressionNode() {}
|
||||||
|
|||||||
@@ -8,43 +8,43 @@ import (
|
|||||||
|
|
||||||
// Builtins ...
|
// Builtins ...
|
||||||
var Builtins = map[string]*object.Builtin{
|
var Builtins = map[string]*object.Builtin{
|
||||||
"len": {Name: "len", Fn: Len},
|
"len": {Name: "len", Fn: Len},
|
||||||
"input": {Name: "input", Fn: Input},
|
"input": {Name: "input", Fn: Input},
|
||||||
"print": {Name: "print", Fn: Print},
|
"print": {Name: "print", Fn: Print},
|
||||||
"first": {Name: "first", Fn: First},
|
"first": {Name: "first", Fn: First},
|
||||||
"last": {Name: "last", Fn: Last},
|
"last": {Name: "last", Fn: Last},
|
||||||
"rest": {Name: "rest", Fn: Rest},
|
"rest": {Name: "rest", Fn: Rest},
|
||||||
"push": {Name: "push", Fn: Push},
|
"push": {Name: "push", Fn: Push},
|
||||||
"pop": {Name: "pop", Fn: Pop},
|
"pop": {Name: "pop", Fn: Pop},
|
||||||
"exit": {Name: "exit", Fn: Exit},
|
"exit": {Name: "exit", Fn: Exit},
|
||||||
"assert": {Name: "assert", Fn: Assert},
|
"assert": {Name: "assert", Fn: Assert},
|
||||||
"bool": {Name: "bool", Fn: Bool},
|
"bool": {Name: "bool", Fn: Bool},
|
||||||
"int": {Name: "int", Fn: Int},
|
"int": {Name: "int", Fn: Int},
|
||||||
"str": {Name: "str", Fn: Str},
|
"str": {Name: "str", Fn: Str},
|
||||||
"type": {Name: "type", Fn: TypeOf},
|
"type": {Name: "type", Fn: TypeOf},
|
||||||
"args": {Name: "args", Fn: Args},
|
"args": {Name: "args", Fn: Args},
|
||||||
"lower": {Name: "lower", Fn: Lower},
|
"lower": {Name: "lower", Fn: Lower},
|
||||||
"upper": {Name: "upper", Fn: Upper},
|
"upper": {Name: "upper", Fn: Upper},
|
||||||
"join": {Name: "join", Fn: Join},
|
"join": {Name: "join", Fn: Join},
|
||||||
"split": {Name: "split", Fn: Split},
|
"split": {Name: "split", Fn: Split},
|
||||||
"find": {Name: "find", Fn: Find},
|
"find": {Name: "find", Fn: Find},
|
||||||
"read": {Name: "read", Fn: Read},
|
"readfile": {Name: "readfile", Fn: ReadFile},
|
||||||
"write": {Name: "write", Fn: Write},
|
"writefile": {Name: "writefile", Fn: WriteFile},
|
||||||
"ffi": {Name: "ffi", Fn: FFI},
|
"ffi": {Name: "ffi", Fn: FFI},
|
||||||
"abs": {Name: "abs", Fn: Abs},
|
"abs": {Name: "abs", Fn: Abs},
|
||||||
"bin": {Name: "bin", Fn: Bin},
|
"bin": {Name: "bin", Fn: Bin},
|
||||||
"hex": {Name: "hex", Fn: Hex},
|
"hex": {Name: "hex", Fn: Hex},
|
||||||
"ord": {Name: "ord", Fn: Ord},
|
"ord": {Name: "ord", Fn: Ord},
|
||||||
"chr": {Name: "chr", Fn: Chr},
|
"chr": {Name: "chr", Fn: Chr},
|
||||||
"divmod": {Name: "divmod", Fn: Divmod},
|
"divmod": {Name: "divmod", Fn: Divmod},
|
||||||
"hash": {Name: "hash", Fn: HashOf},
|
"hash": {Name: "hash", Fn: HashOf},
|
||||||
"id": {Name: "id", Fn: IdOf},
|
"id": {Name: "id", Fn: IdOf},
|
||||||
"oct": {Name: "oct", Fn: Oct},
|
"oct": {Name: "oct", Fn: Oct},
|
||||||
"pow": {Name: "pow", Fn: Pow},
|
"pow": {Name: "pow", Fn: Pow},
|
||||||
"min": {Name: "min", Fn: Min},
|
"min": {Name: "min", Fn: Min},
|
||||||
"max": {Name: "max", Fn: Max},
|
"max": {Name: "max", Fn: Max},
|
||||||
"sorted": {Name: "sorted", Fn: Sorted},
|
"sorted": {Name: "sorted", Fn: Sorted},
|
||||||
"reversed": {Name: "reversed", Fn: Reversed},
|
"reversed": {Name: "reversed", Fn: Reversed},
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuiltinsIndex ...
|
// BuiltinsIndex ...
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"monkey/typing"
|
"monkey/typing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Read ...
|
// ReadFile ...
|
||||||
func Read(args ...object.Object) object.Object {
|
func ReadFile(args ...object.Object) object.Object {
|
||||||
if err := typing.Check(
|
if err := typing.Check(
|
||||||
"read", args,
|
"readfile", args,
|
||||||
typing.ExactArgs(1),
|
typing.ExactArgs(1),
|
||||||
typing.WithTypes(object.STRING_OBJ),
|
typing.WithTypes(object.STRING_OBJ),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"monkey/typing"
|
"monkey/typing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Write ...
|
// WriteFile ...
|
||||||
func Write(args ...object.Object) object.Object {
|
func WriteFile(args ...object.Object) object.Object {
|
||||||
if err := typing.Check(
|
if err := typing.Check(
|
||||||
"write", args,
|
"writefile", args,
|
||||||
typing.ExactArgs(2),
|
typing.ExactArgs(2),
|
||||||
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
|
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -61,7 +61,7 @@ const (
|
|||||||
OpClosure
|
OpClosure
|
||||||
OpGetFree
|
OpGetFree
|
||||||
OpCurrentClosure
|
OpCurrentClosure
|
||||||
OpNoop
|
OpLoadModule
|
||||||
)
|
)
|
||||||
|
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
@@ -112,7 +112,7 @@ var definitions = map[Opcode]*Definition{
|
|||||||
OpClosure: {"OpClosure", []int{2, 1}},
|
OpClosure: {"OpClosure", []int{2, 1}},
|
||||||
OpGetFree: {"OpGetFree", []int{1}},
|
OpGetFree: {"OpGetFree", []int{1}},
|
||||||
OpCurrentClosure: {"OpCurrentClosure", []int{}},
|
OpCurrentClosure: {"OpCurrentClosure", []int{}},
|
||||||
OpNoop: {"OpNoop", []int{}},
|
OpLoadModule: {"OpLoadModule", []int{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lookup(op byte) (*Definition, error) {
|
func Lookup(op byte) (*Definition, error) {
|
||||||
|
|||||||
@@ -513,6 +513,16 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
afterConsequencePos := c.emit(code.OpNull)
|
afterConsequencePos := c.emit(code.OpNull)
|
||||||
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
|
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
|
||||||
|
|
||||||
|
case *ast.ImportExpression:
|
||||||
|
c.l++
|
||||||
|
err := c.Compile(node.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.l--
|
||||||
|
|
||||||
|
c.emit(code.OpLoadModule)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1031,6 +1031,18 @@ func TestIteration(t *testing.T) {
|
|||||||
runCompilerTests(t, tests)
|
runCompilerTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImportExpressions(t *testing.T) {
|
||||||
|
tests := []compilerTestCase2{
|
||||||
|
{
|
||||||
|
input: `import("foo")`,
|
||||||
|
constants: []interface{}{"foo"},
|
||||||
|
instructions: "0000 OpConstant 0\n0003 OpLoadModule\n0004 OpPop\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCompilerTests2(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type Symbol struct {
|
|||||||
type SymbolTable struct {
|
type SymbolTable struct {
|
||||||
Outer *SymbolTable
|
Outer *SymbolTable
|
||||||
|
|
||||||
store map[string]Symbol
|
Store map[string]Symbol
|
||||||
numDefinitions int
|
numDefinitions int
|
||||||
|
|
||||||
FreeSymbols []Symbol
|
FreeSymbols []Symbol
|
||||||
@@ -34,7 +34,7 @@ func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable {
|
|||||||
func NewSymbolTable() *SymbolTable {
|
func NewSymbolTable() *SymbolTable {
|
||||||
s := make(map[string]Symbol)
|
s := make(map[string]Symbol)
|
||||||
free := []Symbol{}
|
free := []Symbol{}
|
||||||
return &SymbolTable{store: s, FreeSymbols: free}
|
return &SymbolTable{Store: s, FreeSymbols: free}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SymbolTable) Define(name string) Symbol {
|
func (s *SymbolTable) Define(name string) Symbol {
|
||||||
@@ -45,13 +45,13 @@ func (s *SymbolTable) Define(name string) Symbol {
|
|||||||
symbol.Scope = LocalScope
|
symbol.Scope = LocalScope
|
||||||
}
|
}
|
||||||
|
|
||||||
s.store[name] = symbol
|
s.Store[name] = symbol
|
||||||
s.numDefinitions++
|
s.numDefinitions++
|
||||||
return symbol
|
return symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
||||||
obj, ok := s.store[name]
|
obj, ok := s.Store[name]
|
||||||
if !ok && s.Outer != nil {
|
if !ok && s.Outer != nil {
|
||||||
obj, ok = s.Outer.Resolve(name)
|
obj, ok = s.Outer.Resolve(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -72,7 +72,7 @@ func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
|||||||
|
|
||||||
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
|
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
|
||||||
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
|
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
|
||||||
s.store[name] = symbol
|
s.Store[name] = symbol
|
||||||
return symbol
|
return symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +82,12 @@ func (s *SymbolTable) DefineFree(original Symbol) Symbol {
|
|||||||
symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1}
|
symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1}
|
||||||
symbol.Scope = FreeScope
|
symbol.Scope = FreeScope
|
||||||
|
|
||||||
s.store[original.Name] = symbol
|
s.Store[original.Name] = symbol
|
||||||
return symbol
|
return symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SymbolTable) DefineFunctionName(name string) Symbol {
|
func (s *SymbolTable) DefineFunctionName(name string) Symbol {
|
||||||
symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope}
|
symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope}
|
||||||
s.store[name] = symbol
|
s.Store[name] = symbol
|
||||||
return symbol
|
return symbol
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"monkey/ast"
|
"monkey/ast"
|
||||||
"monkey/builtins"
|
"monkey/builtins"
|
||||||
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
|
"monkey/parser"
|
||||||
|
"monkey/utils"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +44,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
|||||||
case *ast.WhileExpression:
|
case *ast.WhileExpression:
|
||||||
return evalWhileExpression(node, env)
|
return evalWhileExpression(node, env)
|
||||||
|
|
||||||
|
case *ast.ImportExpression:
|
||||||
|
return evalImportExpression(node, env)
|
||||||
|
|
||||||
case *ast.ReturnStatement:
|
case *ast.ReturnStatement:
|
||||||
val := Eval(node.ReturnValue, env)
|
val := Eval(node.ReturnValue, env)
|
||||||
if isError(val) {
|
if isError(val) {
|
||||||
@@ -190,6 +197,22 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
|
func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
|
||||||
var result object.Object
|
var result object.Object
|
||||||
|
|
||||||
@@ -208,9 +231,9 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec
|
|||||||
|
|
||||||
if result != nil {
|
if result != nil {
|
||||||
return result
|
return result
|
||||||
} else {
|
|
||||||
return NULL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||||
@@ -485,6 +508,29 @@ func newError(format string, a ...interface{}) *object.Error {
|
|||||||
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvalModule evaluates the named module and returns a *object.Module objec
|
||||||
|
func EvalModule(name string) object.Object {
|
||||||
|
filename := utils.FindModule(name)
|
||||||
|
|
||||||
|
b, err := os.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()
|
||||||
|
}
|
||||||
|
|
||||||
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
|
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
|
||||||
if val, ok := env.Get(node.Value); ok {
|
if val, ok := env.Get(node.Value); ok {
|
||||||
return val
|
return val
|
||||||
@@ -557,11 +603,18 @@ func evalIndexExpression(left, index object.Object) object.Object {
|
|||||||
return evalArrayIndexExpression(left, index)
|
return evalArrayIndexExpression(left, index)
|
||||||
case left.Type() == object.HASH_OBJ:
|
case left.Type() == object.HASH_OBJ:
|
||||||
return evalHashIndexExpression(left, index)
|
return evalHashIndexExpression(left, index)
|
||||||
|
case left.Type() == object.MODULE_OBJ:
|
||||||
|
return EvalModuleIndexExpression(left, index)
|
||||||
default:
|
default:
|
||||||
return newError("index operator not supported: %s", left.Type())
|
return newError("index operator not supported: %s", left.Type())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EvalModuleIndexExpression(module, index object.Object) object.Object {
|
||||||
|
moduleObject := module.(*object.Module)
|
||||||
|
return evalHashIndexExpression(moduleObject.Attrs, index)
|
||||||
|
}
|
||||||
|
|
||||||
func evalStringIndexExpression(str, index object.Object) object.Object {
|
func evalStringIndexExpression(str, index object.Object) object.Object {
|
||||||
stringObject := str.(*object.String)
|
stringObject := str.(*object.String)
|
||||||
idx := index.(*object.Integer).Value
|
idx := index.(*object.Integer).Value
|
||||||
|
|||||||
@@ -2,14 +2,51 @@ package evaluator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
|
"monkey/utils"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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) {
|
func TestEvalExpressions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
@@ -814,6 +851,37 @@ func testStringObject(t *testing.T, obj object.Object, expected string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
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) {
|
func TestExamples(t *testing.T) {
|
||||||
matches, err := filepath.Glob("../examples/*.monkey")
|
matches, err := filepath.Glob("../examples/*.monkey")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
||||||
env := NewEnvironment()
|
env := NewEnvironment()
|
||||||
env.outer = outer
|
env.outer = outer
|
||||||
@@ -28,3 +30,18 @@ func (e *Environment) Set(name string, val Object) Object {
|
|||||||
e.store[name] = val
|
e.store[name] = val
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExportedHash returns a new Hash with the names and values of every publicly
|
||||||
|
// 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
|
||||||
|
// evaluated 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}
|
||||||
|
}
|
||||||
|
|||||||
29
object/module.go
Normal file
29
object/module.go
Normal file
@@ -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) String() string {
|
||||||
|
return m.Inspect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Module) Type() ObjectType {
|
||||||
|
return MODULE_OBJ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Module) Bool() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Module) Inspect() string {
|
||||||
|
return fmt.Sprintf("<module '%s'>", m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Module) Compare(other Object) int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
HASH_OBJ = "hash"
|
HASH_OBJ = "hash"
|
||||||
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION"
|
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION"
|
||||||
CLOSURE_OBJ = "closure"
|
CLOSURE_OBJ = "closure"
|
||||||
|
MODULE_OBJ = "module"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Comparable is the interface for comparing two Object and their underlying
|
// Comparable is the interface for comparing two Object and their underlying
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ func New(l *lexer.Lexer) *Parser {
|
|||||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||||
|
p.registerPrefix(token.IMPORT, p.parseImportExpression)
|
||||||
p.registerPrefix(token.WHILE, p.parseWhileExpression)
|
p.registerPrefix(token.WHILE, p.parseWhileExpression)
|
||||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||||
@@ -601,3 +602,20 @@ func (p *Parser) parseBindExpression(expression ast.Expression) ast.Expression {
|
|||||||
|
|
||||||
return be
|
return be
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1227,3 +1227,23 @@ func testComment(t *testing.T, s ast.Statement, expected string) bool {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
40
repl/repl.go
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"monkey/builtins"
|
|
||||||
"monkey/compiler"
|
"monkey/compiler"
|
||||||
"monkey/evaluator"
|
"monkey/evaluator"
|
||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
@@ -42,25 +41,6 @@ type Options struct {
|
|||||||
Interactive bool
|
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.GlobalsSize),
|
|
||||||
symbols: symbolTable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type REPL struct {
|
type REPL struct {
|
||||||
user string
|
user string
|
||||||
args []string
|
args []string
|
||||||
@@ -101,14 +81,14 @@ func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
|
|||||||
|
|
||||||
// Exec parses, compiles and executes the program given by f and returns
|
// Exec parses, compiles and executes the program given by f and returns
|
||||||
// the resulting virtual machine, any errors are printed to stderr
|
// 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 := io.ReadAll(f)
|
b, err := io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state = NewVMState()
|
state = vm.NewVMState()
|
||||||
|
|
||||||
l := lexer.New(string(b))
|
l := lexer.New(string(b))
|
||||||
p := parser.New(l)
|
p := parser.New(l)
|
||||||
@@ -119,7 +99,7 @@ func (r *REPL) Exec(f io.Reader) (state *VMState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c := compiler.NewWithState(state.symbols, state.constants)
|
c := compiler.NewWithState(state.Symbols, state.Constants)
|
||||||
c.Debug = r.opts.Debug
|
c.Debug = r.opts.Debug
|
||||||
err = c.Compile(program)
|
err = c.Compile(program)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,9 +108,9 @@ func (r *REPL) Exec(f io.Reader) (state *VMState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code := c.Bytecode()
|
code := c.Bytecode()
|
||||||
state.constants = code.Constants
|
state.Constants = code.Constants
|
||||||
|
|
||||||
machine := vm.NewWithGlobalState(code, state.globals)
|
machine := vm.NewWithState(code, state)
|
||||||
machine.Debug = r.opts.Debug
|
machine.Debug = r.opts.Debug
|
||||||
err = machine.Run()
|
err = machine.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -176,11 +156,11 @@ func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environmen
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartExecLoop starts the REPL in a continious exec loop
|
// 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)
|
scanner := bufio.NewScanner(in)
|
||||||
|
|
||||||
if state == nil {
|
if state == nil {
|
||||||
state = NewVMState()
|
state = vm.NewVMState()
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -201,7 +181,7 @@ func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c := compiler.NewWithState(state.symbols, state.constants)
|
c := compiler.NewWithState(state.Symbols, state.Constants)
|
||||||
c.Debug = r.opts.Debug
|
c.Debug = r.opts.Debug
|
||||||
err := c.Compile(program)
|
err := c.Compile(program)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,9 +190,9 @@ func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code := c.Bytecode()
|
code := c.Bytecode()
|
||||||
state.constants = code.Constants
|
state.Constants = code.Constants
|
||||||
|
|
||||||
machine := vm.NewWithGlobalState(code, state.globals)
|
machine := vm.NewWithState(code, state)
|
||||||
machine.Debug = r.opts.Debug
|
machine.Debug = r.opts.Debug
|
||||||
err = machine.Run()
|
err = machine.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
3
testdata/mod.monkey
vendored
Normal file
3
testdata/mod.monkey
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
a := 1
|
||||||
|
A := 5
|
||||||
|
Sum := fn(a, b) { return a + b }
|
||||||
@@ -84,6 +84,8 @@ const (
|
|||||||
ELSE = "ELSE"
|
ELSE = "ELSE"
|
||||||
RETURN = "RETURN"
|
RETURN = "RETURN"
|
||||||
WHILE = "WHILE"
|
WHILE = "WHILE"
|
||||||
|
// IMPORT the `import` keyword (import)
|
||||||
|
IMPORT = "IMPORT"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords = map[string]TokenType{
|
var keywords = map[string]TokenType{
|
||||||
@@ -95,6 +97,7 @@ var keywords = map[string]TokenType{
|
|||||||
"else": ELSE,
|
"else": ELSE,
|
||||||
"return": RETURN,
|
"return": RETURN,
|
||||||
"while": WHILE,
|
"while": WHILE,
|
||||||
|
"import": IMPORT,
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupIdent(ident string) TokenType {
|
func LookupIdent(ident string) TokenType {
|
||||||
|
|||||||
53
utils/utils.go
Normal file
53
utils/utils.go
Normal file
@@ -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 ""
|
||||||
|
}
|
||||||
151
vm/vm.go
151
vm/vm.go
@@ -2,12 +2,17 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"monkey/builtins"
|
"monkey/builtins"
|
||||||
"monkey/code"
|
"monkey/code"
|
||||||
"monkey/compiler"
|
"monkey/compiler"
|
||||||
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
|
"monkey/parser"
|
||||||
|
"monkey/utils"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const StackSize = 2048
|
const StackSize = 2048
|
||||||
@@ -18,16 +23,88 @@ var Null = &object.Null{}
|
|||||||
var True = &object.Boolean{Value: true}
|
var True = &object.Boolean{Value: true}
|
||||||
var False = &object.Boolean{Value: false}
|
var False = &object.Boolean{Value: false}
|
||||||
|
|
||||||
|
// 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, GlobalsSize),
|
||||||
|
Symbols: symbolTable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
type VM struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
constants []object.Object
|
state *VMState
|
||||||
|
|
||||||
stack []object.Object
|
stack []object.Object
|
||||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||||
|
|
||||||
globals []object.Object
|
|
||||||
|
|
||||||
frames []*Frame
|
frames []*Frame
|
||||||
framesIndex int
|
framesIndex int
|
||||||
}
|
}
|
||||||
@@ -40,23 +117,37 @@ func New(bytecode *compiler.Bytecode) *VM {
|
|||||||
frames := make([]*Frame, MaxFrames)
|
frames := make([]*Frame, MaxFrames)
|
||||||
frames[0] = mainFrame
|
frames[0] = mainFrame
|
||||||
|
|
||||||
|
state := NewVMState()
|
||||||
|
state.Constants = bytecode.Constants
|
||||||
|
|
||||||
return &VM{
|
return &VM{
|
||||||
constants: bytecode.Constants,
|
state: state,
|
||||||
|
|
||||||
stack: make([]object.Object, StackSize),
|
stack: make([]object.Object, StackSize),
|
||||||
sp: 0,
|
sp: 0,
|
||||||
|
|
||||||
globals: make([]object.Object, GlobalsSize),
|
|
||||||
|
|
||||||
frames: frames,
|
frames: frames,
|
||||||
framesIndex: 1,
|
framesIndex: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithGlobalState(bytecode *compiler.Bytecode, s []object.Object) *VM {
|
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
|
||||||
vm := New(bytecode)
|
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||||
vm.globals = s
|
mainClosure := &object.Closure{Fn: mainFn}
|
||||||
return vm
|
mainFrame := NewFrame(mainClosure, 0)
|
||||||
|
|
||||||
|
frames := make([]*Frame, MaxFrames)
|
||||||
|
frames[0] = mainFrame
|
||||||
|
|
||||||
|
return &VM{
|
||||||
|
state: state,
|
||||||
|
|
||||||
|
frames: frames,
|
||||||
|
framesIndex: 1,
|
||||||
|
|
||||||
|
stack: make([]object.Object, StackSize),
|
||||||
|
sp: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) currentFrame() *Frame {
|
func (vm *VM) currentFrame() *Frame {
|
||||||
@@ -73,6 +164,24 @@ func (vm *VM) popFrame() *Frame {
|
|||||||
return vm.frames[vm.framesIndex]
|
return vm.frames[vm.framesIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) LastPoppedStackElem() object.Object {
|
func (vm *VM) LastPoppedStackElem() object.Object {
|
||||||
return vm.stack[vm.sp]
|
return vm.stack[vm.sp]
|
||||||
}
|
}
|
||||||
@@ -108,7 +217,7 @@ func (vm *VM) Run() error {
|
|||||||
constIndex := code.ReadUint16(ins[ip+1:])
|
constIndex := code.ReadUint16(ins[ip+1:])
|
||||||
vm.currentFrame().ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
err := vm.push(vm.constants[constIndex])
|
err := vm.push(vm.state.Constants[constIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -185,9 +294,9 @@ func (vm *VM) Run() error {
|
|||||||
|
|
||||||
ref := vm.pop()
|
ref := vm.pop()
|
||||||
if immutable, ok := ref.(object.Immutable); ok {
|
if immutable, ok := ref.(object.Immutable); ok {
|
||||||
vm.globals[globalIndex] = immutable.Clone()
|
vm.state.Globals[globalIndex] = immutable.Clone()
|
||||||
} else {
|
} else {
|
||||||
vm.globals[globalIndex] = ref
|
vm.state.Globals[globalIndex] = ref
|
||||||
}
|
}
|
||||||
|
|
||||||
err := vm.push(Null)
|
err := vm.push(Null)
|
||||||
@@ -198,7 +307,7 @@ func (vm *VM) Run() error {
|
|||||||
case code.OpAssignGlobal:
|
case code.OpAssignGlobal:
|
||||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||||
vm.currentFrame().ip += 2
|
vm.currentFrame().ip += 2
|
||||||
vm.globals[globalIndex] = vm.pop()
|
vm.state.Globals[globalIndex] = vm.pop()
|
||||||
|
|
||||||
err := vm.push(Null)
|
err := vm.push(Null)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -221,7 +330,7 @@ func (vm *VM) Run() error {
|
|||||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||||
vm.currentFrame().ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
err := vm.push(vm.globals[globalIndex])
|
err := vm.push(vm.state.Globals[globalIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -359,6 +468,14 @@ func (vm *VM) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case code.OpLoadModule:
|
||||||
|
name := vm.pop()
|
||||||
|
|
||||||
|
err := vm.loadModule(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if vm.Debug {
|
if vm.Debug {
|
||||||
@@ -396,6 +513,8 @@ func (vm *VM) executeGetItem(left, index object.Object) error {
|
|||||||
return vm.executeArrayGetItem(left, index)
|
return vm.executeArrayGetItem(left, index)
|
||||||
case left.Type() == object.HASH_OBJ:
|
case left.Type() == object.HASH_OBJ:
|
||||||
return vm.executeHashGetItem(left, index)
|
return vm.executeHashGetItem(left, index)
|
||||||
|
case left.Type() == object.MODULE_OBJ:
|
||||||
|
return vm.executeHashGetItem(left.(*object.Module).Attrs, index)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"index operator not supported: left=%s index=%s",
|
"index operator not supported: left=%s index=%s",
|
||||||
@@ -766,7 +885,7 @@ func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) pushClosure(constIndex, numFree int) error {
|
func (vm *VM) pushClosure(constIndex, numFree int) error {
|
||||||
constant := vm.constants[constIndex]
|
constant := vm.state.Constants[constIndex]
|
||||||
function, ok := constant.(*object.CompiledFunction)
|
function, ok := constant.(*object.CompiledFunction)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("not a function %+v", constant)
|
return fmt.Errorf("not a function %+v", constant)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
|
"monkey/utils"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -1127,3 +1128,35 @@ func BenchmarkFibonacci(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user