PageRenderTime 130ms CodeModel.GetById 2ms app.highlight 113ms RepoModel.GetById 1ms app.codeStats 1ms

/js/stdlib.js

https://bitbucket.org/pthrasher/nocschedule
JavaScript | 2443 lines | 2014 code | 425 blank | 4 comment | 411 complexity | f79784df606caed8dff9ab1b700c7115 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1JS.MethodChain = function(base) {
   2  var queue      = [],
   3      baseObject = base || {};
   4  
   5  this.____ = function(method, args) {
   6    queue.push({func: method, args: args});
   7  };
   8  
   9  this.fire = function(base) {
  10    return JS.MethodChain.fire(queue, base || baseObject);
  11  };
  12};
  13
  14JS.MethodChain.fire = function(queue, object) {
  15  var method, property, i, n;
  16  loop: for (i = 0, n = queue.length; i < n; i++) {
  17    method = queue[i];
  18    if (object instanceof JS.MethodChain) {
  19      object.____(method.func, method.args);
  20      continue;
  21    }
  22    switch (typeof method.func) {
  23      case 'string':    property = object[method.func];       break;
  24      case 'function':  property = method.func;               break;
  25      case 'object':    object = method.func; continue loop;  break;
  26    }
  27    object = (typeof property === 'function')
  28        ? property.apply(object, method.args)
  29        : property;
  30  }
  31  return object;
  32};
  33
  34JS.MethodChain.prototype = {
  35  _: function() {
  36    var base = arguments[0],
  37        args, i, n;
  38    
  39    switch (typeof base) {
  40      case 'object': case 'function':
  41        args = [];
  42        for (i = 1, n = arguments.length; i < n; i++) args.push(arguments[i]);
  43        this.____(base, args);
  44    }
  45    return this;
  46  },
  47  
  48  toFunction: function() {
  49    var chain = this;
  50    return function(object) { return chain.fire(object); };
  51  }
  52};
  53
  54JS.MethodChain.reserved = (function() {
  55  var names = [], key;
  56  for (key in new JS.MethodChain) names.push(key);
  57  return new RegExp('^(?:' + names.join('|') + ')$');
  58})();
  59
  60JS.MethodChain.addMethod = function(name) {
  61  if (this.reserved.test(name)) return;
  62  var func = this.prototype[name] = function() {
  63    this.____(name, arguments);
  64    return this;
  65  };
  66  func.displayName = 'MethodChain#' + name;
  67};
  68
  69JS.MethodChain.displayName = 'MethodChain';
  70
  71JS.MethodChain.addMethods = function(object) {
  72  var methods = [], property, i;
  73  
  74  for (property in object)
  75    Number(property) !== property && methods.push(property);
  76  
  77  if (object instanceof Array) {
  78    i = object.length;
  79    while (i--)
  80      typeof object[i] === 'string' && methods.push(object[i]);
  81  }
  82  i = methods.length;
  83  while (i--) this.addMethod(methods[i]);
  84  
  85  object.prototype &&
  86    this.addMethods(object.prototype);
  87};
  88
  89it = its = function() { return new JS.MethodChain; };
  90
  91JS.Module.methodAdded(function(name) {
  92  JS.MethodChain.addMethod(name);
  93});
  94
  95JS.Kernel.include({
  96  wait: function(time) {
  97    var chain = new JS.MethodChain;
  98    
  99    typeof time === 'number' &&
 100      setTimeout(chain.fire.bind(chain, this), time * 1000);
 101    
 102    this.forEach && typeof time === 'function' &&
 103      this.forEach(function() {
 104        setTimeout(chain.fire.bind(chain, arguments[0]), time.apply(this, arguments) * 1000);
 105      });
 106    
 107    return chain;
 108  },
 109  
 110  _: function() {
 111    var base = arguments[0],
 112        args = [],
 113        i, n;
 114    
 115    for (i = 1, n = arguments.length; i < n; i++) args.push(arguments[i]);
 116    return  (typeof base === 'object' && base) ||
 117            (typeof base === 'function' && base.apply(this, args)) ||
 118            this;
 119  }
 120}, true);
 121
 122JS.MethodChain.addMethods([
 123  "abbr", "abs", "accept", "acceptCharset", "accesskey", "acos", "action", "addEventListener", 
 124  "adjacentNode", "align", "alignWithTop", "alink", "alt", "anchor", "appendChild", "appendedNode", 
 125  "apply", "archive", "arguments", "arity", "asin", "atan", "atan2", "attrNode", "attributes", 
 126  "axis", "background", "bgcolor", "big", "blink", "blur", "bold", "border", "call", "caller", 
 127  "ceil", "cellpadding", "cellspacing", "char", "charAt", "charCodeAt", "charoff", "charset", 
 128  "checked", "childNodes", "cite", "className", "classid", "clear", "click", "clientHeight", 
 129  "clientLeft", "clientTop", "clientWidth", "cloneNode", "code", "codebase", "codetype", "color", 
 130  "cols", "colspan", "compact", "concat", "content", "coords", "cos", "data", "datetime", "declare", 
 131  "deep", "defer", "dir", "disabled", "dispatchEvent", "enctype", "event", "every", "exec", "exp", 
 132  "face", "filter", "firstChild", "fixed", "floor", "focus", "fontcolor", "fontsize", "forEach", 
 133  "frame", "frameborder", "fromCharCode", "getAttribute", "getAttributeNS", "getAttributeNode", 
 134  "getAttributeNodeNS", "getDate", "getDay", "getElementsByTagName", "getElementsByTagNameNS", 
 135  "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds", "getTime", 
 136  "getTimezoneOffset", "getUTCDate", "getUTCDay", "getUTCFullYear", "getUTCHours", 
 137  "getUTCMilliseconds", "getUTCMinutes", "getUTCMonth", "getUTCSeconds", "getYear", "global", 
 138  "handler", "hasAttribute", "hasAttributeNS", "hasAttributes", "hasChildNodes", "hasOwnProperty", 
 139  "headers", "height", "href", "hreflang", "hspace", "htmlFor", "httpEquiv", "id", "ignoreCase", 
 140  "index", "indexOf", "innerHTML", "input", "insertBefore", "insertedNode", "isPrototypeOf", "ismap", 
 141  "italics", "join", "label", "lang", "language", "lastChild", "lastIndex", "lastIndexOf", "length", 
 142  "link", "listener", "localName", "log", "longdesc", "map", "marginheight", "marginwidth", "match", 
 143  "max", "maxlength", "media", "method", "min", "multiline", "multiple", "name", "namespace", 
 144  "namespaceURI", "nextSibling", "node", "nodeName", "nodeType", "nodeValue", "nohref", "noresize", 
 145  "normalize", "noshade", "now", "nowrap", "object", "offsetHeight", "offsetLeft", "offsetParent", 
 146  "offsetTop", "offsetWidth", "onblur", "onchange", "onclick", "ondblclick", "onfocus", "onkeydown", 
 147  "onkeypress", "onkeyup", "onload", "onmousedown", "onmousemove", "onmouseout", "onmouseover", 
 148  "onmouseup", "onreset", "onselect", "onsubmit", "onunload", "ownerDocument", "parentNode", "parse", 
 149  "pop", "pow", "prefix", "previousSibling", "profile", "prompt", "propertyIsEnumerable", "push", 
 150  "random", "readonly", "reduce", "reduceRight", "rel", "removeAttribute", "removeAttributeNS", 
 151  "removeAttributeNode", "removeChild", "removeEventListener", "removedNode", "replace", 
 152  "replaceChild", "replacedNode", "rev", "reverse", "round", "rows", "rowspan", "rules", "scheme", 
 153  "scope", "scrollHeight", "scrollIntoView", "scrollLeft", "scrollTop", "scrollWidth", "scrolling", 
 154  "search", "selected", "setAttribute", "setAttributeNS", "setAttributeNode", "setAttributeNodeNS", 
 155  "setDate", "setFullYear", "setHours", "setMilliseconds", "setMinutes", "setMonth", "setSeconds", 
 156  "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds", "setUTCMinutes", 
 157  "setUTCMonth", "setUTCSeconds", "setYear", "shape", "shift", "sin", "size", "slice", "small", 
 158  "some", "sort", "source", "span", "splice", "split", "sqrt", "src", "standby", "start", "strike", 
 159  "style", "sub", "substr", "substring", "summary", "sup", "tabIndex", "tabindex", "tagName", "tan", 
 160  "target", "test", "text", "textContent", "title", "toArray", "toFunction", "toGMTString", 
 161  "toLocaleDateString", "toLocaleFormat", "toLocaleString", "toLocaleTimeString", "toLowerCase", 
 162  "toSource", "toString", "toUTCString", "toUpperCase", "type", "unshift", "unwatch", "useCapture", 
 163  "usemap", "valign", "value", "valueOf", "valuetype", "version", "vlink", "vspace", "watch", "width"
 164]);
 165
 166
 167JS.Package = new JS.Class('Package', {
 168  initialize: function(loader) {
 169    this._loader  = loader;
 170    this._deps    = [];
 171    this._uses    = [];
 172    this._names   = [];
 173    this._waiters = [];
 174    this._loading = false;
 175  },
 176  
 177  addDependency: function(pkg) {
 178    if (JS.indexOf(this._deps, pkg) === -1) this._deps.push(pkg);
 179  },
 180  
 181  addSoftDependency: function(pkg) {
 182    if (JS.indexOf(this._uses, pkg) === -1) this._uses.push(pkg);
 183  },
 184  
 185  _getPackage: function(list, index) {
 186    var pkg = list[index];
 187    if (typeof pkg === 'string') pkg = list[index] = this.klass.getByName(pkg);
 188    return pkg;
 189  },
 190  
 191  addName: function(name) {
 192    if (!this.contains(name)) this._names.push(name);
 193  },
 194  
 195  contains: function(name) {
 196    return JS.indexOf(this._names, name) !== -1;
 197  },
 198  
 199  depsComplete: function(deps) {
 200    deps = deps || this._deps;
 201    var n = deps.length, dep;
 202    while (n--) {
 203      if (!this._getPackage(deps, n).isComplete()) return false;
 204    }
 205    return true;
 206  },
 207  
 208  isComplete: function() {
 209    return this.isLoaded() &&
 210           this.depsComplete(this._deps) &&
 211           this.depsComplete(this._uses);
 212  },
 213  
 214  isLoaded: function(withExceptions) {
 215    if (this._isLoaded) return true;
 216    
 217    var names = this._names,
 218        n     = names.length,
 219        object;
 220    
 221    while (n--) {
 222      object = this.klass.getObject(names[n]);
 223      if (object !== undefined) continue;
 224      if (withExceptions)
 225        throw new Error('Expected package at ' + this._loader + ' to define ' + names[n]);
 226      else
 227        return false;
 228    }
 229    return this._isLoaded = true;
 230  },
 231  
 232  readyToLoad: function() {
 233    return !this.isLoaded() && this.depsComplete();
 234  },
 235  
 236  expand: function(list) {
 237    var deps = list || [], dep, n;
 238    n = this._deps.length;
 239    while (n--) this._getPackage(this._deps, n).expand(deps);
 240    if (JS.indexOf(deps, this) === -1) deps.push(this);
 241    n = this._uses.length;
 242    while (n--) this._getPackage(this._uses, n).expand(deps);
 243    return deps;
 244  },
 245  
 246  onload: function(block) {
 247    this._onload = block;
 248  },
 249  
 250  load: function(callback, context) {
 251    var self = this, handler, fireCallbacks;
 252    
 253    handler = function() {
 254      self._loading = false;
 255      callback.call(context || null);
 256    };
 257    
 258    if (this.isLoaded()) return setTimeout(handler, 1);
 259    
 260    this._waiters.push(handler);
 261    if (this._loading) return;
 262    
 263    fireCallbacks = function() {
 264      if (JS.isFn(self._onload)) self._onload();
 265      self.isLoaded(true);
 266      for (var i = 0, n = self._waiters.length; i < n; i++) self._waiters[i]();
 267      self._waiters = [];
 268    };
 269    
 270    this._loading = true;
 271    
 272    JS.isFn(this._loader)
 273        ? this._loader(fireCallbacks)
 274        : this.klass.Loader.loadFile(this._loader, fireCallbacks);
 275  },
 276  
 277  toString: function() {
 278    return 'Package:' + this._names[0];
 279  },
 280  
 281  extend: {
 282    _store:   {},
 283    _env:     this,
 284    
 285    getByPath: function(loader) {
 286      var path = loader.toString();
 287      return this._store[path] || (this._store[path] = new this(loader));
 288    },
 289    
 290    getByName: function(name) {
 291      for (var path in this._store) {
 292        if (this._store[path].contains(name))
 293          return this._store[path];
 294      }
 295      throw new Error('Could not find package containing ' + name);
 296    },
 297    
 298    getObject: function(name) {
 299      var object = this._env,
 300          parts  = name.split('.'), part;
 301      
 302      while (part = parts.shift()) object = (object||{})[part];
 303      return object;
 304    },
 305    
 306    expand: function(list) {
 307      var packages = [], i, n;
 308      for (i = 0, n = list.length; i < n; i++)
 309        list[i].expand(packages);
 310      return packages;
 311    },
 312    
 313    load: function(list, counter, callback) {
 314      var ready    = [],
 315          deferred = [],
 316          n        = list.length,
 317          pkg;
 318      
 319      while (n--) {
 320        pkg = list[n];
 321        if (pkg.isComplete())
 322          counter -= 1;
 323        else
 324          (pkg.readyToLoad() ? ready : deferred).push(pkg);
 325      }
 326      
 327      if (counter === 0) return callback();
 328      
 329      n = ready.length;
 330      while (n--) ready[n].load(function() {
 331        this.load(deferred, --counter, callback);
 332      }, this);
 333    }
 334  }
 335});
 336
 337
 338JS.Package.extend({
 339  DomLoader: {
 340    usable: function() {
 341      return !!JS.Package.getObject('window.document.getElementsByTagName');
 342    },
 343    
 344    __FILE__: function() {
 345      var scripts = document.getElementsByTagName('script');
 346      return scripts[scripts.length - 1].src;
 347    },
 348    
 349    loadFile: function(path, fireCallbacks) {
 350      var self = this,
 351          tag = document.createElement('script');
 352      
 353      tag.type = 'text/javascript';
 354      tag.src = path;
 355      
 356      tag.onload = tag.onreadystatechange = function() {
 357        var state = tag.readyState, status = tag.status;
 358        if ( !state || state === 'loaded' || state === 'complete' || (state === 4 && status === 200) ) {
 359          fireCallbacks();
 360          tag.onload = tag.onreadystatechange = self._K;
 361          tag = null;
 362        }
 363      };
 364      ;;; window.console && console.info('Loading ' + path);
 365      document.getElementsByTagName('head')[0].appendChild(tag);
 366    },
 367    
 368    _K: function() {}
 369  },
 370  
 371  ServerLoader: {
 372    usable: function() {
 373      return JS.isFn(JS.Package.getObject('load')) &&
 374             JS.isFn(JS.Package.getObject('version'));
 375    },
 376    
 377    setup: function() {
 378      var self = this;
 379      load = (function(origLoad) {
 380        return function() {
 381          self._currentPath = arguments[0];
 382          return origLoad.apply(JS.Package._env, arguments);
 383        };
 384      })(load);
 385    },
 386    
 387    __FILE__: function() {
 388      return this._currentPath;
 389    },
 390    
 391    loadFile: function(path, fireCallbacks) {
 392      load(path);
 393      fireCallbacks();
 394    }
 395  }
 396});
 397
 398(function() {
 399  var candidates = [  JS.Package.DomLoader,
 400                      JS.Package.ServerLoader ],
 401      
 402      n = candidates.length,
 403      i, candidate;
 404  
 405  for (i = 0; i < n; i++) {
 406    candidate = candidates[i];
 407    if (candidate.usable()) {
 408      JS.Package.Loader = candidate;
 409      if (candidate.setup) candidate.setup();
 410      break;
 411    }
 412  }
 413})();
 414
 415
 416JS.Package.extend({
 417  DSL: {
 418    __FILE__: function() {
 419      return JS.Package.Loader.__FILE__();
 420    },
 421    
 422    pkg: function(name, path) {
 423      var pkg = path
 424          ? JS.Package.getByPath(path)
 425          : JS.Package.getByName(name);
 426      pkg.addName(name);
 427      return new JS.Package.Description(pkg);
 428    },
 429    
 430    file: function(path) {
 431      var pkg = JS.Package.getByPath(path);
 432      return new JS.Package.Description(pkg);
 433    },
 434    
 435    load: function(path, fireCallbacks) {
 436      JS.Package.loadFile(path, fireCallbacks);
 437    }
 438  },
 439  
 440  Description: new JS.Class({
 441    initialize: function(pkg) {
 442      this._pkg = pkg;
 443    },
 444    
 445    _batch: function(method, args) {
 446      var n = args.length, method = this._pkg[method], i;
 447      for (i = 0; i < n; i++) method.call(this._pkg, args[i]);
 448      return this;
 449    },
 450    
 451    provides: function() {
 452      return this._batch('addName', arguments);
 453    },
 454    
 455    requires: function() {
 456      return this._batch('addDependency', arguments);
 457    },
 458    
 459    uses: function() {
 460      return this._batch('addSoftDependency', arguments);
 461    },
 462    
 463    setup: function(block) {
 464      this._pkg.onload(block);
 465      return this;
 466    }
 467  })
 468});
 469
 470JS.Package.DSL.loader = JS.Package.DSL.file;
 471
 472JS.Packages = function(declaration) {
 473  declaration.call(JS.Package.DSL);
 474};
 475 
 476JS.require = function() {
 477  var args         = JS.array(arguments),
 478      requirements = [];
 479  
 480  while (typeof args[0] === 'string') requirements.push(JS.Package.getByName(args.shift()));
 481  requirements = JS.Package.expand(requirements);
 482  
 483  var fired = false, handler = function() {
 484    if (fired) return;
 485    fired = true;
 486    args[0] && args[0].call(args[1] || null);
 487  };
 488  
 489  JS.Package.load(requirements, requirements.length, handler);
 490};
 491
 492require = JS.require;
 493
 494
 495JS.Comparable = new JS.Module('Comparable', {
 496  extend: {
 497    ClassMethods: new JS.Module({
 498      compare: function(one, another) {
 499        return one.compareTo(another);
 500      }
 501    }),
 502    
 503    included: function(base) {
 504      base.extend(this.ClassMethods);
 505    }
 506  },
 507  
 508  lt: function(other) {
 509    return this.compareTo(other) < 0;
 510  },
 511  
 512  lte: function(other) {
 513    return this.compareTo(other) < 1;
 514  },
 515  
 516  gt: function(other) {
 517    return this.compareTo(other) > 0;
 518  },
 519  
 520  gte: function(other) {
 521    return this.compareTo(other) > -1;
 522  },
 523  
 524  eq: function(other) {
 525    return this.compareTo(other) === 0;
 526  },
 527  
 528  between: function(a, b) {
 529    return this.gte(a) && this.lte(b);
 530  }
 531});
 532
 533
 534JS.Enumerable = new JS.Module('Enumerable', {
 535  extend: {
 536    forEach: function(block, context) {
 537      if (!block) return new JS.Enumerator(this, 'forEach');
 538      for (var i = 0, n = this.length; i < n; i++) {
 539        if (this[i] !== undefined)
 540          block.call(context || null, this[i]);
 541      }
 542      return this;
 543    },
 544    
 545    isComparable: function(list) {
 546      return list.all(function(item) { return JS.isFn(item.compareTo) });
 547    },
 548    
 549    areEqual: function(one, another) {
 550      return one.equals
 551          ? one.equals(another)
 552          : (one === another);
 553    },
 554    
 555    Collection: new JS.Class({
 556      initialize: function(array) {
 557        this.length = 0;
 558        var push = Array.prototype.push;
 559        JS.Enumerable.forEach.call(array, function(item) {
 560          push.call(this, item);
 561        }, this);
 562      }
 563    })
 564  },
 565  
 566  all: function(block, context) {
 567    block = JS.Enumerable.toFn(block);
 568    var truth = true;
 569    this.forEach(function(item) {
 570      truth = truth && (block ? block.apply(context || null, arguments) : item);
 571    });
 572    return !!truth;
 573  },
 574  
 575  any: function(block, context) {
 576    block = JS.Enumerable.toFn(block);
 577    var truth = false;
 578    this.forEach(function(item) {
 579      truth = truth || (block ? block.apply(context || null, arguments) : item);
 580    });
 581    return !!truth;
 582  },
 583  
 584  count: function(block, context) {
 585    if (JS.isFn(this.size)) return this.size();
 586    var count = 0, object = block;
 587    
 588    if (block && !JS.isFn(block))
 589      block = function(x) { return JS.Enumerable.areEqual(x, object) };
 590    
 591    this.forEach(function() {
 592      if (!block || block.apply(context || null, arguments))
 593        count += 1;
 594    });
 595    return count;
 596  },
 597  
 598  cycle: function(n, block, context) {
 599    if (!block) return this.enumFor('cycle', n);
 600    block = JS.Enumerable.toFn(block);
 601    while (n--) this.forEach(block, context);
 602  },
 603  
 604  drop: function(n) {
 605    var entries = [];
 606    this.forEachWithIndex(function(item, i) {
 607      if (i >= n) entries.push(item);
 608    });
 609    return entries;
 610  },
 611  
 612  dropWhile: function(block, context) {
 613    if (!block) return this.enumFor('dropWhile');
 614    block = JS.Enumerable.toFn(block);
 615    
 616    var entries = [],
 617        drop    = true;
 618    
 619    this.forEach(function(item) {
 620      if (drop) drop = drop && block.apply(context || null, arguments);
 621      if (!drop) entries.push(item);
 622    });
 623    return entries;
 624  },
 625  
 626  forEachCons: function(n, block, context) {
 627    if (!block) return this.enumFor('forEachCons', n);
 628    block = JS.Enumerable.toFn(block);
 629    
 630    var entries = this.toArray(),
 631        size    = entries.length,
 632        limit   = size - n,
 633        i;
 634    
 635    for (i = 0; i <= limit; i++)
 636      block.call(context || null, entries.slice(i, i+n));
 637    
 638    return this;
 639  },
 640  
 641  forEachSlice: function(n, block, context) {
 642    if (!block) return this.enumFor('forEachSlice', n);
 643    block = JS.Enumerable.toFn(block);
 644    
 645    var entries = this.toArray(),
 646        size    = entries.length,
 647        m       = Math.ceil(size/n),
 648        i;
 649    
 650    for (i = 0; i < m; i++)
 651      block.call(context || null, entries.slice(i*n, (i+1)*n));
 652    
 653    return this;
 654  },
 655  
 656  forEachWithIndex: function(offset, block, context) {
 657    if (JS.isFn(offset)) {
 658      context = block;
 659      block   = offset;
 660      offset  = 0;
 661    }
 662    offset = offset || 0;
 663    
 664    if (!block) return this.enumFor('forEachWithIndex', offset);
 665    block = JS.Enumerable.toFn(block);
 666    
 667    return this.forEach(function(item) {
 668      var result = block.call(context || null, item, offset);
 669      offset += 1;
 670      return result;
 671    });
 672  },
 673  
 674  forEachWithObject: function(object, block, context) {
 675    if (!block) return this.enumFor('forEachWithObject', object);
 676    block = JS.Enumerable.toFn(block);
 677    
 678    this.forEach(function() {
 679      var args = [object].concat(JS.array(arguments));
 680      block.apply(context || null, args);
 681    });
 682    return object;
 683  },
 684  
 685  find: function(block, context) {
 686    if (!block) return this.enumFor('find');
 687    block = JS.Enumerable.toFn(block);
 688    
 689    var needle = {}, K = needle;
 690    this.forEach(function(item) {
 691      if (needle !== K) return;
 692      needle = block.apply(context || null, arguments) ? item : needle;
 693    });
 694    return needle === K ? null : needle;
 695  },
 696  
 697  findIndex: function(needle, context) {
 698    if (needle === undefined) return this.enumFor('findIndex');
 699    
 700    var index = null,
 701        block = JS.isFn(needle);
 702    
 703    this.forEachWithIndex(function(item, i) {
 704      if (index !== null) return;
 705      if (JS.Enumerable.areEqual(needle, item) || (block && needle.apply(context || null, arguments)))
 706        index = i;
 707    });
 708    return index;
 709  },
 710  
 711  first: function(n) {
 712    var entries = this.toArray();
 713    return (n === undefined) ? entries[0] : entries.slice(0,n);
 714  },
 715  
 716  grep: function(pattern, block, context) {
 717    block = JS.Enumerable.toFn(block);
 718    var results = [];
 719    this.forEach(function(item) {
 720      var match = JS.isFn(pattern.match) ? pattern.match(item) : pattern(item);
 721      if (!match) return;
 722      if (block) item = block.apply(context || null, arguments);
 723      results.push(item);
 724    });
 725    return results;
 726  },
 727  
 728  groupBy: function(block, context) {
 729    if (!block) return this.enumFor('groupBy');
 730    block = JS.Enumerable.toFn(block);
 731    
 732    var hash = new JS.Hash();
 733    this.forEach(function(item) {
 734      var value = block.apply(context || null, arguments);
 735      if (!hash.hasKey(value)) hash.store(value, []);
 736      hash.get(value).push(item);
 737    });
 738    return hash;
 739  },
 740  
 741  inject: function(memo, block, context) {
 742    var args    = JS.array(arguments),
 743        counter = 0,
 744        K       = {};
 745    
 746    switch (args.length) {
 747      case 1:   memo      = K;
 748                block     = args[0];
 749                break;
 750      
 751      case 2:   if (JS.isFn(memo)) {
 752                  memo    = K;
 753                  block   = args[0];
 754                  context = args[1];
 755                }
 756    }
 757    block = JS.Enumerable.toFn(block);
 758    
 759    this.forEach(function(item) {
 760      if (!counter++ && memo === K) return memo = item;
 761      var args = [memo].concat(JS.array(arguments));
 762      memo = block.apply(context || null, args);
 763    });
 764    return memo;
 765  },
 766  
 767  map: function(block, context) {
 768    if (!block) return this.enumFor('map');
 769    block = JS.Enumerable.toFn(block);
 770    
 771    var map = [];
 772    this.forEach(function() {
 773      map.push(block.apply(context || null, arguments));
 774    });
 775    return map;
 776  },
 777  
 778  max: function(block, context) {
 779    return this.minmax(block, context)[1];
 780  },
 781  
 782  maxBy: function(block, context) {
 783    if (!block) return this.enumFor('maxBy');
 784    return this.minmaxBy(block, context)[1];
 785  },
 786  
 787  member: function(needle) {
 788    return this.any(function(item) { return JS.Enumerable.areEqual(item, needle) });
 789  },
 790  
 791  min: function(block, context) {
 792    return this.minmax(block, context)[0];
 793  },
 794  
 795  minBy: function(block, context) {
 796    if (!block) return this.enumFor('minBy');
 797    return this.minmaxBy(block, context)[0];
 798  },
 799  
 800  minmax: function(block, context) {
 801    var list = this.sort(block, context);
 802    return [list[0], list[list.length - 1]];
 803  },
 804  
 805  minmaxBy: function(block, context) {
 806    if (!block) return this.enumFor('minmaxBy');
 807    var list = this.sortBy(block, context);
 808    return [list[0], list[list.length - 1]];
 809  },
 810  
 811  none: function(block, context) {
 812    return !this.any(block, context);
 813  },
 814  
 815  one: function(block, context) {
 816    block = JS.Enumerable.toFn(block);
 817    var count = 0;
 818    this.forEach(function(item) {
 819      if (block ? block.apply(context || null, arguments) : item) count += 1;
 820    });
 821    return count === 1;
 822  },
 823  
 824  partition: function(block, context) {
 825    if (!block) return this.enumFor('partition');
 826    block = JS.Enumerable.toFn(block);
 827    
 828    var ayes = [], noes = [];
 829    this.forEach(function(item) {
 830      (block.apply(context || null, arguments) ? ayes : noes).push(item);
 831    });
 832    return [ayes, noes];
 833  },
 834  
 835  reject: function(block, context) {
 836    if (!block) return this.enumFor('reject');
 837    block = JS.Enumerable.toFn(block);
 838    
 839    var map = [];
 840    this.forEach(function(item) {
 841      if (!block.apply(context || null, arguments)) map.push(item);
 842    });
 843    return map;
 844  },
 845  
 846  reverseForEach: function(block, context) {
 847    if (!block) return this.enumFor('reverseForEach');
 848    block = JS.Enumerable.toFn(block);
 849    
 850    var entries = this.toArray(),
 851        n       = entries.length;
 852    
 853    while (n--) block.call(context || null, entries[n]);
 854    return this;
 855  },
 856  
 857  select: function(block, context) {
 858    if (!block) return this.enumFor('select');
 859    block = JS.Enumerable.toFn(block);
 860    
 861    var map = [];
 862    this.forEach(function(item) {
 863      if (block.apply(context || null, arguments)) map.push(item);
 864    });
 865    return map;
 866  },
 867  
 868  sort: function(block, context) {
 869    var comparable = JS.Enumerable.isComparable(this),
 870        entries    = this.toArray();
 871    
 872    block = block || (comparable
 873        ? function(a,b) { return a.compareTo(b); }
 874        : null);
 875    return block
 876        ? entries.sort(function(a,b) { return block.call(context || null, a, b); })
 877        : entries.sort();
 878  },
 879  
 880  sortBy: function(block, context) {
 881    if (!block) return this.enumFor('sortBy');
 882    block = JS.Enumerable.toFn(block);
 883    
 884    var util       = JS.Enumerable,
 885        map        = new util.Collection(this.map(block, context)),
 886        comparable = util.isComparable(map);
 887    
 888    return new util.Collection(map.zip(this).sort(function(a, b) {
 889      a = a[0]; b = b[0];
 890      return comparable ? a.compareTo(b) : (a < b ? -1 : (a > b ? 1 : 0));
 891    })).map(function(item) { return item[1]; });
 892  },
 893  
 894  take: function(n) {
 895    var entries = [];
 896    this.forEachWithIndex(function(item, i) {
 897      if (i < n) entries.push(item);
 898    });
 899    return entries;
 900  },
 901  
 902  takeWhile: function(block, context) {
 903    if (!block) return this.enumFor('takeWhile');
 904    block = JS.Enumerable.toFn(block);
 905    
 906    var entries = [],
 907        take    = true;
 908    this.forEach(function(item) {
 909      if (take) take = take && block.apply(context || null, arguments);
 910      if (take) entries.push(item);
 911    });
 912    return entries;
 913  },
 914  
 915  toArray: function() {
 916    return this.drop(0);
 917  },
 918  
 919  zip: function() {
 920    var util    = JS.Enumerable,
 921        args    = [],
 922        counter = 0,
 923        n       = arguments.length,
 924        block, context;
 925    
 926    if (JS.isFn(arguments[n-1])) {
 927      block = arguments[n-1]; context = {};
 928    }
 929    if (JS.isFn(arguments[n-2])) {
 930      block = arguments[n-2]; context = arguments[n-1];
 931    }
 932    util.forEach.call(arguments, function(arg) {
 933      if (arg === block || arg === context) return;
 934      if (arg.toArray) arg = arg.toArray();
 935      if (JS.isType(arg, Array)) args.push(arg);
 936    });
 937    var results = this.map(function(item) {
 938      var zip = [item];
 939      util.forEach.call(args, function(arg) {
 940        zip.push(arg[counter] === undefined ? null : arg[counter]);
 941      });
 942      return ++counter && zip;
 943    });
 944    if (!block) return results;
 945    util.forEach.call(results, block, context);
 946  }
 947});
 948  
 949// http://developer.mozilla.org/en/docs/index.php?title=Core_JavaScript_1.5_Reference:Global_Objects:Array&oldid=58326
 950JS.Enumerable.include({
 951  forEach:    JS.Enumerable.forEach,
 952  collect:    JS.Enumerable.instanceMethod('map'),
 953  detect:     JS.Enumerable.instanceMethod('find'),
 954  entries:    JS.Enumerable.instanceMethod('toArray'),
 955  every:      JS.Enumerable.instanceMethod('all'),
 956  findAll:    JS.Enumerable.instanceMethod('select'),
 957  filter:     JS.Enumerable.instanceMethod('select'),
 958  some:       JS.Enumerable.instanceMethod('any'),
 959  
 960  extend: {
 961    toFn: function(object) {
 962      if (!object) return object;
 963      if (object.toFunction) return object.toFunction();
 964      if (this.OPS[object]) return this.OPS[object];
 965      if (JS.isType(object, 'string') || JS.isType(object, String))
 966        return function() {
 967          var args   = JS.array(arguments),
 968              target = args.shift(),
 969              method = target[object];
 970          return JS.isFn(method) ? method.apply(target, args) : method;
 971        };
 972      return object;
 973    },
 974    
 975    OPS: {
 976      '+':    function(a,b) { return a + b },
 977      '-':    function(a,b) { return a - b },
 978      '*':    function(a,b) { return a * b },
 979      '/':    function(a,b) { return a / b },
 980      '%':    function(a,b) { return a % b },
 981      '^':    function(a,b) { return a ^ b },
 982      '&':    function(a,b) { return a & b },
 983      '&&':   function(a,b) { return a && b },
 984      '|':    function(a,b) { return a | b },
 985      '||':   function(a,b) { return a || b },
 986      '==':   function(a,b) { return a == b },
 987      '!=':   function(a,b) { return a != b },
 988      '>':    function(a,b) { return a > b },
 989      '>=':   function(a,b) { return a >= b },
 990      '<':    function(a,b) { return a < b },
 991      '<=':   function(a,b) { return a <= b },
 992      '===':  function(a,b) { return a === b },
 993      '!==':  function(a,b) { return a !== b },
 994      '[]':   function(a,b) { return a[b] },
 995      '()':   function(a,b) { return a(b) }
 996    },
 997    
 998    Enumerator: new JS.Class({
 999      include: JS.Enumerable,
1000      
1001      extend: {
1002        DEFAULT_METHOD: 'forEach'
1003      },
1004      
1005      initialize: function(object, method, args) {
1006        this._object = object;
1007        this._method = method || this.klass.DEFAULT_METHOD;
1008        this._args   = (args || []).slice();
1009      },
1010      
1011      forEach: function(block, context) {
1012        if (!block) return this;
1013        var args = this._args.slice();
1014        args.push(block);
1015        if (context) args.push(context);
1016        return this._object[this._method].apply(this._object, args);
1017      },
1018      
1019      cons:       JS.Enumerable.instanceMethod('forEachCons'),
1020      reverse:    JS.Enumerable.instanceMethod('reverseForEach'),
1021      slice:      JS.Enumerable.instanceMethod('forEachSlice'),
1022      withIndex:  JS.Enumerable.instanceMethod('forEachWithIndex'),
1023      withObject: JS.Enumerable.instanceMethod('forEachWithObject')
1024    })
1025  }
1026}, false);
1027
1028JS.Enumerable.Collection.include(JS.Enumerable, true);
1029
1030JS.Kernel.include({
1031  enumFor: function(method) {
1032    var args   = JS.array(arguments),
1033        method = args.shift();
1034    return new JS.Enumerable.Enumerator(this, method, args);
1035  }
1036}, false);
1037
1038JS.Kernel.define('toEnum', JS.Kernel.instanceMethod('enumFor'), true);
1039
1040
1041JS.LinkedList = new JS.Class('LinkedList', {
1042  include: JS.Enumerable || {},
1043  
1044  initialize: function(array, useNodes) {
1045    this.length = 0;
1046    this.first = this.last = null;
1047    if (!array) return;
1048    for (var i = 0, n = array.length; i < n; i++)
1049      this.push( useNodes ? new this.klass.Node(array[i]) : array[i] );
1050  },
1051  
1052  forEach: function(block, context) {
1053    if (!block) return this.enumFor('forEach');
1054    block = JS.Enumerable.toFn(block);
1055    
1056    var node   = this.first,
1057        next, i, n;
1058    
1059    for (i = 0, n = this.length; i < n; i++) {
1060      next = node.next;
1061      block.call(context || null, node, i);
1062      if (node === this.last) break;
1063      node = next;
1064    }
1065    return this;
1066  },
1067  
1068  at: function(n) {
1069    if (n < 0 || n >= this.length) return undefined;
1070    var node = this.first;
1071    while (n--) node = node.next;
1072    return node;
1073  },
1074  
1075  pop: function() {
1076    return this.length ? this.remove(this.last) : undefined;
1077  },
1078  
1079  shift: function() {
1080    return this.length ? this.remove(this.first) : undefined;
1081  },
1082  
1083  // stubs - should be implemented by concrete list types
1084  insertAfter:  function() {},
1085  push:         function() {},
1086  remove:       function() {},
1087  
1088  extend: {
1089    Node: new JS.Class({
1090      initialize: function(data) {
1091        this.data = data;
1092        this.prev = this.next = this.list = null;
1093      }
1094    })
1095  }
1096});
1097
1098JS.LinkedList.Doubly = new JS.Class('LinkedList.Doubly', JS.LinkedList, {
1099  insertAt: function(n, newNode) {
1100    if (n < 0 || n >= this.length) return;
1101    this.insertBefore(this.at(n), newNode);
1102  },
1103  
1104  unshift: function(newNode) {
1105    this.length > 0
1106        ? this.insertBefore(this.first, newNode)
1107        : this.push(newNode);
1108  },
1109  
1110  insertBefore: function() {}
1111});
1112
1113JS.LinkedList.insertTemplate = function(prev, next, pos) {
1114  return function(node, newNode) {
1115    if (node.list !== this) return;
1116    newNode[prev] = node;
1117    newNode[next] = node[next];
1118    node[next] = (node[next][prev] = newNode);
1119    if (newNode[prev] === this[pos]) this[pos] = newNode;
1120    newNode.list = this;
1121    this.length++;
1122  };
1123};
1124
1125JS.LinkedList.Doubly.Circular = new JS.Class('LinkedList.Doubly.Circular', JS.LinkedList.Doubly, {
1126  insertAfter: JS.LinkedList.insertTemplate('prev', 'next', 'last'),
1127  insertBefore: JS.LinkedList.insertTemplate('next', 'prev', 'first'),
1128  
1129  push: function(newNode) {
1130    if (this.length)
1131      return this.insertAfter(this.last, newNode);
1132    
1133    this.first = this.last =
1134        newNode.prev = newNode.next = newNode;
1135    
1136    newNode.list = this;
1137    this.length = 1;
1138  },
1139  
1140  remove: function(removed) {
1141    if (removed.list !== this || this.length === 0) return null;
1142    if (this.length > 1) {
1143      removed.prev.next = removed.next;
1144      removed.next.prev = removed.prev;
1145      if (removed === this.first) this.first = removed.next;
1146      if (removed === this.last) this.last = removed.prev;
1147    } else {
1148      this.first = this.last = null;
1149    }
1150    removed.prev = removed.next = removed.list = null;
1151    this.length--;
1152    return removed;
1153  }
1154});
1155
1156
1157JS.Hash = new JS.Class('Hash', {
1158  include: JS.Enumerable || {},
1159  
1160  extend: {
1161    Pair: new JS.Class({
1162      include: JS.Comparable || {},
1163      
1164      setKey: function(key) {
1165        this[0] = this.key = key;
1166      },
1167      
1168      hasKey: function(key) {
1169        var my = this.key;
1170        return my.equals ? my.equals(key) : my === key;
1171      },
1172      
1173      setValue: function(value) {
1174        this[1] = this.value = value;
1175      },
1176      
1177      hasValue: function(value) {
1178        var my = this.value;
1179        return my.equals ? my.equals(value) : my === value;
1180      },
1181      
1182      compareTo: function(other) {
1183        return this.key.compareTo
1184            ? this.key.compareTo(other.key)
1185            : (this.key < other.key ? -1 : (this.key > other.key ? 1 : 0));
1186      },
1187      
1188      hash: function() {
1189        var key   = JS.Hash.codeFor(this.key),
1190            value = JS.Hash.codeFor(this.value);
1191        
1192        return [key, value].sort().join('');
1193      }
1194    }),
1195    
1196    codeFor: function(object) {
1197      if (typeof object !== 'object') return String(object);
1198      return JS.isFn(object.hash)
1199          ? object.hash()
1200          : object.toString();
1201    }
1202  },
1203  
1204  initialize: function(object) {
1205    this.clear();
1206    if (!JS.isType(object, Array)) return this.setDefault(object);
1207    for (var i = 0, n = object.length; i < n; i += 2)
1208      this.store(object[i], object[i+1]);
1209  },
1210  
1211  forEach: function(block, context) {
1212    if (!block) return this.enumFor('forEach');
1213    block = JS.Enumerable.toFn(block);
1214    
1215    var hash, bucket, i;
1216    
1217    for (hash in this._buckets) {
1218      if (!this._buckets.hasOwnProperty(hash)) continue;
1219      bucket = this._buckets[hash];
1220      i = bucket.length;
1221      while (i--) block.call(context || null, bucket[i]);
1222    }
1223    return this;
1224  },
1225  
1226  _bucketForKey: function(key, createIfAbsent) {
1227    var hash   = this.klass.codeFor(key),
1228        bucket = this._buckets[hash];
1229    
1230    if (!bucket && createIfAbsent)
1231      bucket = this._buckets[hash] = [];
1232    
1233    return bucket;
1234  },
1235  
1236  _indexInBucket: function(bucket, key) {
1237    var i     = bucket.length,
1238        ident = !!this._compareByIdentity;
1239        
1240    while (i--) {
1241      if (ident ? (bucket[i].key === key) : bucket[i].hasKey(key))
1242        return i;
1243    }
1244    return -1;
1245  },
1246  
1247  assoc: function(key, createIfAbsent) {
1248    var bucket, index, pair;
1249    
1250    bucket = this._bucketForKey(key, createIfAbsent);
1251    if (!bucket) return null;
1252    
1253    index = this._indexInBucket(bucket, key);
1254    if (index > -1) return bucket[index];
1255    if (!createIfAbsent) return null;
1256    
1257    this.size += 1; this.length += 1;
1258    pair = new this.klass.Pair;
1259    pair.setKey(key);
1260    bucket.push(pair);
1261    return pair;
1262  },
1263  
1264  rassoc: function(value) {
1265    var key = this.key(value);
1266    return key ? this.assoc(key) : null;
1267  },
1268  
1269  clear: function() {
1270    this._buckets = {};
1271    this.length = this.size = 0;
1272  },
1273  
1274  compareByIdentity: function() {
1275    this._compareByIdentity = true;
1276  },
1277  
1278  comparesByIdentity: function() {
1279    return !!this._compareByIdentity;
1280  },
1281  
1282  setDefault: function(value) {
1283    this._default = value;
1284    return this;
1285  },
1286  
1287  getDefault: function(key) {
1288    return JS.isFn(this._default)
1289        ? this._default(this, key)
1290        : (this._default || null);
1291  },
1292  
1293  equals: function(other) {
1294    if (!JS.isType(other, JS.Hash) || this.length !== other.length)
1295      return false;
1296    var result = true;
1297    this.forEach(function(pair) {
1298      if (!result) return;
1299      var otherPair = other.assoc(pair.key);
1300      if (otherPair === null || !otherPair.hasValue(pair.value)) result = false;
1301    });
1302    return result;
1303  },
1304  
1305  hash: function() {
1306    var hashes = [];
1307    this.forEach(function(pair) { hashes.push(pair.hash()) });
1308    return hashes.sort().join('');
1309  },
1310  
1311  fetch: function(key, defaultValue) {
1312    var pair = this.assoc(key);
1313    if (pair) return pair.value;
1314    
1315    if (defaultValue === undefined) throw new Error('key not found');
1316    if (JS.isFn(defaultValue)) return defaultValue(key);
1317    return defaultValue;
1318  },
1319  
1320  forEachKey: function(block, context) {
1321    if (!block) return this.enumFor('forEachKey');
1322    block = JS.Enumerable.toFn(block);
1323    
1324    this.forEach(function(pair) {
1325      block.call(context || null, pair.key);
1326    });
1327    return this;
1328  },
1329  
1330  forEachPair: function(block, context) {
1331    if (!block) return this.enumFor('forEachPair');
1332    block = JS.Enumerable.toFn(block);
1333    
1334    this.forEach(function(pair) {
1335      block.call(context || null, pair.key, pair.value);
1336    });
1337    return this;
1338  },
1339  
1340  forEachValue: function(block, context) {
1341    if (!block) return this.enumFor('forEachValue');
1342    block = JS.Enumerable.toFn(block);
1343    
1344    this.forEach(function(pair) {
1345      block.call(context || null, pair.value);
1346    });
1347    return this;
1348  },
1349  
1350  get: function(key) {
1351    var pair = this.assoc(key);
1352    return pair ? pair.value : this.getDefault(key);
1353  },
1354  
1355  hasKey: function(key) {
1356    return !!this.assoc(key);
1357  },
1358  
1359  hasValue: function(value) {
1360    var has = false, ident = !!this._compareByIdentity;
1361    this.forEach(function(pair) {
1362      if ((value.equals && !ident) ? value.equals(pair.value) : value === pair.value)
1363        has = true;
1364    });
1365    return has;
1366  },
1367  
1368  invert: function() {
1369    var hash = new this.klass;
1370    this.forEach(function(pair) {
1371      hash.store(pair.value, pair.key);
1372    });
1373    return hash;
1374  },
1375  
1376  isEmpty: function() {
1377    for (var hash in this._buckets) {
1378      if (this._buckets.hasOwnProperty(hash) && this._buckets[hash].length > 0)
1379        return false;
1380    }
1381    return true;
1382  },
1383  
1384  key: function(value) {
1385    var result = null;
1386    this.forEach(function(pair) {
1387      if (value.equals ? value.equals(pair.value) : (value === pair.value))
1388        result = pair.key;
1389    });
1390    return result;
1391  },
1392  
1393  keys: function() {
1394    var keys = [];
1395    this.forEach(function(pair) { keys.push(pair.key) });
1396    return keys;
1397  },
1398  
1399  merge: function(hash, block, context) {
1400    var newHash = new this.klass;
1401    newHash.update(this);
1402    newHash.update(hash, block, context);
1403    return newHash;
1404  },
1405  
1406  rehash: function() {
1407    var temp = new this.klass;
1408    temp._buckets = this._buckets;
1409    this.clear();
1410    this.update(temp);
1411  },
1412  
1413  remove: function(key, block) {
1414    if (block === undefined) block = null;
1415    var bucket, index, result;
1416    
1417    bucket = this._bucketForKey(key);
1418    if (!bucket) return JS.isFn(block) ? this.fetch(key, block) : this.getDefault(key);
1419    
1420    index = this._indexInBucket(bucket, key);
1421    if (index < 0) return JS.isFn(block) ? this.fetch(key, block) : this.getDefault(key);
1422    
1423    result = bucket[index].value;
1424    bucket.splice(index, 1);
1425    this.size -= 1;
1426    this.length -= 1;
1427    
1428    if (bucket.length === 0)
1429      delete this._buckets[this.klass.codeFor(key)];
1430    
1431    return result;
1432  },
1433  
1434  removeIf: function(block, context) {
1435    if (!block) return this.enumFor('removeIf');
1436    block = JS.Enumerable.toFn(block);
1437    
1438    this.forEach(function(pair) {
1439      if (block.call(context || null, pair))
1440        this.remove(pair.key);
1441    }, this);
1442    return this;
1443  },
1444  
1445  replace: function(hash) {
1446    this.clear();
1447    this.update(hash);
1448  },
1449  
1450  shift: function() {
1451    var keys = this.keys();
1452    if (keys.length === 0) return this.getDefault();
1453    var pair = this.assoc(keys[0]);
1454    this.remove(pair.key);
1455    return pair;
1456  },
1457  
1458  store: function(key, value) {
1459    this.assoc(key, true).setValue(value);
1460    return value;
1461  },
1462  
1463  update: function(hash, block, context) {
1464    var blockGiven = JS.isFn(block);
1465    hash.forEach(function(pair) {
1466      var key = pair.key, value = pair.value;
1467      if (blockGiven && this.hasKey(key))
1468        value = block.call(context || null, key, this.get(key), value);
1469      this.store(key, value);
1470    }, this);
1471  },
1472  
1473  values: function() {
1474    var values = [];
1475    this.forEach(function(pair) { values.push(pair.value) });
1476    return values;
1477  },
1478  
1479  valuesAt: function() {
1480    var i = arguments.length, results = [];
1481    while (i--) results.push(this.get(arguments[i]));
1482    return results;
1483  }
1484});
1485
1486JS.Hash.include({
1487  includes: JS.Hash.instanceMethod('hasKey'),
1488  index:    JS.Hash.instanceMethod('key'),
1489  put:      JS.Hash.instanceMethod('store')
1490}, true);
1491
1492
1493JS.Set = new JS.Class('Set', {
1494  extend: {
1495    forEach: function(list, block, context) {
1496      if (!list || !block) return;
1497      if (list.forEach) return list.forEach(block, context);
1498      for (var i = 0, n = list.length; i < n; i++) {
1499        if (list[i] !== undefined)
1500          block.call(context || null, list[i], i);
1501      }
1502    }
1503  },
1504  
1505  include: JS.Enumerable || {},
1506  
1507  initialize: function(list, block, context) {
1508    this.clear();
1509    if (block) this.klass.forEach(list, function(item) {
1510      this.add(block.call(context || null, item));
1511    }, this);
1512    else this.merge(list);
1513  },
1514  
1515  forEach: function(block, context) {
1516    if (!block) return this.enumFor('forEach');
1517    block = JS.Enumerable.toFn(block);
1518    
1519    this.klass.forEach(this._members, block, context);
1520    return this;
1521  },
1522  
1523  add: function(item) {
1524    if (this.contains(item)) return false;
1525    this._members.push(item);
1526    this.length = this.size = this._members.length;
1527    return true;
1528  },
1529  
1530  classify: function(block, context) {
1531    if (!block) return this.enumFor('classify');
1532    block = JS.Enumerable.toFn(block);
1533    
1534    var classes = new JS.Hash();
1535    this.forEach(function(item) {
1536      var value = block.call(context || null, item);
1537      if (!classes.hasKey(value)) classes.store(value, new this.klass);
1538      classes.get(value).add(item);
1539    }, this);
1540    return classes;
1541  },
1542  
1543  clear: function() {
1544    this._members = [];
1545    this.length = this.size = 0;
1546  },
1547  
1548  complement: function(other) {
1549    var set = new this.klass;
1550    this.klass.forEach(other, function(item) {
1551      if (!this.contains(item)) set.add(item);
1552    }, this);
1553    return set;
1554  },
1555  
1556  contains: function(item) {
1557    return this._indexOf(item) !== -1;
1558  },
1559  
1560  difference: function(other) {
1561    other = JS.isType(other, JS.Set) ? other : new JS.Set(other);
1562    var set = new this.klass;
1563    this.forEach(function(item) {
1564      if (!other.contains(item)) set.add(item);
1565    });
1566    return set;
1567  },
1568  
1569  divide: function(block, context) {
1570    if (!block) return this.enumFor('divide');
1571    block = JS.Enumerable.toFn(block);
1572    
1573    var classes = this.classify(block, context),
1574        sets    = new this.klass;
1575    
1576    classes.forEachValue(sets.method('add'));
1577    return sets;
1578  },
1579  
1580  equals: function(other) {
1581    if (this.length !== other.length || !JS.isType(other, JS.Set)) return false;
1582    var result = true;
1583    this.forEach(function(item) {
1584      if (!result) return;
1585      if (!other.contains(item)) result = false;
1586    });
1587    return result;
1588  },
1589  
1590  hash: function() {
1591    var hashes = [];
1592    this.forEach(function(object) { hashes.push(JS.Hash.codeFor(object)) });
1593    return hashes.sort().join('');
1594  },
1595  
1596  flatten: function(set) {
1597    var copy = new this.klass;
1598    copy._members = this._members;
1599    if (!set) { set = this; set.clear(); }
1600    copy.forEach(function(item) {
1601      if (JS.isType(item, JS.Set)) item.flatten(set);
1602      else set.add(item);
1603    });
1604    return set;
1605  },
1606  
1607  intersection: function(other) {
1608    var set = new this.klass;
1609    this.klass.forEach(other, function(item) {
1610      if (this.contains(item)) set.add(item);
1611    }, this);
1612    return set;
1613  },
1614  
1615  isEmpty: function() {
1616    return this._members.length === 0;
1617  },
1618  
1619  isProperSubset: function(other) {
1620    return this._members.length < other._members.length && this.isSubset(other);
1621  },
1622  
1623  isProperSuperset: function(other) {
1624    return this._members.length > other._members.length && this.isSuperset(other);
1625  },
1626  
1627  isSubset: function(other) {
1628    var result = true;
1629    this.forEach(function(item) {
1630      if (!result) return;
1631      if (!other.contains(item)) result = false;
1632    });
1633    return result;
1634  },
1635  
1636  isSuperset: function(other) {
1637    return other.isSubset(this);
1638  },
1639  
1640  merge: function(list) {
1641    this.klass.forEach(list, function(item) { this.add(item) }, this);
1642  },
1643  
1644  product: function(other) {
1645    var pairs = new JS.Set;
1646    this.forEach(function(item) {
1647      this.klass.forEach(other, function(partner) {
1648        pairs.add([item, partner]);
1649      });
1650    }, this);
1651    return pairs;
1652  },
1653  
1654  rebuild: function() {
1655    var members = this._members;
1656    this.clear();
1657    this.merge(members);
1658  },
1659  
1660  remove: function(item) {
1661    var index = this._indexOf(item);
1662    if (index === -1) return;
1663    this._members.splice(index, 1);
1664    this.length = this.size = this._members.length;
1665  },
1666  
1667  removeIf: function(block, context) {
1668    if (!block) return this.enumFor('removeIf');
1669    block = JS.Enumerable.toFn(block);
1670    
1671    var members = this._members,
1672        i       = members.length;
1673    
1674    while (i--) {
1675      if (block.call(context || null, members[i]))
1676        this.remove(members[i]);
1677    }
1678    return this;
1679  },
1680  
1681  replace: function(other) {
1682    this.clear();
1683    this.merge(other);
1684  },
1685  
1686  subtract: function(list) {
1687    this.klass.forEach(list, function(item) {
1688      this.remove(item);
1689    }, this);
1690  },
1691  
1692  union: function(other) {
1693    var set = new this.klass;
1694    set.merge(this);
1695    set.merge(other);
1696    return set;
1697  },
1698  
1699  xor: function(other) {
1700    var set = new JS.Set(other);
1701    this.forEach(function(item) {
1702      set[set.contains(item) ? 'remove' : 'add'](item);
1703    });
1704    return set;
1705  },
1706  
1707  _indexOf: function(item) {
1708    var i     = this._members.length,
1709        equal = JS.Enumerable.areEqual;
1710    
1711    while (i--) {
1712      if (equal(item, this._members[i])) return i;
1713    }
1714    return -1;
1715  }
1716});
1717
1718JS.Set.include({
1719  n:  JS.Set.instanceMethod('intersection'),
1720  u:  JS.Set.instanceMethod('union'),
1721  x:  JS.Set.instanceMethod('product')
1722}, false);
1723
1724JS.SortedSet = new JS.Class('SortedSet', JS.Set, {
1725  extend: {
1726    compare: function(one, another) {
1727      return JS.isType(one, Object)
1728          ? one.compareTo(another)
1729          : (one < another ? -1 : (one > another ? 1 : 0));
1730    }
1731  },
1732  
1733  add: function(item) {
1734    var point = this._indexOf(item, true);
1735    if (point === null) return;
1736    this._members.splice(point, 0, item);
1737    this.length = this.size = this._members.length;
1738  },
1739  
1740  _indexOf: function(item, insertionPoint) {
1741    var items   = this._members,
1742        n       = items.length,
1743        i       = 0,
1744        d       = n,
1745        compare = this.klass.compare,
1746        equal   = JS.Enumerable.areEqual,
1747        found;
1748    
1749    if (n === 0) return insertionPoint ? 0 : -1;
1750    
1751    if (compare(item, items[0]) < 1)   { d = 0; i = 0; }
1752    if (compare(item, items[n-1]) > 0) { d = 0; i = n; }
1753    
1754    while (!equal(item, items[i]) && d > 0.5) {
1755      d = d / 2;
1756      i += (compare(item, items[i]) > 0 ? 1 : -1) * Math.round(d);
1757      if (i > 0 && compare(item, items[i-1]) > 0 && compare(item, items[i]) < 1) d = 0;
1758    }
1759    
1760    // The pointer will end up at the start of any homogenous section. Step
1761    // through the section until we find the needle or until the section ends.
1762    while (items[i] && !equal(item, items[i]) &&
1763        compare(item, items[i]) === 0) i += 1;
1764    
1765    found = equal(item, items[i]);
1766    return insertionPoint
1767        ? (found ? null : i)
1768        : (found ? i : -1);
1769  }
1770});
1771
1772JS.HashSet = new JS.Class('HashSet', JS.Set, {
1773  forEach: function(block, context) {
1774    if (!block) return this.enumFor('forEach');
1775    block = JS.Enumerable.toFn(block);
1776    
1777    this._members.forEachKey(block, context);
1778    return this;
1779  },
1780  
1781  add: function(item) {
1782    if (this.contains(item)) return false;
1783    this._members.store(item, true);
1784    this.length = this.size = this._members.length;
1785    return true;
1786  },
1787  
1788  clear: function() {
1789    this._members = new JS.Hash();
1790    this.size = this.length = 0;
1791  },
1792  
1793  contains: function(item) {
1794    return this._members.hasKey(item);
1795  },
1796  
1797  rebuild: function() {
1798    this._members.rehash();
1799    this.length = this.size = this._members.length;
1800  },
1801  
1802  remove: function(item) {
1803    this._members.remove(item);
1804    this.length = this.size = this._members.length;
1805  },
1806  
1807  removeIf: function(block, context) {
1808    if (!block) return this.enumFor('removeIf');
1809    block = JS.Enumerable.toFn(block);
1810    
1811    this._members.removeIf(fu

Large files files are truncated, but you can click here to view the full file