Repository: skx/monkey (go)

Issue:
Encountered a panic when attempting to retrieve an index value from a string
I encountered a panic when attempting to retrieve an index value from a string.

```
let i = "index";
puts(i["d"])
```

```
panic: interface conversion: object.Object is *object.String, not *object.Integer

goroutine 1 [running]:
github.com/skx/monkey/evaluator.evalStringIndexExpression(...)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:1157
github.com/skx/monkey/evaluator.evalIndexExpression({0x113d918, 0xc000010048?}, {0x113d918?, 0xc000010060})
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:1126 +0x47f
github.com/skx/monkey/evaluator.EvalContext({0x113d800, 0x1227780}, {0x113d0b0?, 0xc000078080?}, 0xc00006e2d0)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:184 +0x13fd
github.com/skx/monkey/evaluator.evalExpression({0x113d800, 0x1227780}, {0xc0000143d0?, 0x1, 0xc00006e2d0?}, 0x101480b?)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:1000 +0xf2
github.com/skx/monkey/evaluator.EvalContext({0x113d800, 0x1227780}, {0x113cfe8?, 0xc00006a0a0?}, 0xc00006e2d0)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:149 +0xe52
github.com/skx/monkey/evaluator.EvalContext({0x113d800, 0x1227780}, {0x113ccf0?, 0xc00006e3f0?}, 0xc00006e2d0)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:55 +0x11b
github.com/skx/monkey/evaluator.evalProgram({0x113d800, 0x1227780}, 0x1?, 0x0?)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:961 +0xac
github.com/skx/monkey/evaluator.EvalContext({0x113d800, 0x1227780}, {0x113cca0?, 0xc000010030?}, 0xc00006e2d0)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:53 +0xba
github.com/skx/monkey/evaluator.Eval(...)
        expr/staging/src/github.com/skx/monkey/evaluator/evaluator.go:32
main.main()
        expr/main.go:24 +0x1bf
exit status 2

```

Pull request description:
Ensure we have integer for string index-expression.
THis closes #102, by ensuring that the string-index expression uses an integer value - it also returns an error when out of bounds.

Tests added.

Patch:
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 064f565..0918196 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -1154,17 +1154,22 @@ func evalHashIndexExpression(hash, index object.Object) object.Object {
 
 func evalStringIndexExpression(input, index object.Object) object.Object {
 	str := input.(*object.String).Value
-	idx := index.(*object.Integer).Value
+	idx, isInt := index.(*object.Integer)
+	if !isInt {
+		return newError("expected an integer for string index, got something else")
+	}
+
+	i := idx.Value
 	max := int64(len(str))
-	if idx < 0 || idx > max {
-		return NULL
+	if i < 0 || i > max {
+		return newError("index out of bounds")
 	}
 
 	// Get the characters as an array of runes
 	chars := []rune(str)
 
 	// Now index
-	ret := chars[idx]
+	ret := chars[i]
 
 	// And return as a string.
 	return &object.String{Value: string(ret)}


Test patch:
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index deb3538..a741cd2 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -458,40 +458,66 @@ func TestStringIndexExpression(t *testing.T) {
 	tests := []struct {
 		input    string
 		expected interface{}
+		err      bool
 	}{
 		{
 			"\"Steve\"[0]",
 			"S",
+			false,
 		},
 		{
 			"\"Steve\"[1]",
 			"t",
+			false,
 		},
 		{
 			"\"Steve\"[101]",
 			nil,
+			true,
 		},
 		{
 			"\"Steve\"[-1]",
 			nil,
+			true,
 		},
 		{
 			"\"狐犬\"[0]",
 			"狐",
+			false,
 		},
 		{
 			"\"狐犬\"[1]",
 			"犬",
+			false,
+		},
+		{
+			"\"狐犬\"[\"x\"]",
+			"",
+			true,
+		},
+		{
+			"\"狐犬\"[-3]",
+			"",
+			true,
 		},
 	}
 	for _, tt := range tests {
 		evaluated := testEval(tt.input)
 
-		str, ok := tt.expected.(string)
-		if ok {
-			testStringObject(t, evaluated, str)
+		_, err := evaluated.(*object.Error)
+
+		if err {
+			if tt.err == false {
+				t.Fatalf("expected error for input '%s', got %T", tt.input, evaluated)
+			}
 		} else {
-			testNullObject(t, evaluated)
+			str, ok := tt.expected.(string)
+			if ok {
+				testStringObject(t, evaluated, str)
+			} else {
+
+				testNullObject(t, evaluated)
+			}
 		}
 	}
 }