diff options
| -rw-r--r-- | evaluator/evaluator.go | 53 | ||||
| -rw-r--r-- | evaluator/evaluator_test.go | 24 | ||||
| -rw-r--r-- | object/environment.go | 20 | ||||
| -rw-r--r-- | repl/repl.go | 5 |
4 files changed, 83 insertions, 19 deletions
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index b9b8bb9..9cead89 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -13,17 +13,27 @@ var ( ) // Eval evaluates the given ast.Node and returns an object.Object. -func Eval(node ast.Node) object.Object { +func Eval(node ast.Node, env *object.Environment) object.Object { switch node := node.(type) { // Statements case *ast.Program: - return evalProgram(node) + return evalProgram(node, env) case *ast.ExpressionStatement: - return Eval(node.Expression) + return Eval(node.Expression, env) case *ast.BlockStatement: - return evalBlockStatement(node) + return evalBlockStatement(node, env) + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + case *ast.Identifier: + return evalIdentifier(node, env) // Expressions case *ast.IntegerLiteral: @@ -33,28 +43,28 @@ func Eval(node ast.Node) object.Object { return nativeBoolToBooleanObject(node.Value) case *ast.PrefixExpression: - right := Eval(node.Right) + right := Eval(node.Right, env) if isError(right) { return right } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: - left := Eval(node.Left) + left := Eval(node.Left, env) if isError(left) { return left } - right := Eval(node.Right) + right := Eval(node.Right, env) if isError(right) { return right } return evalInfixExpression(node.Operator, left, right) case *ast.IfExpression: - return evalIfExpression(node) + return evalIfExpression(node, env) case *ast.ReturnStatement: - val := Eval(node.ReturnValue) + val := Eval(node.ReturnValue, env) if isError(val) { return val } @@ -64,11 +74,11 @@ func Eval(node ast.Node) object.Object { return nil } -func evalProgram(program *ast.Program) object.Object { +func evalProgram(program *ast.Program, env *object.Environment) object.Object { var result object.Object for _, statement := range program.Statements { - result = Eval(statement) + result = Eval(statement, env) switch result := result.(type) { case *object.ReturnValue: @@ -81,11 +91,12 @@ func evalProgram(program *ast.Program) object.Object { return result } -func evalBlockStatement(block *ast.BlockStatement) object.Object { + +func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { var result object.Object for _, statement := range block.Statements { - result = Eval(statement) + result = Eval(statement, env) if result != nil { rt := result.Type() @@ -182,20 +193,28 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje } } -func evalIfExpression(ie *ast.IfExpression) object.Object { - condition := Eval(ie.Condition) +func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ie.Condition, env) if isError(condition) { return condition } if isTruthy(condition) { - return Eval(ie.Consequence) + return Eval(ie.Consequence, env) } else if ie.Alternative != nil { - return Eval(ie.Alternative) + return Eval(ie.Alternative, env) } else { return NULL } } +func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { + val, ok := env.Get(node.Value) + if !ok { + return newError("identifier not found: " + node.Value) + } + return val +} + func isTruthy(obj object.Object) bool { switch obj { case NULL: diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 6553f55..38fed91 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -177,6 +177,10 @@ func TestErrorhandling(t *testing.T) { `, "unknown operator: BOOLEAN + BOOLEAN", }, + { + "foobar", + "identifier not found: foobar", + }, } for _, tt := range tests { @@ -194,11 +198,29 @@ func TestErrorhandling(t *testing.T) { } } +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + } { + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) program := p.ParseProgram() - return Eval(program) + env := object.NewEnvironment() + + return Eval(program, env) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { diff --git a/object/environment.go b/object/environment.go new file mode 100644 index 0000000..4b37749 --- /dev/null +++ b/object/environment.go @@ -0,0 +1,20 @@ +package object + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s} +} + +type Environment struct { + store map[string]Object +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/repl/repl.go b/repl/repl.go index 9a43119..59c56d6 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -7,6 +7,7 @@ import ( "mana/lexer" "mana/parser" "mana/evaluator" + "mana/object" ) // PROMPT is the prompt for the REPL. @@ -22,6 +23,8 @@ const MANA_START = ` func Start(in io.Reader, out io.Writer) { var scanner *bufio.Scanner = bufio.NewScanner(in) + env := object.NewEnvironment() + io.WriteString(out, MANA_START + "\n") for { @@ -43,7 +46,7 @@ func Start(in io.Reader, out io.Writer) { continue } - evaluated := evaluator.Eval(program) + evaluated := evaluator.Eval(program, env) if evaluated != nil { io.WriteString(out, evaluated.Inspect()) io.WriteString(out, "\n") |
