/* --------------------------------------------------------- * * __________ D E L T A S C R I P T * * (_________() * * / === / - A fast, dynamic scripting language * * | == | - Version 4.13.11.0 * * / === / - Developed by Adam R. Nelson * * | = = | - 2011-2013 * * / === / - Distributed under GNU LGPL v3 * * (________() - http://github.com/ar-nelson/deltascript * * * * --------------------------------------------------------- */ package com.sector91.delta.script.objects; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.sector91.delta.script.DScriptErr; import com.sector91.delta.script.DeltaScript; import com.sector91.delta.script.NumberTypes; import com.sector91.delta.script.Operator; import com.sector91.delta.script.annotations.DSDynamicField; import com.sector91.delta.script.annotations.DSInaccessible; import com.sector91.delta.script.annotations.DSName; import com.sector91.delta.script.annotations.DSType; import com.sector91.delta.script.objects.reflect.DS_JavaClass; /** * <p>A range of numbers, which is treated as a sequence and can be iterated * over.</p> * * <p>Ranges are defined with the {@code to} operator (ex: {@code 1 to 5}) or * the {@code until} operator (ex: {@code 1 until 5}). Ranges are immutable, and * support most basic sequence operations (indexing, slices, etc).</p> * * @author Adam R. Nelson * @version 4.13.11.0 */ @DSType("Range") public class DS_Range extends DS_AbstractObject implements DS_Iterable, DS_Indexable, DS_Numeric, Serializable { private static final long serialVersionUID = DeltaScript.VERSION.majorVersion(); private static final DS_Scalar DEFAULT_STEP = ScalarFactory.fromInt(1); public static final String TYPE_NAME = "Range"; private static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass( DS_Range.class); private final int nType; private final DS_Scalar upperBound, lowerBound, step; private final boolean inclusive; public DS_Range(DS_Scalar lowerBound, DS_Scalar upperBound, boolean inclusive) {this(lowerBound, upperBound, DEFAULT_STEP, inclusive);} @DSInaccessible public DS_Range(DS_Scalar lowerBound, DS_Scalar upperBound, boolean inclusive, int nType) {this(lowerBound, upperBound, DEFAULT_STEP, inclusive, nType);} public DS_Range(DS_Scalar lowerBound, DS_Scalar upperBound, DS_Scalar step, boolean inclusive) { this(lowerBound, upperBound, step, inclusive, upperBound.getNumberType() | lowerBound.getNumberType() | step.getNumberType()); } @DSInaccessible public DS_Range(DS_Scalar lowerBound, DS_Scalar upperBound, DS_Scalar step, boolean inclusive, int nType) { this.nType = nType; if (step.signum() != 1) throw new IllegalArgumentException("A range's step must be" + " positive and nonzero."); if (upperBound.compareTo(lowerBound) >= 0) { this.upperBound = upperBound.castTo(nType); this.lowerBound = lowerBound.castTo(nType); this.step = step.castTo(nType); } else { this.upperBound = lowerBound.castTo(nType); this.lowerBound = upperBound.castTo(nType); this.step = step.negate().castTo(nType); } this.inclusive = inclusive; } /** * <p>Returns the number at which this range starts.</p> * * @return The number at which this range starts. */ @DSName({"lowerBound", "start", "min"}) @DSDynamicField public DS_Scalar lowerBound() {return lowerBound;} /** * <p>Returns the number at which this range ends.</p> * * @return The number at which this range ends. */ @DSName({"upperBound", "end", "max"}) @DSDynamicField public DS_Scalar upperBound() {return upperBound;} /** * <p>Returns the amount by which this range increments every * iteration.</p> * * @return The amount by which this range increments every iteration. */ @DSName("step") @DSDynamicField public DS_Scalar step() {return step;} @DSName("span") @DSDynamicField public DS_Scalar span() {return ScalarFactory.sub(upperBound, lowerBound, nType);} @DSInaccessible public DS_Scalar random() {return ScalarFactory.add(lowerBound, span().random(), nType);} @DSName("clamp") public DS_Scalar clamp(DS_Scalar num) { if (num.compareTo(upperBound) > 0) return upperBound; else if (num.compareTo(lowerBound) < 0) return lowerBound; return num; } // DS_Sequence Methods // ---------------------------------------------------- @DSName("size") @DSDynamicField public DS_Integer realSize() { final DS_Scalar div = ScalarFactory.div(span(), step.absolute(), nType); final DS_Integer size = div.floor(); if (inclusive || !div.equals(size)) return size.increment(1); return size; } @DSName("iterSize") @DSDynamicField public int size() { // TODO: Handle int overflow for size(). return realSize().intValue(); } @DSName("empty") @DSDynamicField public boolean isEmpty() {return !inclusive && upperBound.equals(lowerBound);} @DSName("contains") public boolean contains(DS_Object o) { if (o instanceof DS_Scalar) { DS_Scalar n = (DS_Scalar)o; if (n.compareTo(lowerBound) >= 0) { final int ucmp = n.compareTo(upperBound); if (ucmp < 0 || (inclusive && ucmp == 0)) return true; } return false; } else if (o instanceof DS_Range) { DS_Range r = (DS_Range)o; if (r.lowerBound.compareTo(lowerBound) >= 0) { final int ucmp = r.upperBound.compareTo(upperBound); if (ucmp < 0 || ((inclusive || !r.inclusive) && ucmp == 0)) return true; } return false; } else return false; } @DSInaccessible public Iterator<DS_Object> iterator() { switch (nType) { case NumberTypes.SHORT_INT: return new ShortIntRangeIterator(); case NumberTypes.LONG_INT: return new LongIntRangeIterator(); case NumberTypes.SHORT_FLOAT: return new ShortFloatRangeIterator(); case NumberTypes.LONG_FLOAT: return new LongFloatRangeIterator(); case NumberTypes.REAL_DECIMAL: case NumberTypes.REAL_INT: throw new UnsupportedOperationException("This range uses a large" + " number type (REAL or REAL_INT) for which iteration is not" + " yet supported."); default: throw new IllegalStateException(String.format("This range has an" + " invalid or unsupported number type (0x%x)", nType)); } } @DSInaccessible public DS_Object[] dumpToArray() { final List<DS_Object> list = new ArrayList<DS_Object>(); final Iterator<DS_Object> iter = iterator(); while (iter.hasNext()) list.add(iter.next()); return list.toArray(new DS_Object[list.size()]); } @DSName("each") public void each(DS_Callable func) throws DScriptErr { final Iterator<DS_Object> iter = iterator(); while (iter.hasNext()) func.call(iter.next()); } @DSInaccessible public DS_Object getIndex(DS_Object index) throws DScriptErr { if (index instanceof DS_Scalar) { final DS_Scalar s = (DS_Scalar)index; final int t = nType | s.getNumberType(); final DS_Scalar item; if (s.signum() >= 0) item = ScalarFactory.add( lowerBound, ScalarFactory.mul(step, s, t), t); else { if (s.isIntegral()) { final DS_Scalar s2 = ScalarFactory.add(realSize(), s, t); item = ScalarFactory.add( lowerBound, ScalarFactory.mul(step, s2, t), t); } else item = ScalarFactory.add( upperBound, ScalarFactory.mul(step, s, t), t); } if (!contains(item)) throw new DScriptErr(TYPE_NAME + " index out of bounds: " + index); return item; } else throw new DScriptErr(TYPE_NAME + " index must be a Scalar."); } @DSInaccessible public DS_Object setIndex(DS_Object index, DS_Object value) throws DScriptErr { throw new DScriptErr(TYPE_NAME + " objects are immutable.", DScriptErr.T_IMMUTABLE); } @DSInaccessible public int getNumberType() {return nType;} @DSInaccessible public boolean isIntegral() { return nType != NumberTypes.SHORT_FLOAT && nType != NumberTypes.LONG_FLOAT && nType != NumberTypes.REAL_DECIMAL; } @DSInaccessible public DS_Numeric castTo(int nType) { if (nType == this.nType) return this; return new DS_Range(upperBound, lowerBound, step, inclusive, nType); } // DS_Object Methods // ---------------------------------------------------- public DS_Range unbox() {return this;} @Override public boolean booleanValue() {return lowerBound != upperBound;} public String getTypeName() {return TYPE_NAME;} @Override protected DS_JavaClass getDeltaScriptClass() {return DSCLASS;} @SuppressWarnings("incomplete-switch") @Override public DS_Object operator(Operator op, DS_Object other) throws DScriptErr { switch (op) { case RANGE_STEP: if (other instanceof DS_Scalar) { if (step.signum() > 0) return new DS_Range(lowerBound, upperBound,(DS_Scalar)other, inclusive, nType | ((DS_Scalar)other).getNumberType()); else return new DS_Range(upperBound, lowerBound,(DS_Scalar)other, inclusive, nType | ((DS_Scalar)other).getNumberType()); } else throw new DScriptErr("The \"" + op.str + "\" operator" + " must be followed by a Scalar.", DScriptErr.T_INVALID_TYPE, DScriptErr.T_NOT_NUMBER); case IN: return DS_Boolean.box(contains(other)); case UNARY_MINUS: return new DS_Range(lowerBound, upperBound, step.negate(), inclusive, nType); case RANDOM: return random(); } return super.operator(op, other); } @Override public String toString() { return lowerBound + (inclusive ? " to " : " until ") + upperBound + (step.intValue()!=1 ? " by "+step : ""); } public boolean equals(DS_Object other) { if (other instanceof DS_Range) { if (this == other) return true; return ((DS_Range)other).lowerBound.equals(lowerBound) && ((DS_Range)other).upperBound.equals(upperBound) && ((DS_Range)other).step.equals(step) && ((DS_Range)other).inclusive == inclusive; } else return false; } // Range Iterator Subclasses // ---------------------------------------------------- protected abstract class RangeIterator implements Iterator<DS_Object> { protected boolean done; public boolean hasNext() {return !done;} public final void remove() {throw new UnsupportedOperationException("Unsupported.");} } protected class ShortIntRangeIterator extends RangeIterator { private final int start, end, step; private int next; ShortIntRangeIterator() { step = DS_Range.this.step.intValue(); if (step > 0) { start = lowerBound.intValue(); end = upperBound.intValue(); } else { start = upperBound.intValue(); end = lowerBound.intValue(); } next = start; } public DS_Scalar next() { if (done) throw new RuntimeException("Range iteration finished."); DS_Scalar ret = new DS_ShortIntScalar(next); next += step; done = checkIfDone(); return ret; } private boolean checkIfDone() { if (inclusive) return step>0 ? next > end : next < end; else return step>0 ? next >= end : next <= end; } } protected class LongIntRangeIterator extends RangeIterator { private final long start, end, step; private long next; LongIntRangeIterator() { step = DS_Range.this.step.longValue(); if (step > 0) { start = lowerBound.longValue(); end = upperBound.longValue(); } else { start = upperBound.longValue(); end = lowerBound.longValue(); } next = start; } public DS_Scalar next() { if (done) throw new RuntimeException("Range iteration finished."); DS_Scalar ret = new DS_LongIntScalar(next); next += step; done = checkIfDone(); return ret; } private boolean checkIfDone() { if (inclusive) return step>0 ? next > end : next < end; else return step>0 ? next >= end : next <= end; } } // TODO: Add BigInteger iterator support. protected class ShortFloatRangeIterator extends RangeIterator { private final float start, end, step; private float next; ShortFloatRangeIterator() { step = DS_Range.this.step.floatValue(); if (step > 0) { start = lowerBound.floatValue(); end = upperBound.floatValue(); } else { start = upperBound.floatValue(); end = lowerBound.floatValue(); } next = start; } public DS_Scalar next() { if (done) throw new RuntimeException("Range iteration finished."); DS_Scalar ret = new DS_ShortFloatScalar(next); next += step; done = checkIfDone(); return ret; } private boolean checkIfDone() { if (inclusive) return step>0 ? next > end : next < end; else return step>0 ? next >= end : next <= end; } } protected class LongFloatRangeIterator extends RangeIterator { private final double start, end, step; private double next; LongFloatRangeIterator() { step = DS_Range.this.step.doubleValue(); if (step > 0) { start = lowerBound.doubleValue(); end = upperBound.doubleValue(); } else { start = upperBound.doubleValue(); end = lowerBound.doubleValue(); } next = start; } public DS_Scalar next() { if (done) throw new RuntimeException("Range iteration finished."); DS_Scalar ret = new DS_LongFloatScalar(next); next += step; done = checkIfDone(); return ret; } private boolean checkIfDone() { if (inclusive) return step>0 ? next > end : next < end; else return step>0 ? next >= end : next <= end; } } // TODO: Add BigDecimal iterator support. }