Extra Features
This commit is contained in:
@@ -6,9 +6,12 @@ import (
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": object.GetBuiltinByName("len"),
|
||||
"input": object.GetBuiltinByName("input"),
|
||||
"print": object.GetBuiltinByName("print"),
|
||||
"first": object.GetBuiltinByName("first"),
|
||||
"last": object.GetBuiltinByName("last"),
|
||||
"rest": object.GetBuiltinByName("rest"),
|
||||
"push": object.GetBuiltinByName("push"),
|
||||
"puts": object.GetBuiltinByName("puts"),
|
||||
"pop": object.GetBuiltinByName("pop"),
|
||||
"exit": object.GetBuiltinByName("exit"),
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
case *ast.IfExpression:
|
||||
return evalIfExpression(node, env)
|
||||
|
||||
case *ast.WhileExpression:
|
||||
return evalWhileExpression(node, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
@@ -123,6 +126,29 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for {
|
||||
condition := Eval(we.Condition, env)
|
||||
if isError(condition) {
|
||||
return condition
|
||||
}
|
||||
|
||||
if isTruthy(condition) {
|
||||
result = Eval(we.Consequence, env)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
return result
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
}
|
||||
|
||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package evaluator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -338,22 +341,25 @@ func TestBuiltinFunctions(t *testing.T) {
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
||||
{`len(1)`, errors.New("argument to `len` not supported, got INTEGER")},
|
||||
{`len("one", "two")`, errors.New("wrong number of arguments. got=2, want=1")},
|
||||
{`len("∑")`, 1},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, nil},
|
||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
||||
{`first(1)`, errors.New("argument to `first` must be ARRAY, got INTEGER")},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, nil},
|
||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
||||
{`last(1)`, errors.New("argument to `last` must be ARRAY, got INTEGER")},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, nil},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
||||
{`puts("Hello World")`, nil},
|
||||
{`push(1, 1)`, errors.New("argument to `push` must be ARRAY, got INTEGER")},
|
||||
{`print("Hello World")`, nil},
|
||||
{`input()`, ""},
|
||||
{`pop([])`, errors.New("cannot pop from an empty array")},
|
||||
{`pop([1])`, 1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -363,13 +369,15 @@ func TestBuiltinFunctions(t *testing.T) {
|
||||
case int:
|
||||
testIntegerObject(t, evaluated, int64(expected))
|
||||
case string:
|
||||
testStringObject(t, evaluated, expected)
|
||||
case error:
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error. got=%T (%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
if errObj.Message != expected {
|
||||
if errObj.Message != expected.Error() {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
expected, errObj.Message)
|
||||
}
|
||||
@@ -541,6 +549,31 @@ func TestHashIndexExpressions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhileExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"while (false) { }", nil},
|
||||
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10},
|
||||
{"let n = 10; while (n > 0) { let n = n - 1 }; n", 0},
|
||||
// FIXME: let is an expression statement and bind new values
|
||||
// there is currently no assignment expressions :/
|
||||
{"let n = 0; while (n < 10) { let n = n + 1 }", nil},
|
||||
{"let n = 10; while (n > 0) { let n = n - 1 }", nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
integer, ok := tt.expected.(int)
|
||||
if ok {
|
||||
testIntegerObject(t, evaluated, int64(integer))
|
||||
} else {
|
||||
testNullObject(t, evaluated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testEval(input string) object.Object {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
@@ -585,3 +618,32 @@ func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testStringObject(t *testing.T, obj object.Object, expected string) bool {
|
||||
result, ok := obj.(*object.String)
|
||||
if !ok {
|
||||
t.Errorf("object is not String. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%s, want=%s",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestExamples(t *testing.T) {
|
||||
matches, err := filepath.Glob("../examples/*.monkey")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
b, err := os.ReadFile(match)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
testEval(string(b))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user