Extra Features
Some checks failed
Build / build (push) Failing after 1m40s
Test / build (push) Failing after 11m47s

This commit is contained in:
Chuck Smith
2024-03-14 21:25:47 -04:00
parent 36f04713bd
commit 997f0865f4
20 changed files with 757 additions and 128 deletions

View File

@@ -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"),
}

View File

@@ -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

View File

@@ -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))
}
}