diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 721c9c4..04a9db0 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -376,6 +376,8 @@ func unwrapReturnValue(obj object.Object) object.Object { func evalIndexExpression(left, index object.Object) object.Object { switch { + case left.Type() == object.STRING_OBJ && index.Type() == object.INTEGER_OBJ: + return evalStringIndexExpression(left, index) case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: return evalArrayIndexExpression(left, index) case left.Type() == object.HASH_OBJ: @@ -385,6 +387,18 @@ func evalIndexExpression(left, index object.Object) object.Object { } } +func evalStringIndexExpression(str, index object.Object) object.Object { + stringObject := str.(*object.String) + idx := index.(*object.Integer).Value + max := int64((len(stringObject.Value)) - 1) + + if idx < 0 || idx > max { + return &object.String{Value: ""} + } + + return &object.String{Value: string(stringObject.Value[idx])} +} + func evalHashIndexExpression(hash, index object.Object) object.Object { hashObject := hash.(*object.Hash) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 484701e..342b157 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -647,3 +647,53 @@ func TestExamples(t *testing.T) { testEval(string(b)) } } + +func TestStringIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `"abc"[0]`, + "a", + }, + { + `"abc"[1]`, + "b", + }, + { + `"abc"[2]`, + "c", + }, + { + `let i = 0; "abc"[i];`, + "a", + }, + { + `"abc"[1 + 1];`, + "c", + }, + { + `let myString = "abc"; myString[0] + myString[1] + myString[2];`, + "abc", + }, + { + `"abc"[3]`, + "", + }, + { + `"foo"[-1]`, + "", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + str, ok := tt.expected.(string) + if ok { + testStringObject(t, evaluated, string(str)) + } else { + testNullObject(t, evaluated) + } + } +} diff --git a/vm/vm.go b/vm/vm.go index 0c093be..7bf2549 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -297,6 +297,8 @@ func (vm *VM) Run() error { func (vm *VM) executeIndexExpressions(left, index object.Object) error { switch { + case left.Type() == object.STRING_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeStringIndex(left, index) case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: return vm.executeArrayIndex(left, index) case left.Type() == object.HASH_OBJ: @@ -567,6 +569,18 @@ func (vm *VM) pushClosure(constIndex, numFree int) error { return vm.push(closure) } +func (vm *VM) executeStringIndex(str, index object.Object) error { + stringObject := str.(*object.String) + i := index.(*object.Integer).Value + max := int64(len(stringObject.Value) - 1) + + if i < 0 || i > max { + return vm.push(&object.String{Value: ""}) + } + + return vm.push(&object.String{Value: string(stringObject.Value[i])}) +} + func nativeBoolToBooleanObject(input bool) *object.Boolean { if input { return True diff --git a/vm/vm_test.go b/vm/vm_test.go index 30266c6..c39fbe4 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -317,6 +317,11 @@ func TestIndexExpressions(t *testing.T) { {"{1: 1, 2: 2}[2]", 2}, {"{1: 1}[0]", Null}, {"{}[0]", Null}, + {`"abc"[0]`, "a"}, + {`"abc"[1]`, "b"}, + {`"abc"[2]`, "c"}, + {`"abc"[3]`, ""}, + {`"abc"[-1]`, ""}, } runVmTests(t, tests)