module support
This commit is contained in:
@@ -4,7 +4,11 @@ import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/builtins"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"monkey/utils"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -40,6 +44,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
case *ast.WhileExpression:
|
||||
return evalWhileExpression(node, env)
|
||||
|
||||
case *ast.ImportExpression:
|
||||
return evalImportExpression(node, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
@@ -190,6 +197,22 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
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 {
|
||||
var result object.Object
|
||||
|
||||
@@ -208,9 +231,9 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec
|
||||
|
||||
if result != nil {
|
||||
return result
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return NULL
|
||||
}
|
||||
|
||||
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...)}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if val, ok := env.Get(node.Value); ok {
|
||||
return val
|
||||
@@ -557,11 +603,18 @@ func evalIndexExpression(left, index object.Object) object.Object {
|
||||
return evalArrayIndexExpression(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return evalHashIndexExpression(left, index)
|
||||
case left.Type() == object.MODULE_OBJ:
|
||||
return EvalModuleIndexExpression(left, index)
|
||||
default:
|
||||
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 {
|
||||
stringObject := str.(*object.String)
|
||||
idx := index.(*object.Integer).Value
|
||||
|
||||
@@ -2,14 +2,51 @@ package evaluator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"monkey/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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) {
|
||||
tests := []struct {
|
||||
input string
|
||||
@@ -814,6 +851,37 @@ func testStringObject(t *testing.T, obj object.Object, expected string) bool {
|
||||
}
|
||||
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) {
|
||||
matches, err := filepath.Glob("../examples/*.monkey")
|
||||
|
||||
Reference in New Issue
Block a user