umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs

commit 4da9d4fc0e5ca1e1e2420536976b9644d19cc346
parent 073b20492e0f7bb8c9b8e93b013a8ac68e66cd6d
Author: bsandro <email@bsandro.tech>
Date:   Sun, 26 Jun 2022 00:25:51 +0300

object.Context

Diffstat:
Meval/eval.go | 74+++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Meval/eval_test.go | 19++++++++++++++++++-
Aobject/context.go | 20++++++++++++++++++++
Mrepl/repl.go | 3++-
4 files changed, 85 insertions(+), 31 deletions(-)

diff --git a/eval/eval.go b/eval/eval.go @@ -31,50 +31,58 @@ func isError(obj object.Object) bool { return false } -func Eval(node ast.Node) object.Object { +func Eval(node ast.Node, ctx *object.Context) object.Object { switch node := node.(type) { case *ast.Program: - return evalProgram(node) + return evalProgram(node, ctx) case *ast.ExpressionStatement: - return Eval(node.Expression) + return Eval(node.Expression, ctx) case *ast.IntegerLiteral: return &object.Integer{Value: node.Value} case *ast.Boolean: return boolToObject(node.Value) case *ast.PrefixExpression: - right := Eval(node.Right) + right := Eval(node.Right, ctx) if isError(right) { return right } - return evalPrefixExpression(node.Operator, right) + return evalPrefixExpression(node.Operator, right, ctx) case *ast.InfixExpression: - left := Eval(node.Left) + left := Eval(node.Left, ctx) if isError(left) { return left } - right := Eval(node.Right) + right := Eval(node.Right, ctx) if isError(right) { return right } - return evalInfixExpression(node.Operator, left, right) + return evalInfixExpression(node.Operator, left, right, ctx) case *ast.BlockStatement: - return evalBlockStatement(node) + return evalBlockStatement(node, ctx) case *ast.IfExpression: - return evalIfExpression(node) + return evalIfExpression(node, ctx) case *ast.ReturnStatement: - value := Eval(node.ReturnValue) + value := Eval(node.ReturnValue, ctx) if isError(value) { return value } return &object.ReturnValue{Value: value} + case *ast.LetStatement: + value := Eval(node.Value, ctx) + if isError(value) { + return value + } + ctx.Set(node.Name.Value, value) + case *ast.Identifier: + return evalIdentifier(node, ctx) } return nil } -func evalStatements(statements []ast.Statement) object.Object { +func evalStatements(statements []ast.Statement, ctx *object.Context) object.Object { var result object.Object for _, statement := range statements { - result = Eval(statement) + result = Eval(statement, ctx) if returnValue, ok := result.(*object.ReturnValue); ok { return returnValue.Value } @@ -82,18 +90,18 @@ func evalStatements(statements []ast.Statement) object.Object { return result } -func evalPrefixExpression(operator string, right object.Object) object.Object { +func evalPrefixExpression(operator string, right object.Object, ctx *object.Context) object.Object { switch operator { case "!": - return evalShriekOperatorExpression(right) + return evalShriekOperatorExpression(right, ctx) case "-": - return evalMinusPrefixOperatorExpression(right) + return evalMinusPrefixOperatorExpression(right, ctx) default: return newError("unknown operator: %s%s", operator, right.Type()) } } -func evalShriekOperatorExpression(right object.Object) object.Object { +func evalShriekOperatorExpression(right object.Object, ctx *object.Context) object.Object { switch right { case TRUE: return FALSE @@ -106,7 +114,7 @@ func evalShriekOperatorExpression(right object.Object) object.Object { } } -func evalMinusPrefixOperatorExpression(right object.Object) object.Object { +func evalMinusPrefixOperatorExpression(right object.Object, ctx *object.Context) object.Object { if right.Type() != object.INTEGER_OBJ { return newError("unknown operator: -%s", right.Type()) } @@ -114,10 +122,10 @@ func evalMinusPrefixOperatorExpression(right object.Object) object.Object { return &object.Integer{Value: -value} } -func evalInfixExpression(operator string, left, right object.Object) object.Object { +func evalInfixExpression(operator string, left, right object.Object, ctx *object.Context) object.Object { switch { case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: - return evalIntegerInfixExpression(operator, left, right) + return evalIntegerInfixExpression(operator, left, right, ctx) case operator == "==": return boolToObject(left == right) case operator == "!=": @@ -129,7 +137,7 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje } } -func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object { +func evalIntegerInfixExpression(operator string, left, right object.Object, ctx *object.Context) object.Object { leftValue := left.(*object.Integer).Value rightValue := right.(*object.Integer).Value switch operator { @@ -154,14 +162,14 @@ 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, ctx *object.Context) object.Object { + condition := Eval(ie.Condition, ctx) if isError(condition) { return condition } else if isTruthy(condition) { - return Eval(ie.Consequence) + return Eval(ie.Consequence, ctx) } else if ie.Alternative != nil { - return Eval(ie.Alternative) + return Eval(ie.Alternative, ctx) } else { return NULL } @@ -180,10 +188,10 @@ func isTruthy(obj object.Object) bool { } } -func evalProgram(program *ast.Program) object.Object { +func evalProgram(program *ast.Program, ctx *object.Context) object.Object { var result object.Object for _, statement := range program.Statements { - result = Eval(statement) + result = Eval(statement, ctx) switch result := result.(type) { case *object.ReturnValue: return result.Value @@ -194,10 +202,10 @@ func evalProgram(program *ast.Program) object.Object { return result } -func evalBlockStatement(block *ast.BlockStatement) object.Object { +func evalBlockStatement(block *ast.BlockStatement, ctx *object.Context) object.Object { var result object.Object for _, statement := range block.Statements { - result = Eval(statement) + result = Eval(statement, ctx) if result != nil { rt := result.Type() if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { @@ -207,3 +215,11 @@ func evalBlockStatement(block *ast.BlockStatement) object.Object { } return result } + +func evalIdentifier(node *ast.Identifier, ctx *object.Context) object.Object { + value, ok := ctx.Get(node.Value) + if !ok { + return newError("identifier not found: " + node.Value) + } + return value +} diff --git a/eval/eval_test.go b/eval/eval_test.go @@ -31,7 +31,8 @@ func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) prg := p.ParseProgram() - return Eval(prg) + ctx := object.NewContext() + return Eval(prg, ctx) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { @@ -162,6 +163,7 @@ func TestErrorHandling(t *testing.T) { {"-true", "unknown operator: -BOOLEAN"}, {"true+false;", "unknown operator: BOOLEAN + BOOLEAN"}, {"if (10>1) { true + false; }", "unknown operator: BOOLEAN + BOOLEAN"}, + {"foo", "identifier not found: foo"}, } for _, tt := range tests { evaluated := testEval(tt.input) @@ -175,3 +177,18 @@ func TestErrorHandling(t *testing.T) { } } } + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 8*8; a;", 64}, + {"let a = 8; let b=a; b;", 8}, + {"let a = 5; let b=a; let c = a+b+5; c", 15}, + } + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} diff --git a/object/context.go b/object/context.go @@ -0,0 +1,20 @@ +package object + +type Context struct { + store map[string]Object +} + +func NewContext() *Context { + s := make(map[string]Object) + return &Context{store: s} +} + +func (ctx *Context) Get(name string) (Object, bool) { + obj, ok := ctx.store[name] + return obj, ok +} + +func (ctx *Context) Set(name string, obj Object) Object { + ctx.store[name] = obj + return obj +} diff --git a/repl/repl.go b/repl/repl.go @@ -13,6 +13,7 @@ const PROMPT = "$> " func Start(in io.Reader, out io.Writer) { scanner := bufio.NewScanner(in) + ctx := object.NewContext() for { fmt.Fprintf(out, PROMPT) @@ -27,7 +28,7 @@ func Start(in io.Reader, out io.Writer) { if len(p.Errors()) != 0 { printParserErrors(out, p.Errors()) } - evaluated := eval.Eval(prg) + evaluated := eval.Eval(prg, ctx) if evaluated != nil { io.WriteString(out, evaluated.Inspect()) io.WriteString(out, "\n")