Repository: skx/monkey (go)

Issue:
Range operator assumes increasing direction
This works:

     puts( 1..5 )

This does not return what you expect:

     puts(10..0)

Instead of "10, 9,.." you get "10, 11, 12 ."

We should be able to count down as appropriate.

Pull request description:
Fix the range-operator for "backwards" ranges.
This pull-request closes #98, by ensuring that the range operator (`..`) can count down as well as up.  This allows ranges such as:

* 3..10
* 4..0

However note that the first number cannot be negative, as that's then treated as "- [1, 2..]" - i.e. negative array, which makes no sense.  This should be resolved, but it is a bigger job.

Patch:
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index be122cf..5f9d372 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -448,12 +448,29 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje
 	case "!=":
 		return nativeBoolToBooleanObject(leftVal != rightVal)
 	case "..":
-		len := int(rightVal-leftVal) + 1
+		// The start and end might not be ascending, so the size
+		// will be the span
+		diff := float64(rightVal - leftVal)
+		len := int(math.Abs(diff)) + 1
+
+		// Step is generally +1, but if we're going to
+		// express the range "10..0" it will be -1 to allow
+		// us to count down via subtraction
+		var step int64
+		step = 1.0
+
+		if rightVal < leftVal {
+			step = -1.0
+		}
+
+		// Make an array to hold the return value
 		array := make([]object.Object, len)
+
+		// Now make the range of integers, counting via the step.
 		i := 0
 		for i < len {
 			array[i] = &object.Integer{Value: leftVal}
-			leftVal++
+			leftVal += step
 			i++
 		}
 		return &object.Array{Elements: array}


Test patch:
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 9febf82..deb3538 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -721,3 +721,27 @@ if (match( "+", name) ) { puts( "Hello\n" ); }
 	}
 
 }
+
+func TestRangeOperator(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		// normal
+		{"return 0..3;", "[0, 1, 2, 3]"},
+		{"return 0..1;", "[0, 1]"},
+		{"return 0..0;", "[0]"},
+		{"a = -3; b = 3; return a..b;", "[-3, -2, -1, 0, 1, 2, 3]"},
+		// reversed
+		{"return 3..0;", "[3, 2, 1, 0]"},
+		{"return 1..0;", "[1, 0]"},
+		{"return 0..0;", "[0]"},
+		{"return 3..-5;", "[3, 2, 1, 0, -1, -2, -3, -4, -5]"},
+	}
+	for _, tt := range tests {
+		evaluated := testEval(tt.input)
+		if tt.expected != evaluated.Inspect() {
+			t.Fatalf("unexpected output for range operator, got %s for input %s", evaluated.Inspect(), tt.input)
+		}
+	}
+}