diff --git a/src/com/annimon/ownlang/lib/ArrayValue.java b/src/com/annimon/ownlang/lib/ArrayValue.java index f1d1509..81a3448 100644 --- a/src/com/annimon/ownlang/lib/ArrayValue.java +++ b/src/com/annimon/ownlang/lib/ArrayValue.java @@ -6,10 +6,11 @@ import java.util.Iterator; import java.util.List; /** - * + * Represents array type. + * * @author aNNiMON */ -public final class ArrayValue implements Value, Iterable { +public class ArrayValue implements Value, Iterable { public static ArrayValue of(byte[] array) { final int size = array.length; diff --git a/src/com/annimon/ownlang/lib/modules/functions/std_range.java b/src/com/annimon/ownlang/lib/modules/functions/std_range.java new file mode 100644 index 0000000..5cb17be --- /dev/null +++ b/src/com/annimon/ownlang/lib/modules/functions/std_range.java @@ -0,0 +1,218 @@ +package com.annimon.ownlang.lib.modules.functions; + +import com.annimon.ownlang.lib.*; +import java.util.Iterator; + +public final class std_range implements Function { + + @Override + public Value execute(Value... args) { + Arguments.checkRange(1, 3, args.length); + + final long from, to, step; + switch (args.length) { + default: + case 1: + from = 0; + to = getLong(args[0]); + step = 1; + break; + case 2: + from = getLong(args[0]); + to = getLong(args[1]); + step = 1; + break; + case 3: + from = getLong(args[0]); + to = getLong(args[1]); + step = getLong(args[2]); + break; + } + return RangeValue.of(from, to, step); + } + + private static long getLong(Value v) { + if (v.type() == Types.NUMBER) { + return ((NumberValue) v).asLong(); + } + return v.asInt(); + } + + private static class RangeValue extends ArrayValue { + + public static ArrayValue of(long from, long to, long step) { + boolean isInvalid = false; + isInvalid = isInvalid || (step == 0); + isInvalid = isInvalid || ((step > 0) && (from >= to)); + isInvalid = isInvalid || ((step < 0) && (to >= from)); + if (isInvalid) return new ArrayValue(0); + return new RangeValue(from, to, step); + } + + private final long from, to, step; + private final int size; + + public RangeValue(long from, long to, long step) { + super(new Value[0]); + this.from = from; + this.to = to; + this.step = step; + // x = range(0, 10, 2) + // 0, 2, 4, 6, 8 | 0..10 step 2 + // 0, 3, 6, 9 | 0..10 step 3 + // 0, 4, 8 | 0..10 step 4 + final long base = (from < to) ? (to - from) : (from - to); + final long absStep = (step < 0) ? -step : step; + this.size = (int) (base / absStep + (base % absStep == 0 ? 0 : 1)); + } + + @Override + public Value[] getCopyElements() { + final Value[] result = new Value[size]; + int i = 0; + if (isIntegerRange()) { + final int toInt = (int) to; + final int stepInt = (int) step; + for (int value = (int) from; value < toInt; value += stepInt) { + result[i++] = NumberValue.of(value); + } + } else { + for (long value = from; value < to; value += step) { + result[i++] = NumberValue.of(value); + } + } + return result; + } + + private boolean isIntegerRange() { + if (to > 0) { + return (to < Integer.MAX_VALUE) && (from > Integer.MIN_VALUE && to < Integer.MAX_VALUE); + } + return (to > Integer.MIN_VALUE) && (to > Integer.MIN_VALUE && from < Integer.MAX_VALUE); + } + + @Override + public int size() { + return size; + } + + @Override + public Value get(int index) { + if (isIntegerRange()) { + return NumberValue.of((int) (from + index * step)); + } + return NumberValue.of(from + (long) index * step); + } + + @Override + public void set(int index, Value value) { + // not implemented + } + + @Override + public Object raw() { + return getCopyElements(); + } + + @Override + public String asString() { + if (size == 0) return "[]"; + + final StringBuilder sb = new StringBuilder(); + sb.append('[').append(from); + for (long value = from + step; value < to; value += step) { + sb.append(", ").append(value); + } + sb.append(']'); + return sb.toString(); + } + + @Override + public Iterator iterator() { + if (isIntegerRange()) { + final int toInt = (int) to; + final int stepInt = (int) step; + return new Iterator() { + + int value = (int) from; + + @Override + public boolean hasNext() { + return value < toInt; + } + + @Override + public Value next() { + final int result = value; + value += stepInt; + return NumberValue.of(result); + } + }; + } + return new Iterator() { + + long value = from; + + @Override + public boolean hasNext() { + return value < to; + } + + @Override + public Value next() { + final long result = value; + value += step; + return NumberValue.of(result); + } + }; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 59 * hash + (int) (this.from ^ (this.from >>> 32)); + hash = 59 * hash + (int) (this.to ^ (this.to >>> 32)); + hash = 59 * hash + (int) (this.step ^ (this.step >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) + return false; + final RangeValue other = (RangeValue) obj; + if (this.from != other.from) return false; + if (this.to != other.to) return false; + if (this.step != other.step) return false; + return true; + } + + @Override + public int compareTo(Value o) { + if (o.type() == Types.ARRAY) { + final int lengthCompare = Integer.compare(size(), ((ArrayValue) o).size()); + if (lengthCompare != 0) return lengthCompare; + + if (o instanceof RangeValue) { + final RangeValue o2 = ((RangeValue) o); + int compareResult; + compareResult = Long.compare(this.from, o2.from); + if (compareResult != 0) return compareResult; + compareResult = Long.compare(this.to, o2.to); + if (compareResult != 0) return compareResult; + } + } + return asString().compareTo(o.asString()); + } + + @Override + public String toString() { + if (step == 1) { + return String.format("range(%d, %d)", from, to); + } + return String.format("range(%d, %d, %d)", from, to, step); + } + } +} \ No newline at end of file diff --git a/src/com/annimon/ownlang/lib/modules/std.java b/src/com/annimon/ownlang/lib/modules/std.java index 2e5529d..ed4b046 100644 --- a/src/com/annimon/ownlang/lib/modules/std.java +++ b/src/com/annimon/ownlang/lib/modules/std.java @@ -47,5 +47,6 @@ public final class std implements Module { Functions.set("arrayKeyExists", new std_arrayKeyExists()); Functions.set("arrayKeys", new std_arrayKeys()); Functions.set("arrayValues", new std_arrayValues()); + Functions.set("range", new std_range()); } } diff --git a/test/resources/modules/std/range.own b/test/resources/modules/std/range.own new file mode 100644 index 0000000..d1e13fe --- /dev/null +++ b/test/resources/modules/std/range.own @@ -0,0 +1,94 @@ +use "std" +use "types" +use "functional" + +def testRangeParams() { + x = range(10) + assertEquals(10, length(x)) + assertEquals(0, x[0]) + assertEquals(9, x[9]) +} + +def testRangeParamsFromTo() { + x = range(4, 10) + assertEquals(6, length(x)) + assertEquals(4, x[0]) + assertEquals(9, x[5]) +} + +def testRangeParamsWithStep() { + x = range(4, 10, 2) + assertEquals(3, length(x)) + assertEquals(4, x[0]) + assertEquals(8, x[2]) +} + +def testRangeParamsReversed() { + x = range(10, 4, -1) + assertEquals(6, length(x)) + assertEquals(10, x[0]) + assertEquals(5, x[5]) +} + +def testRangeLength() { + assertEquals(10, length(range(0, 10, 1))) + assertEquals(5, length(range(0, 10, 2))) + assertEquals(4, length(range(0, 10, 3))) + assertEquals(3, length(range(0, 10, 4))) + assertEquals(2, length(range(0, 10, 5))) + assertEquals(1, length(range(0, 10, 15))) +} + +def testRangeReversedLength() { + assertEquals(10, length(range(10, 0, -1))) + assertEquals(5, length(range(10, 0, -2))) + assertEquals(4, length(range(10, 0, -3))) + assertEquals(3, length(range(10, 0, -4))) + assertEquals(2, length(range(10, 0, -5))) + assertEquals(1, length(range(10, 0, -15))) +} + +def testRangeHuge() { + x = range(-1002003004005006007, 1002003004005006007) + assertEquals(-1002003004005006000, x[7]) + + x = range(-1002003004005006007, 1002003004005006007, 100000000000) + assertEquals(20040061, length(x)) + + x = range(-10000000, 20000000) + assertEquals(30000000, length(x)) + assertEquals(0, x[10000000]) +} + +def testRangeAsString() { + x = range(1, 6) + assertEquals("[1, 2, 3, 4, 5]", string(x)) + + x = range(1, 6, 2) + assertEquals("[1, 3, 5]", string(x)) + assertEquals(string([1,3,5]), string(x)) +} + +def testRangeIterate() { + sum = 0 + for x : range(5) { + sum += x + } + assertEquals(10, sum) +} + +def testRangeFunctionalForeach() { + i = 0 + foreach(range(5), def(x) = assertEquals(i++, x)) +} + +def testRangeFunctionalReduce() { + sum = reduce(range(5), 0, def(x, y) = x + y) + assertEquals(10, sum) +} + +def testRangeInvalid() { + assertEquals(0, length(range(-100))) + assertEquals(0, length(range(0, -100, 1))) + assertEquals(0, length(range(0, 100, 0))) +} \ No newline at end of file