Repository: skx/monkey (go)

Issue:
iteration.mon : wrong hash key/value
When I execute iteration.mon, I got wrong :

_Hash: key
        Foo
        Steve
Hash: key/value
        Bar     =>      Foo
        Bar     =>      Foo_

When I rewrite script to:
_h = { "Foo": "Bar", "Steve": "Kemp", "Name": "Wade" }
puts( "Hash: key\n")
foreach key in h {
     puts( "\t", key, "\n");
}

hh = { "Foo": "Bar", "Steve": "Kemp", "Name": "Wade" }
puts( "Hash: key/value\n")
foreach key,val in hh {
     puts( "\t", key, "\t=>\t", val, "\n");
}_

I got:
_Hash: key
        Steve
        Foo
        Steve
Hash: key/value
        Bar     =>      Foo
        Kemp    =>      Steve
        Wade    =>      Name_


Pull request description:
Correct the iteration of key/values of hashes
This commit adds a test-case which checks that the iteration over hash keys returns the correct results.  It seems that the value varies over runs - so repeating the test ten times is likely sufficient to trigger the issue.

Once this bug is fixed this will close #90.

Patch:
diff --git a/.github/build b/.github/build
index ea779ca..ecdc823 100755
--- a/.github/build
+++ b/.github/build
@@ -3,6 +3,10 @@
 # The basename of our binary
 BASE="monkey"
 
+
+# I don't even ..
+go env -w GOFLAGS="-buildvcs=false"
+
 #
 # We build on multiple platforms/archs
 #
diff --git a/object/object_hash.go b/object/object_hash.go
index c05cc18..fbfca82 100644
--- a/object/object_hash.go
+++ b/object/object_hash.go
@@ -102,10 +102,36 @@ func (h *Hash) Next() (Object, Object, bool) {
 	if h.offset < len(h.Pairs) {
 		idx := 0
 
-		for _, pair := range h.Pairs {
+		//
+		// Bug: #90
+		//
+		// Using "range" to iterate over a map will
+		// return the values in a random order.
+		//
+		// We need to sort the keys, that will give us
+		// a standard order.
+		//
+		//  x -> sorted keys
+		//  y -> key/val map, for simplicity
+		//
+		x := []Object{}
+		y := make(map[Object]Object)
+		// x is now the keys
+		for _, ent := range h.Pairs {
+			x = append(x, ent.Key)
+			y[ent.Key] = ent.Value
+		}
+
+		// sort the keys
+		sort.Slice(x, func(i, j int) bool {
+			return x[i].Inspect() < x[j].Inspect()
+		})
+
+		// Now range over the keys
+		for _, key := range x {
 			if h.offset == idx {
 				h.offset++
-				return pair.Key, pair.Value, true
+				return key, y[key], true
 			}
 			idx++
 		}


Test patch:
diff --git a/.github/run-tests.sh b/.github/run-tests.sh
index 676774e..80d1be1 100755
--- a/.github/run-tests.sh
+++ b/.github/run-tests.sh
@@ -1,8 +1,12 @@
 #!/bin/sh
 
+
+# I don't even ..
+go env -w GOFLAGS="-buildvcs=false"
+
 # Install the lint-tool, and the shadow-tool
-go get -u golang.org/x/lint/golint
-go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
+go install golang.org/x/lint/golint@latest
+go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
 
 # At this point failures cause aborts
 set -e
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 62afd12..454e4b0 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -55,6 +55,11 @@ func testEval(input string) object.Object {
 	p := parser.New(l)
 	program := p.ParseProgram()
 	env := object.NewEnvironment()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
+	defer cancel()
+	SetContext(ctx)
+
 	return Eval(program, env)
 }
 
@@ -654,3 +659,32 @@ for ( true ) {
 	}
 
 }
+
+// Test90 tests hash-key iteration, which was reported in #90
+func Test90(t *testing.T) {
+	input := `
+a = { 1: "one", 2: "two", 3: "three" }
+total = 0
+foreach key in a {
+    total += key
+}
+return total;
+`
+	count := 0
+
+	for count < 10 {
+
+		evaluated := testEval(input)
+		result, ok := evaluated.(*object.Integer)
+		if !ok {
+			t.Fatalf("Eval did't return number. got=%T(%+v)",
+				evaluated, evaluated)
+		}
+		if result.Value != 6 {
+			t.Fatalf("key iteration %d resulted in %d != 6", count, result)
+		}
+
+		count++
+	}
+
+}