mohayonao/SCScript

View on GitHub
src/sc/classlib/Collections/ArrayedCollection.js

Summary

Maintainability
F
1 wk
Test Coverage
SCScript.install(function(sc) {
  "use strict";

  require("./SequenceableCollection");

  var $ = sc.lang.$;
  var random  = sc.libs.random;
  var mathlib = sc.libs.mathlib;
  var $nil  = $.nil;
  var $int0 = $.int0;
  var $int1 = $.int1;

  sc.lang.klass.refine("ArrayedCollection", function(builder, _) {
    builder.addMethod("valueOf", function() {
      return this._.map(function(elem) {
        return elem.valueOf();
      });
    });

    builder.addMethod("__elem__", function(item) {
      return item || $nil;
    });

    function throwIfImmutable($this) {
      if ($this.__immutable) {
        throw new Error("Attempted write to immutable object.");
      }
    }

    builder.addClassMethod("newClear", {
      args: "indexedSize=0"
    }, function($indexedSize) {
      var $obj;
      var array, indexedSize, i;

      $obj = this.new();

      indexedSize = $indexedSize.__int__();
      array = new Array(indexedSize);
      for (i = 0; i < indexedSize; ++i) {
        array[i] = $obj.__elem__();
      }
      $obj._ = array;

      return $obj;
    });

    // TODO: implements indexedSize

    builder.addMethod("size", function() {
      return $.Integer(this._.length);
    });

    // TODO: implements maxSize

    builder.addMethod("swap", {
      args: "a; b"
    }, function($a, $b) {
      var raw = this._;
      var a, b, len, tmp;

      throwIfImmutable(this);

      a = $a.__int__();
      b = $b.__int__();
      len = raw.length;

      if (a < 0 || len <= a || b < 0 || len <= b) {
        throw new Error("out of index");
      }

      tmp = raw[b];
      raw[b] = raw[a];
      raw[a] = tmp;

      return this;
    });

    builder.addMethod("at", {
      args: "index"
    }, function($index) {
      var i;

      if (Array.isArray($index._)) {
        return $.Array($index._.map(function($index) {
          i = $index.__int__();
          if (i < 0 || this._.length <= i) {
            return $nil;
          }
          return this._[i];
        }, this));
      }

      i = $index.__int__();

      return this._[i] || $nil;
    });

    builder.addMethod("clipAt", {
      args: "index"
    }, function($index) {
      var i;

      if (Array.isArray($index._)) {
        return $.Array($index._.map(function($index) {
          i = mathlib.clipIndex($index.__int__(), this._.length);
          return this._[i];
        }, this));
      }

      i = mathlib.clipIndex($index.__int__(), this._.length);

      return this._[i];
    });

    builder.addMethod("wrapAt", {
      args: "index"
    }, function($index) {
      var i;

      if (Array.isArray($index._)) {
        return $.Array($index._.map(function($index) {
          var i = mathlib.wrapIndex($index.__int__(), this._.length);
          return this._[i];
        }, this));
      }

      i = mathlib.wrapIndex($index.__int__(), this._.length);

      return this._[i];
    });

    builder.addMethod("foldAt", {
      args: "index"
    }, function($index) {
      var i;

      if (Array.isArray($index._)) {
        return $.Array($index._.map(function($index) {
          var i = mathlib.foldIndex($index.__int__(), this._.length);
          return this._[i];
        }, this));
      }

      i = mathlib.foldIndex($index.__int__(), this._.length);

      return this._[i];
    });

    builder.addMethod("put", {
      args: "index; item"
    }, function($index, $item) {
      var i;

      throwIfImmutable(this);

      if (Array.isArray($index._)) {
        $index._.forEach(function($index) {
          var i = $index.__int__();
          if (i < 0 || this._.length <= i) {
            throw new Error("out of index");
          }
          this._[i] = this.__elem__($item);
        }, this);
      } else {
        i = $index.__int__();
        if (i < 0 || this._.length <= i) {
          throw new Error("out of index");
        }
        this._[i] = this.__elem__($item);
      }

      return this;
    });

    builder.addMethod("clipPut", {
      args: "index; item"
    }, function($index, $item) {
      throwIfImmutable(this);

      if (Array.isArray($index._)) {
        $index._.forEach(function($index) {
          this._[mathlib.clipIndex($index.__int__(), this._.length)] = this.__elem__($item);
        }, this);
      } else {
        this._[mathlib.clipIndex($index.__int__(), this._.length)] = this.__elem__($item);
      }

      return this;
    });

    builder.addMethod("wrapPut", {
      args: "index; item"
    }, function($index, $item) {
      throwIfImmutable(this);

      if (Array.isArray($index._)) {
        $index._.forEach(function($index) {
          this._[mathlib.wrapIndex($index.__int__(), this._.length)] = this.__elem__($item);
        }, this);
      } else {
        this._[mathlib.wrapIndex($index.__int__(), this._.length)] = this.__elem__($item);
      }

      return this;
    });

    builder.addMethod("foldPut", {
      args: "index; item"
    }, function($index, $item) {
      throwIfImmutable(this);

      if (Array.isArray($index._)) {
        $index._.forEach(function($index) {
          this._[mathlib.foldIndex($index.__int__(), this._.length)] = this.__elem__($item);
        }, this);
      } else {
        this._[mathlib.foldIndex($index.__int__(), this._.length)] = this.__elem__($item);
      }

      return this;
    });

    builder.addMethod("removeAt", {
      args: "index"
    }, function($index) {
      var raw = this._;
      var index;

      throwIfImmutable(this);

      index = $index.__int__();
      if (index < 0 || raw.length <= index) {
        throw new Error("out of index");
      }

      return raw.splice(index, 1)[0];
    });

    builder.addMethod("takeAt", {
      args: "index"
    }, function($index) {
      var raw = this._;
      var index, ret, instead;

      throwIfImmutable(this);

      index = $index.__int__();
      if (index < 0 || raw.length <= index) {
        throw new Error("out of index");
      }

      ret = raw[index];
      instead = raw.pop();
      if (index !== raw.length) {
        raw[index] = instead;
      }

      return ret;
    });

    builder.addMethod("indexOf", {
      args: "item"
    }, function($item) {
      var index = this._.indexOf($item);
      return index === -1 ? $nil : $.Integer(index);
    });

    builder.addMethod("indexOfGreaterThan", {
      args: "val"
    }, function($val) {
      var raw = this._;
      var val, i, imax = raw.length;

      val = $val.__num__();
      for (i = 0; i < imax; ++i) {
        if (raw[i].__num__() > val) {
          return $.Integer(i);
        }
      }

      return $nil;
    });

    builder.addMethod("takeThese", {
      args: "func"
    }, function($func) {
      var raw = this._;
      var i = 0, $i;

      $i = $.Integer(i);
      while (i < raw.length) {
        if ($func.value(raw[i], $i).__bool__()) {
          this.takeAt($i);
        } else {
          $i = $.Integer(++i);
        }
      }

      return this;
    });

    builder.addMethod("replace", {
      args: "find; replace"
    }, function($find, $replace) {
      var $index, $out, $array;

      throwIfImmutable(this);

      $out     = $.Array();
      $array   = this;
      $find    = $find.asArray();
      $replace = $replace.asArray();
      $.Func(function() {
        return ($index = $array.find($find)).notNil();
      }).while($.Func(function() {
        $out = $out ["++"] ($array.keep($index)) ["++"] ($replace);
        $array = $array.drop($index ["+"] ($find.size()));
        return $array;
      }));

      return $out ["++"] ($array);
    });

    builder.addMethod("slotSize", function() {
      return this.size();
    });

    builder.addMethod("slotAt", function($index) {
      return this.at($index);
    });

    builder.addMethod("slotPut", function($index, $value) {
      return this.put($index, $value);
    });

    builder.addMethod("slotKey", function($index) {
      return $index;
    });

    builder.addMethod("slotIndex", function() {
      return $nil;
    });

    builder.addMethod("getSlots", function() {
      return this.copy();
    });

    builder.addMethod("setSlots", function($array) {
      return this.overWrite($array);
    });

    builder.addMethod("atModify", {
      args: "index; function"
    }, function($index, $function) {
      this.put($index, $function.value(this.at($index), $index));
      return this;
    });

    builder.addMethod("atInc", {
      args: "index; inc=1"
    }, function($index, $inc) {
      this.put($index, this.at($index).$("+", [ $inc ]));
      return this;
    });

    builder.addMethod("atDec", {
      args: "index; dec=1"
    }, function($index, $dec) {
      this.put($index, this.at($index).$("-", [ $dec ]));
      return this;
    });

    builder.addMethod("isArray", sc.TRUE);
    builder.addMethod("asArray");

    builder.addMethod("copyRange", {
      args: "start; end"
    }, function($start, $end) {
      var start, end, instance, raw;

      if ($start === $nil) {
        start = 0;
      } else {
        start = $start.__int__();
      }
      if ($end === $nil) {
        end = this._.length;
      } else {
        end = $end.__int__();
      }
      raw = this._.slice(start, end + 1);

      instance = new this.__Spec([]);
      instance._ = raw;
      return instance;
    });

    builder.addMethod("copySeries", {
      args: "first; second; last"
    }, function($first, $second, $last) {
      var i, first, second, last, step, instance, raw;

      raw = [];
      if ($first === $nil) {
        first = 0;
      } else {
        first = $first.__int__();
      }
      if ($second === $nil) {
        second = first + 1;
      } else {
        second = $second.__int__();
      }
      if ($last === $nil) {
        last = Infinity;
      } else {
        last = $last.__int__();
      }
      last = Math.max(0, Math.min(last, this._.length - 1));
      step = second - first;

      if (step > 0) {
        for (i = first; i <= last; i += step) {
          raw.push(this._[i]);
        }
      } else if (step < 0) {
        for (i = first; i >= last; i += step) {
          raw.push(this._[i]);
        }
      }

      instance = new this.__Spec([]);
      instance._ = raw;
      return instance;
    });

    builder.addMethod("putSeries", {
      args: "first; second; last; value"
    }, function($first, $second, $last, $value) {
      var i, first, second, last, step;

      throwIfImmutable(this);

      if ($first === $nil) {
        first = 0;
      } else {
        first = $first.__int__();
      }
      if ($second === $nil) {
        second = first + 1;
      } else {
        second = $second.__int__();
      }
      if ($last === $nil) {
        last = Infinity;
      } else {
        last = $last.__int__();
      }
      last = Math.max(0, Math.min(last, this._.length - 1));
      step = second - first;

      $value = this.__elem__($value);

      if (step > 0) {
        for (i = first; i <= last; i += step) {
          this._[i] = $value;
        }
      } else if (step < 0) {
        for (i = first; i >= last; i += step) {
          this._[i] = $value;
        }
      }

      return this;
    });

    builder.addMethod("add", {
      args: "item"
    }, function($item) {
      throwIfImmutable(this);
      this._.push(this.__elem__($item));

      return this;
    });

    builder.addMethod("addAll", {
      args: "aCollection"
    }, function($aCollection) {
      var $this = this;

      throwIfImmutable(this);

      if ($aCollection.isCollection().__bool__()) {
        $aCollection.do($.Func(function($item) {
          return $this._.push($this.__elem__($item));
        }));
      } else {
        this.add($aCollection);
      }

      return this;
    });

    builder.addMethod("putEach", {
      args: "keys; values"
    }, function($keys, $values) {
      var keys, values, i, imax;

      throwIfImmutable(this);

      $keys   = $keys.asArray();
      $values = $values.asArray();

      keys   = $keys._;
      values = $values._;
      for (i = 0, imax = keys.length; i < imax; ++i) {
        this.put(keys[i], this.__elem__(values[i % values.length]));
      }

      return this;
    });

    builder.addMethod("extend", {
      args: "size; item"
    }, function($size, $item) {
      var instance, raw, size, i;

      raw  = this._.slice();
      size = $size.__int__();
      if (raw.length > size) {
        raw.splice(size);
      } else if (raw.length < size) {
        for (i = size - raw.length; i--; ) {
          raw.push(this.__elem__($item));
        }
      }

      instance = new this.__Spec([]);
      instance._ = raw;
      return instance;
    });

    builder.addMethod("insert", {
      args: "index; item"
    }, function($index, $item) {
      var index;

      throwIfImmutable(this);

      index = Math.max(0, $index.__int__());
      this._.splice(index, 0, this.__elem__($item));

      return this;
    });

    builder.addMethod("move", function($fromIndex, $toIndex) {
      return this.insert($toIndex, this.removeAt($fromIndex));
    });

    builder.addMethod("addFirst", {
      args: "item"
    }, function($item) {
      var instance, raw;

      raw = this._.slice();
      raw.unshift(this.__elem__($item));

      instance = new this.__Spec([]);
      instance._ = raw;
      return instance;
    });

    builder.addMethod("addIfNotNil", {
      args: "item"
    }, function($item) {
      if ($item === $nil) {
        return this;
      }
      return this.addFirst(this.__elem__($item));
    });

    builder.addMethod("pop", function() {
      if (this._.length === 0) {
        return $nil;
      }
      throwIfImmutable(this);
      return this._.pop();
    });

    builder.addMethod("++", function($anArray) {
      var instance, raw;

      raw = this._.slice();

      instance = new this.__Spec([]);
      instance._ = raw;
      if ($anArray !== $nil) {
        instance.addAll($anArray);
      }
      return instance;
    });

    // TODO: implements overWrite
    // TODO: implements grow
    // TODO: implements growClear

    builder.addMethod("seriesFill", {
      args: "start; step"
    }, function($start, $step) {
      var i, imax;

      for (i = 0, imax = this._.length; i < imax; ++i) {
        this.put($.Integer(i), $start);
        $start = $start.$("+", [ $step ]);
      }

      return this;
    });

    builder.addMethod("fill", {
      args: "value"
    }, function($value) {
      var raw, i, imax;

      throwIfImmutable(this);

      $value = this.__elem__($value);

      raw = this._;
      for (i = 0, imax = raw.length; i < imax; ++i) {
        raw[i] = $value;
      }

      return this;
    });

    builder.addMethod("do", function($function) {
      sc.lang.iterator.execute(
        sc.lang.iterator.array$do(this),
        $function
      );
      return this;
    });

    builder.addMethod("reverseDo", function($function) {
      sc.lang.iterator.execute(
        sc.lang.iterator.array$reverseDo(this),
        $function
      );
      return this;
    });

    builder.addMethod("reverse", function() {
      var $res = this.copy();
      $res._.reverse();
      return $res;
    });

    builder.addMethod("windex", function() {
      var raw = this._;
      var x, r, i, imax;

      // <-- _ArrayWindex -->
      x = 0;
      r = random.next();
      for (i = 0, imax = raw.length; i < imax; ++i) {
        x += raw[i].__num__();
        if (x >= r) {
          return $.Integer(i);
        }
      }

      return $int0;
    });

    builder.addMethod("normalizeSum", function() {
      return this ["*"] (this.sum().reciprocal());
    });

    builder.addMethod("normalize", {
      args: "min=0.0; max=1.0"
    }, function($min, $max) {
      var $minItem, $maxItem;

      $minItem = this.minItem();
      $maxItem = this.maxItem();
      return this.collect($.Func(function($el) {
        return $el.$("linlin", [ $minItem, $maxItem, $min, $max ]);
      }));
    });

    // TODO: implements asciiPlot
    // TODO: implements perfectShuffle
    // TODO: implements performInPlace

    builder.addMethod("clipExtend", {
      args: "length"
    }, function($length) {
      var last = this._[this._.length - 1] || $nil;
      return this.extend($length, last);
    });

    builder.addMethod("rank", function() {
      return $int1 ["+"] (this.first().rank());
    });

    builder.addMethod("shape", function() {
      return $.Array([ this.size() ]) ["++"] (this.at($int0).$("shape"));
    });

    builder.addMethod("reshape", function() {
      var $result;
      var shape, size, i, imax;

      shape = _.toArray(arguments);

      size = 1;
      for (i = 0, imax = shape.length; i < imax; ++i) {
        size *= shape[i].__int__();
      }

      $result = this.flat().wrapExtend($.Integer(size));
      for (i = imax - 1; i >= 1; --i) {
        $result = $result.clump(shape[i]);
      }

      return $result;
    });

    builder.addMethod("reshapeLike", {
      args: "another; indexing=\\wrapAt"
    }, function($another, $indexing) {
      var $index, $flat;

      $index = $int0;
      $flat  = this.flat();

      return $another.deepCollect($.Integer(0x7FFFFFFF), $.Func(function() {
        var $item = $flat.perform($indexing, $index);
        $index = $index.__inc__();
        return $item;
      }));
    });

    // TODO: implements deepCollect
    // TODO: implements deepDo

    builder.addMethod("unbubble", {
      args: "depth=0; levels=1"
    }, function($depth, $levels) {
      if ($depth.__num__() <= 0) {
        if (this.size().__int__() > 1) {
          return this;
        }
        if ($levels.__int__() <= 1) {
          return this.at($int0);
        }
        return this.at($int0).unbubble($depth, $levels.__dec__());
      }

      return this.collect($.Func(function($item) {
        return $item.unbubble($depth.__dec__());
      }));
    });

    builder.addMethod("bubble", {
      args: "depth=0; levels=1"
    }, function($depth, $levels) {
      if ($depth.__int__() <= 0) {
        if ($levels.__int__() <= 1) {
          return $.Array([ this ]);
        }
        return $.Array([ this.bubble($depth, $levels.__dec__()) ]);
      }

      return this.collect($.Func(function($item) {
        return $item.bubble($depth.__dec__(), $levels);
      }));
    });

    builder.addMethod("slice", {
      args: "*cuts"
    }, function($$cuts) {
      var $firstCut, $list;
      var lenOfCuts, cuts;

      lenOfCuts = $$cuts.size().__int__();
      if (lenOfCuts === 0) {
        return this.copy();
      }

      $firstCut = $$cuts.at($int0);
      if ($firstCut === $nil) {
        $list = this.copy();
      } else {
        $list = this.at($firstCut.asArray());
      }

      if (lenOfCuts === 1) {
        return $list.unbubble();
      }

      cuts = $$cuts._.slice(1);
      return $list.collect($.Func(function($item) {
        return $item.$("slice", cuts);
      })).unbubble();
    });

    builder.addClassMethod("iota", function() {
      var $a;
      var args, product, i, imax, a;

      args = arguments;

      product = 1;
      for (i = 0, imax = args.length; i < imax; ++i) {
        product *= args[i].__int__();
      }

      a = new Array(product);
      for (i = 0; i < product; ++i) {
        a[i] = $.Integer(i);
      }

      $a = $.Array(a);
      return $a.reshape.apply($a, args);
    });

    // TODO: implements asRandomTable
    // TODO: implements tableRand
    // TODO: implements msgSize
    // TODO: implements bundleSize
    // TODO: implements clumpBundles

    builder.addMethod("includes", function($item) {
      return $.Boolean(this._.indexOf($item) !== -1);
    });

    builder.addMethod("asString", function() {
      return $.String("[ " + this._.map(function($elem) {
        return $elem.asString().__str__();
      }).join(", ") + " ]");
    });

    /* istanbul ignore next */
    builder.addMethod("__sort__", function($function) {
      throwIfImmutable(this);
      this._.sort(function($a, $b) {
        return $function.value($a, $b).__bool__() ? -1 : 1;
      });
    });
  });

  sc.lang.klass.refine("RawArray", function(builder) {
    var SCArray = $("Array");

    builder.addMethod("archiveAsCompileString", sc.TRUE);
    builder.addMethod("archiveAsObject", sc.TRUE);

    builder.addMethod("rate", function() {
      return $.Symbol("scalar");
    });

    // TODO: implements readFromStream

    builder.addMethod("powerset", function() {
      return this.as(SCArray).powerset();
    });
  });

  sc.lang.klass.define("Int8Array : RawArray", function(builder) {
    var int8 = new Int8Array(1);

    builder.addMethod("valueOf", function() {
      return new Int8Array(this._.map(function($elem) {
        return $elem.__int__();
      }));
    });

    builder.addMethod("__elem__", function(item) {
      int8[0] = item ? item.__int__() : 0;
      return $.Integer(int8[0]);
    });
  });

  sc.lang.klass.define("Int16Array : RawArray", function(builder) {
    var int16 = new Int16Array(1);

    builder.addMethod("valueOf", function() {
      return new Int16Array(this._.map(function($elem) {
        return $elem.__int__();
      }));
    });

    builder.addMethod("__elem__", function(item) {
      int16[0] = item ? item.__int__() : 0;
      return $.Integer(int16[0]);
    });
  });

  sc.lang.klass.define("Int32Array : RawArray", function(builder) {
    var int32 = new Int32Array(1);

    builder.addMethod("valueOf", function() {
      return new Int32Array(this._.map(function($elem) {
        return $elem.__int__();
      }));
    });

    builder.addMethod("__elem__", function(item) {
      int32[0] = item ? item.__int__() : 0;
      return $.Integer(int32[0]);
    });
  });

  sc.lang.klass.define("FloatArray : RawArray", function(builder) {
    var float32 = new Float32Array(1);

    builder.addMethod("valueOf", function() {
      return new Float32Array(this._.map(function($elem) {
        return $elem.__num__();
      }));
    });

    builder.addMethod("__elem__", function(item) {
      float32[0] = item ? item.__num__() : 0;
      return $.Float(float32[0]);
    });
  });

  sc.lang.klass.define("DoubleArray : RawArray", function(builder) {
    var float64 = new Float64Array(1);

    builder.addMethod("valueOf", function() {
      return new Float64Array(this._.map(function($elem) {
        return $elem.__num__();
      }));
    });

    builder.addMethod("__elem__", function(item) {
      float64[0] = item ? item.__num__() : 0;
      return $.Float(float64[0]);
    });
  });
});