diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index e9866f3..1fcc816 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -19,29 +19,39 @@ func isError(obj object.Object) bool { return false } -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 evalBlockStatements(node) + return evalBlockStatements(node, env) 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 } return &object.ReturnValue{Value: val} + 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: return &object.Integer{Value: node.Value} @@ -50,18 +60,18 @@ 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 } @@ -72,11 +82,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: @@ -90,11 +100,11 @@ func evalProgram(program *ast.Program) object.Object { return result } -func evalBlockStatements(block *ast.BlockStatement) object.Object { +func evalBlockStatements(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() @@ -188,16 +198,16 @@ 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 } @@ -219,3 +229,11 @@ func isTruthy(obj object.Object) bool { func newError(format string, a ...interface{}) *object.Error { return &object.Error{Message: fmt.Sprintf(format, a...)} } + +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 +} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 335753c..569c32c 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -180,6 +180,10 @@ func TestErrorHandling(t *testing.T) { `, "unknown operator: BOOLEAN + BOOLEAN", }, + { + "foobar", + "identifier not found: foobar", + }, } for _, tt := range tests { @@ -199,12 +203,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() + env := object.NewEnvironment() - return Eval(program) + 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..bd89056 --- /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 94d10e7..bb3df33 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -6,6 +6,7 @@ import ( "io" "monkey/evaluator" "monkey/lexer" + "monkey/object" "monkey/parser" ) @@ -13,6 +14,7 @@ const PROMPT = ">> " func Start(in io.Reader, out io.Writer) { scanner := bufio.NewScanner(in) + env := object.NewEnvironment() for { fmt.Fprintf(out, PROMPT) @@ -31,7 +33,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")