From 09f0409706fe9040aa8ff11598fc62c85d96d755 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Tue, 15 Oct 2024 01:41:12 +0900 Subject: [PATCH 1/5] consider outer env when to set values to identifiers (#57) --- object/environment.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/object/environment.go b/object/environment.go index a4bd8ca..4658556 100644 --- a/object/environment.go +++ b/object/environment.go @@ -27,6 +27,24 @@ func (e *Environment) Get(name string) (Object, bool) { } func (e *Environment) Set(name string, val Object) Object { + // check if the variable already exists in the current environment + // current environment has higher priority than outer environment + if _, ok := e.store[name]; ok { + // if it does, update the value + e.store[name] = val + return val + } + + // check if the variable exists in the outer environment + if e.outer != nil { + if _, ok := e.outer.Get(name); ok { + // if it does, update the value + e.outer.Set(name, val) + return val + } + } + + // create a new variable in the current environment e.store[name] = val return val } From d49dab8a6dd2f71c47dcb5b2b0af1ea2795df4c3 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Tue, 15 Oct 2024 01:42:03 +0900 Subject: [PATCH 2/5] add object for break, continue and return (#57) --- object/object.go | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/object/object.go b/object/object.go index 89dc4bf..6fd2c84 100644 --- a/object/object.go +++ b/object/object.go @@ -8,16 +8,19 @@ import ( ) const ( - ERROR_OBJ = "ERROR" - INTEGER_OBJ = "INTEGER" - STRING_OBJ = "STRING" - BOOLEAN_OBJ = "BOOLEAN" - ARRAY_OBJ = "ARRAY" - NULL_OBJ = "NULL" - FUNCTION_OBJ = "FUNCTION" - BUILTIN_OBJ = "BUILTIN" - QUOTE_OBJ = "QUOTE" - MACRO_OBJ = "MACRO" + ERROR_OBJ = "ERROR" + INTEGER_OBJ = "INTEGER" + STRING_OBJ = "STRING" + BOOLEAN_OBJ = "BOOLEAN" + ARRAY_OBJ = "ARRAY" + NULL_OBJ = "NULL" + RETURN_VALUE_OBJ = "RETURN_VALUE" + BREAK_OBJ = "BREAK" + CONTINUE_OBJ = "CONTINUE" + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + QUOTE_OBJ = "QUOTE" + MACRO_OBJ = "MACRO" ) type ObjectType string @@ -73,6 +76,23 @@ type Null struct{} func (n *Null) Type() ObjectType { return NULL_OBJ } func (n *Null) Inspect() string { return "null" } +type Break struct{} + +func (b *Break) Type() ObjectType { return BREAK_OBJ } +func (b *Break) Inspect() string { return "break" } + +type Continue struct{} + +func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } +func (c *Continue) Inspect() string { return "continue" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + type Error struct { Message string } From 2b6d288bc2097dc54af3438a2d12ee37c5213619 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Tue, 15 Oct 2024 01:42:31 +0900 Subject: [PATCH 3/5] add unit tests for break, continue and return statements (#57) --- evaluator/evaluator_test.go | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 305c542..f8e1f4c 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -535,6 +535,72 @@ func TestLoopExpression(t *testing.T) { ]`, expected: 60, }, + { + name: "break and continue in loop", + input: ` + [ + { + "set": { + "var": "$sum", + "val": 0 + } + }, + { + "loop": { + "for": "$i", + "from": 1, + "until": 15, + "do": { + "if": { + "cond": { + "command": { + "symbol": ">", + "args": ["$i", 10] + } + }, + "conseq": { + "break": {} + }, + "alt": { + "if": { + "cond": { + "command": { + "symbol": "==", + "args": [ + { + "command": { + "symbol": "%", + "args": ["$i", 2] + } + }, + 0 + ] + } + }, + "conseq": { + "set": { + "var": "$sum", + "val": { + "command": { + "symbol": "+", + "args": ["$sum", "$i"] + } + } + } + }, + "alt": { + "continue": {} + } + } + } + } + } + } + }, + "$sum" + ]`, + expected: 30, + }, } for _, tt := range tests { @@ -713,6 +779,66 @@ func TestLambdaExpression(t *testing.T) { ]`, expected: 6, }, + { + name: "lambda expression with return statement", + input: ` + [ + { + "set": { + "var": "$f", + "val": { + "lambda": { + "body": [ + { + "set": { + "var": "$sum", + "val": 0 + } + }, + { + "loop": { + "for": "$i", + "from": 1, + "until": 11, + "do": { + "if": { + "cond": { + "command": { + "symbol": ">", + "args": ["$i", 5] + } + }, + "conseq": { + "return": "$sum" + }, + "alt": { + "set": { + "var": "$sum", + "val": { + "command": { + "symbol": "+", + "args": ["$sum", "$i"] + } + } + } + } + } + } + } + } + ] + } + } + } + }, + { + "command": { + "symbol": "$f" + } + } + ]`, + expected: 15, + }, } for _, tt := range tests { From 3b17ceb12dda371740232e0a7205d475f5df22c6 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Tue, 15 Oct 2024 01:43:23 +0900 Subject: [PATCH 4/5] handle with break, continue and return exp in eval (#57) --- evaluator/evaluator.go | 51 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index c01f84a..61badf1 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -10,9 +10,11 @@ import ( ) var ( - Null = &object.Null{} - True = &object.Boolean{Value: true} - False = &object.Boolean{Value: false} + Null = &object.Null{} + Break = &object.Break{} + Continue = &object.Continue{} + True = &object.Boolean{Value: true} + False = &object.Boolean{Value: false} ) const identEmbedPattern = `\{\s*\$\w+\s*\}` @@ -52,7 +54,7 @@ func Eval(exp ast.Expression, env *object.Environment) object.Object { } } -func evalArray(array *ast.Array, env *object.Environment) *object.Array { +func evalArray(array *ast.Array, env *object.Environment) object.Object { result := &object.Array{ Elements: []object.Object{}, } @@ -62,6 +64,9 @@ func evalArray(array *ast.Array, env *object.Environment) *object.Array { if isError(evaluated) { return &object.Array{Elements: []object.Object{evaluated}} } + if returnValue, ok := evaluated.(*object.ReturnValue); ok { + return returnValue + } result.Elements = append(result.Elements, evaluated) } @@ -131,6 +136,16 @@ func evalKeyValueObject(kv *ast.KeyValueObject, env *object.Environment) object. return evalLoopExpression(value, env) case "lambda": return evalLambdaExpression(value, env) + case "break": + return Break + case "continue": + return Continue + case "return": + evaluated := Eval(value, env) + if isError(evaluated) { + return evaluated + } + return &object.ReturnValue{Value: evaluated} } } @@ -225,12 +240,22 @@ func applyFunction(function object.Object, args object.Object) object.Object { return newError("failed to apply function: %s", err) } - return Eval(funcType.Body, extendedEnv) + evaluated := Eval(funcType.Body, extendedEnv) + return unwrapReturnValue(evaluated) default: return newError("not a function: %s", function.Type()) } } +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + fmt.Println("returnValue.Value", returnValue.Value) + return returnValue.Value + } + + return obj +} + func evalIfExpression(exp ast.Expression, env *object.Environment) object.Object { keyValueObj, ok := exp.(*ast.KeyValueObject) if !ok { @@ -384,6 +409,14 @@ func evalFromUntilLoop(keyValueObj *ast.KeyValueObject, env *object.Environment) return evaluated } + if evaluated == Break { + break + } else if evaluated == Continue { + continue + } else if returnValue, ok := evaluated.(*object.ReturnValue); ok { + return returnValue + } + result = evaluated } @@ -462,6 +495,14 @@ func evalInLoop(keyValueObj *ast.KeyValueObject, env *object.Environment) object return evaluated } + if evaluated == Break { + break + } else if evaluated == Continue { + continue + } else if returnValue, ok := evaluated.(*object.ReturnValue); ok { + return returnValue + } + result = evaluated } From 151dbf565d74764abe8a2bdfcf1df725682dc630 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Tue, 15 Oct 2024 01:43:39 +0900 Subject: [PATCH 5/5] add examples (#57) --- examples/func_return.json | 55 +++++++++++++++++++++++ examples/loop_break_continue.jsop.json | 61 ++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 examples/func_return.json create mode 100644 examples/loop_break_continue.jsop.json diff --git a/examples/func_return.json b/examples/func_return.json new file mode 100644 index 0000000..463689d --- /dev/null +++ b/examples/func_return.json @@ -0,0 +1,55 @@ +[ + { + "set": { + "var": "$f", + "val": { + "lambda": { + "body": [ + { + "set": { + "var": "$sum", + "val": 0 + } + }, + { + "loop": { + "for": "$i", + "from": 1, + "until": 11, + "do": { + "if": { + "cond": { + "command": { + "symbol": ">", + "args": ["$i", 5] + } + }, + "conseq": { + "return": "$sum" + }, + "alt": { + "set": { + "var": "$sum", + "val": { + "command": { + "symbol": "+", + "args": ["$sum", "$i"] + } + } + } + } + } + } + } + } + ] + } + } + } + }, + { + "command": { + "symbol": "$f" + } + } +] \ No newline at end of file diff --git a/examples/loop_break_continue.jsop.json b/examples/loop_break_continue.jsop.json new file mode 100644 index 0000000..9914b1c --- /dev/null +++ b/examples/loop_break_continue.jsop.json @@ -0,0 +1,61 @@ +[ + { + "set": { + "var": "$sum", + "val": 0 + } + }, + { + "loop": { + "for": "$i", + "from": 1, + "until": 15, + "do": { + "if": { + "cond": { + "command": { + "symbol": ">", + "args": ["$i", 10] + } + }, + "conseq": { + "break": {} + }, + "alt": { + "if": { + "cond": { + "command": { + "symbol": "==", + "args": [ + { + "command": { + "symbol": "%", + "args": ["$i", 2] + } + }, + 0 + ] + } + }, + "conseq": { + "set": { + "var": "$sum", + "val": { + "command": { + "symbol": "+", + "args": ["$sum", "$i"] + } + } + } + }, + "alt": { + "continue": {} + } + } + } + } + } + } + }, + "$sum" +] \ No newline at end of file