mohayonao/SCScript

View on GitHub
src/sc/classlib/Core/Object.js

Summary

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

  var $ = sc.lang.$;
  var $nil   = $.nil;
  var $true  = $.true;
  var $false = $.false;
  var $int0  = $.int0;
  var $int1  = $.int1;
  var strlib = sc.libs.strlib;

  var SCArray = $("Array");
  var SCRoutine = $("Routine");
  var SCAssociation = $("Association");

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

    builder.addMethod("toString", function() {
      return String(sc.libs.strlib.article(this.__className) + " " + this.__className);
    });

    builder.addMethod("toJSON", function() {
      return JSON.stringify({ class: this.__className, hash: this.__hash });
    });

    builder.addMethod("__num__", function() {
      throw new Error(strlib.format("#{0} cannot be converted to a Number.", this.__className));
    });

    builder.addMethod("__int__", function() {
      return this.__num__()|0;
    });

    builder.addMethod("__bool__", function() {
      throw new Error(strlib.format("#{0} cannot be converted to a Boolean.", this.__className));
    });

    builder.addMethod("__sym__", function() {
      throw new Error(strlib.format("#{0} cannot be converted to a Symbol.", this.__className));
    });

    builder.addMethod("__str__", function() {
      return String(this);
    });

    // TODO: implements $new
    // TODO: implements $newCopyArgs

    builder.doesNotUnderstand("newFrom");

    // TODO: implements dump

    builder.addMethod("post", function() {
      this.asString().post();
      return this;
    });

    builder.addMethod("postln", function() {
      this.asString().postln();
      return this;
    });

    builder.addMethod("postc", function() {
      this.asString().postc();
      return this;
    });

    builder.addMethod("postcln", function() {
      this.asString().postcln();
      return this;
    });

    // TODO: implements postcs
    // TODO: implements totalFree
    // TODO: implements largestFreeBlock
    // TODO: implements gcDumpGrey
    // TODO: implements gcDumpSet
    // TODO: implements gcInfo
    // TODO: implements gcSanity
    // TODO: implements canCallOS

    builder.addMethod("size", function() {
      return $int0;
    });

    builder.addMethod("indexedSize", function() {
      return $int0;
    });

    builder.addMethod("flatSize", function() {
      return $int1;
    });

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

    builder.addMethod("generate", {
      args: "function; state"
    }, function($function, $state) {
      this.do($function);
      return $state;
    });

    builder.addMethod("class", function() {
      return this.__class;
    });

    builder.addMethod("isKindOf", function($aClass) {
      return $.Boolean(this instanceof $aClass.__Spec);
    });

    builder.addMethod("isMemberOf", function($aClass) {
      return $.Boolean(this.__class === $aClass);
    });

    function sym2str($aSymbol) {
      return $aSymbol ? $aSymbol.__sym__() : /* istanbul ignore next */ "";
    }

    function getFunction($this, methodName) {
      if (/^([a-z]\w*|[-+*\/%<=>!?&|@]+)$/.test(methodName)) {
        if (typeof $this[methodName] === "function") {
          return $this[methodName];
        }
      }
      return null;
    }

    function respondsTo($this, $aSymbol) {
      var func = getFunction($this, sym2str($aSymbol));
      return func && !func.__errorType;
    }

    builder.addMethod("respondsTo", function($aSymbol) {
      var $this = this;
      if ($aSymbol && $aSymbol.isSequenceableCollection().__bool__()) {
        return $.Boolean($aSymbol.asArray()._.every(function($aSymbol) {
          return $.Boolean(respondsTo($this, $aSymbol)).__bool__();
        }));
      }
      return $.Boolean(respondsTo(this, $aSymbol));
    });

    function performMsg($this, msg) {
      var methodName = sym2str(msg[0]);
      var func = getFunction($this, methodName);

      if (func) {
        return func.apply($this, msg.slice(1));
      }

      throw new Error(strlib.format("Message '#{0}' is not understood.", methodName));
    }

    builder.addMethod("performMsg", function($msg) {
      return performMsg(this, $msg ? $msg.asArray()._ : /* istanbul ignore next */ []);
    });

    builder.addMethod("perform", function() {
      return performMsg(this, _.toArray(arguments));
    });

    builder.addMethod("performList", function($selector, $arglist) {
      return performMsg(this, [ $selector ].concat(
        $arglist ? $arglist.asArray()._ : /* istanbul ignore next */ []
      ));
    });

    builder.addMethod("functionPerformList");

    // TODO: implements superPerform
    // TODO: implements superPerformList

    builder.addMethod("tryPerform", function($selector) {
      if (respondsTo(this, $selector)) {
        return performMsg(this, _.toArray(arguments));
      }
      return $nil;
    });

    builder.addMethod("multiChannelPerform", function($selector) {
      var list, items, length, i, args, $obj, iter;
      items = [ this ].concat(_.toArray(arguments).slice(1));
      length = Math.max.apply(null, items.map(function($_) {
        return $_.size().__int__();
      }));
      iter = function($_) {
        return $_.wrapAt ? $_.wrapAt($.Integer(i)) : $_;
      };
      list = new Array(length);
      for (i = 0; i < length; ++i) {
        args = items.map(iter);
        $obj = args[0];
        args[0] = $selector;
        list[i] = performMsg($obj, args);
      }
      return $.Array(list);
    });

    // TODO: implements performWithEnvir
    // TODO: implements performKeyValuePairs

    var copy = function(obj) {
      var copied = obj;

      if (Array.isArray(obj)) {
        copied = obj.slice();
      } else if (obj && obj.constructor === Object) {
        copied = {};
        Object.keys(obj).forEach(function(key) {
          copied[key] = obj[key];
        });
      }

      return copied;
    };

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

    // TODO: implements contentsCopy

    builder.addMethod("shallowCopy", function() {
      var a = new this.__Spec([]);

      Object.keys(this).forEach(function(key) {
        a[key] = copy(this[key]);
      }, this);

      if (this._ === this) {
        a._ = a;
      }

      return a;
    });

    // TODO: implements copyImmutable
    // TODO: implements deepCopy

    builder.addMethod("dup", {
      args: "n=2"
    }, function($n) {
      var $this = this;
      var array, i, n;

      if ($n.isSequenceableCollection().__bool__()) {
        return SCArray.fillND($n, $.Func(function() {
          return $this.copy();
        }));
      }

      n = $n.__int__();
      array = new Array(n);
      for (i = 0; i < n; ++i) {
        array[i] = this.copy();
      }

      return $.Array(array);
    });

    builder.addMethod("!", function($n) {
      return this.dup($n);
    });

    builder.addMethod("poll", function() {
      return this.value();
    });

    builder.addMethod("value");
    builder.addMethod("valueArray");
    builder.addMethod("valueEnvir");
    builder.addMethod("valueArrayEnvir");

    builder.addMethod("==", function($obj) {
      return this ["==="] ($obj);
    });

    builder.addMethod("!=", function($obj) {
      return (this ["=="] ($obj)).not();
    });

    builder.addMethod("===", function($obj) {
      return $.Boolean(this === $obj);
    });

    builder.addMethod("!==", function($obj) {
      return $.Boolean(this !== $obj);
    });

    builder.addMethod("equals", {
      args: "that; properties"
    }, function($that, $properties) {
      var $this = this;
      if (this === $that) {
        return $true;
      }
      if (this.respondsTo($properties).__bool__() && $that.respondsTo($properties).__bool__()) {
        return $.Boolean($properties.asArray()._.every(function($_) {
          return performMsg($this, [ $_ ]) ["=="] (performMsg($that, [ $_ ])).__bool__();
        }));
      }
      return this ["=="] ($that);
    });

    // TODO: implements compareObject
    // TODO: implements instVarHash

    builder.addMethod("basicHash", function() {
      return $.Integer(this.__hash);
    });

    builder.addMethod("hash", function() {
      return $.Integer(this.__hash);
    });

    builder.addMethod("identityHash", function() {
      return $.Integer(this.__hash);
    });

    builder.addMethod("->", function($obj) {
      return SCAssociation.new(this, $obj);
    });

    builder.addMethod("next");
    builder.addMethod("reset");

    builder.addMethod("first", {
      args: "inval"
    }, function($inval) {
      this.reset();
      return this.next($inval);
    });

    builder.addMethod("iter", function() {
      return $("OneShotStream").new(this);
    });

    builder.addMethod("stop");
    builder.addMethod("free");
    builder.addMethod("clear");
    builder.addMethod("removedFromScheduler");
    builder.addMethod("isPlaying", sc.FALSE);

    builder.addMethod("embedInStream", function() {
      return this.yield();
    });

    builder.addMethod("cyc", {
      args: "n=inf"
    }, function($n) {
      var $this = this;
      return SCRoutine.new($.Function(function() {
        var $inval;
        return [
          function(_arg0) {
            $inval = _arg0;
            return $n.do($.Function(function() {
              return [
                function() {
                  $inval = $this.embedInStream($inval);
                  return $inval;
                },
                function() {
                  return $this.reset();
                }
              ];
            }, null, null));
          },
          function() {
            $inval = null;
          }
        ];
      }, null, []));
    });

    builder.addMethod("fin", {
      args: "n=1"
    }, function($n) {
      var $this = this;
      return SCRoutine.new($.Function(function() {
        var $inval;
        return [
          function(_arg0) {
            var $item;
            $inval = _arg0;
            return $n.do($.Function(function() {
              return [
                function() {
                  $item = $this.next($inval);
                  return $item;
                },
                function() {
                  if ($item === $nil) {
                    $nil.alwaysYield();
                  }
                  return $nil;
                },
                function() {
                  $inval = $item.yield();
                  return $inval;
                }
              ];
            }, null, null));
          },
          function() {
            $inval = null;
          }
        ];
      }, null, []));
    });

    builder.addMethod("repeat", {
      args: "repeats=inf"
    }, function($repeats) {
      return $("Pn").new(this, $repeats).asStream();
    });

    builder.addMethod("loop", function() {
      return this.repeat($.Float(Infinity));
    });

    builder.addMethod("asStream");

    builder.addMethod("streamArg", {
      args: "embed=false"
    }, function($embed) {
      var $this = this;
      if ($embed === $true) {
        return SCRoutine.new($.Function(function() {
          var $inval;
          return [
            function(_arg) {
              $inval = _arg;
              return $this.embedInStream($inval);
            },
            function() {
              $inval = null;
            }
          ];
        }, null, []));
      } else {
        return SCRoutine.new($.Func(function() {
          return $.Func(function() {
            return $this.yield();
          }).loop();
        }));
      }
    });

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

    builder.addMethod("composeEvents", {
      args: "event"
    }, function($event) {
      return $event.copy();
    });

    builder.addMethod("finishEvent");
    builder.addMethod("atLimit", sc.FALSE);
    builder.addMethod("isRest", sc.FALSE);
    builder.addMethod("threadPlayer");
    builder.addMethod("threadPlayer_");
    builder.addMethod("?");
    builder.addMethod("??");

    builder.addMethod("!?", function($obj) {
      return $obj.value(this);
    });

    builder.addMethod("isNil", sc.FALSE);
    builder.addMethod("notNil", sc.TRUE);
    builder.addMethod("isNumber", sc.FALSE);
    builder.addMethod("isInteger", sc.FALSE);
    builder.addMethod("isFloat", sc.FALSE);
    builder.addMethod("isSequenceableCollection", sc.FALSE);
    builder.addMethod("isCollection", sc.FALSE);
    builder.addMethod("isArray", sc.FALSE);
    builder.addMethod("isString", sc.FALSE);
    builder.addMethod("containsSeqColl", sc.FALSE);
    builder.addMethod("isValidUGenInput", sc.FALSE);
    builder.addMethod("isException", sc.FALSE);
    builder.addMethod("isFunction", sc.FALSE);

    builder.addMethod("matchItem", {
      args: "item"
    }, function($item) {
      return this ["==="] ($item);
    });

    builder.addMethod("trueAt", sc.FALSE);

    builder.addMethod("falseAt", {
      args: "key"
    }, function($key) {
      return this.trueAt($key).not();
    });

    // TODO: implements pointsTo
    // TODO: implements mutable
    // TODO: implements frozen
    // TODO: implements halt
    // TODO: implements primitiveFailed
    // TODO: implements reportError
    // TODO: implements subclassResponsibility
    // TODO: implements doesNotUnderstand
    // TODO: implements shouldNotImplement
    // TODO: implements outOfContextReturn
    // TODO: implements immutableError
    // TODO: implements deprecated
    // TODO: implements mustBeBoolean
    // TODO: implements notYetImplemented
    // TODO: implements dumpBackTrace
    // TODO: implements getBackTrace
    // TODO: implements throw

    builder.addMethod("species", function() {
      return this.class();
    });

    builder.addMethod("asCollection", function() {
      return $.Array([ this ]);
    });

    builder.addMethod("asSymbol", function() {
      return this.asString().asSymbol();
    });

    builder.addMethod("asString", function() {
      return $.String(String(this));
    });

    // TODO: implements asCompileString
    // TODO: implements cs
    // TODO: implements printClassNameOn
    // TODO: implements printOn
    // TODO: implements storeOn
    // TODO: implements storeParamsOn
    // TODO: implements simplifyStoreArgs
    // TODO: implements storeArgs
    // TODO: implements storeModifiersOn

    builder.addMethod("as", {
      args: "aSimilarClass"
    }, function($aSimilarClass) {
      return $aSimilarClass.newFrom(this);
    });

    builder.addMethod("dereference");

    builder.addMethod("reference", function() {
      return $.Ref(this);
    });

    builder.addMethod("asRef", function() {
      return $.Ref(this);
    });

    builder.addMethod("asArray", function() {
      return this.asCollection().asArray();
    });

    builder.addMethod("asSequenceableCollection", function() {
      return this.asArray();
    });

    builder.addMethod("rank", function() {
      return $int0;
    });

    builder.addMethod("deepCollect", {
      args: "depth; function; index; rank"
    }, function($depth, $function, $index, $rank) {
      return $function.value(this, $index, $rank);
    });

    builder.addMethod("deepDo", {
      args: "depth; function; index; rank"
    }, function($depth, $function, $index, $rank) {
      $function.value(this, $index, $rank);
      return this;
    });

    builder.addMethod("slice");

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

    builder.addMethod("unbubble");

    builder.addMethod("bubble", {
      args: "depth; levels"
    }, function($depth, $levels) {
      var levels, a;

      levels = $levels.__int__();
      if (levels <= 1) {
        a = [ this ];
      } else {
        a = [
          this.bubble($depth, $.Integer(levels - 1))
        ];
      }

      return $.Array(a);
    });

    builder.addMethod("obtain", {
      args: "index; default"
    }, function($index, $default) {
      if ($index.__num__() === 0) {
        return this;
      } else {
        return $default;
      }
    });

    builder.addMethod("instill", {
      args: "index; item; default"
    }, function($index, $item, $default) {
      if ($index.__num__() === 0) {
        return $item;
      } else {
        return this.asArray().instill($index, $item, $default);
      }
    });

    builder.addMethod("addFunc", {
      args: "*functions"
    }, function($$functions) {
      return $("FunctionList").new(this ["++"] ($$functions));
    });

    builder.addMethod("removeFunc", function($function) {
      if (this === $function) {
        return $nil;
      }
      return this;
    });

    builder.addMethod("replaceFunc", {
      args: "find; replace"
    }, function($find, $replace) {
      if (this === $find) {
        return $replace;
      }
      return this;
    });

    // TODO: implements addFuncTo
    // TODO: implements removeFuncFrom

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

      $.Func(function() {
        return $this.value();
      }).while($.Func(function() {
        return $body.value();
      }));

      return this;
    });

    builder.addMethod("switch", function() {
      var args, i, imax;

      args = _.toArray(arguments);
      for (i = 0, imax = args.length >> 1; i < imax; i++) {
        if (this ["=="] (args[i * 2]).__bool__()) {
          return args[i * 2 + 1].value();
        }
      }

      if (args.length % 2 === 1) {
        return args[args.length - 1].value();
      }

      return $nil;
    });

    builder.addMethod("yield", function() {
      sc.lang.bytecode.yield(this.value());
      return $nil;
    });

    builder.addMethod("alwaysYield", function() {
      sc.lang.bytecode.alwaysYield(this.value());
      return $nil;
    });

    builder.addMethod("yieldAndReset", function($reset) {
      if (!$reset || $reset === $true) {
        sc.lang.bytecode.yieldAndReset(this.value());
      } else {
        sc.lang.bytecode.yield(this.value());
      }
      return $nil;
    });

    // TODO: implements idle
    // TODO: implements $initClass
    // TODO: implements dependants
    // TODO: implements changed
    // TODO: implements addDependant
    // TODO: implements removeDependant
    // TODO: implements release
    // TODO: implements releaseDependants
    // TODO: implements update
    // TODO: implements addUniqueMethod
    // TODO: implements removeUniqueMethods
    // TODO: implements removeUniqueMethod
    // TODO: implements inspect
    // TODO: implements inspectorClass
    // TODO: implements inspector
    // TODO: implements crash
    // TODO: implements stackDepth
    // TODO: implements dumpStack
    // TODO: implements dumpDetailedBackTrace
    // TODO: implements freeze

    builder.addMethod("&", function($that) {
      return this.$("bitAnd", [ $that ]);
    });

    builder.addMethod("|", function($that) {
      return this.$("bitOr", [ $that ]);
    });

    builder.addMethod("%", function($that) {
      return this.$("mod", [ $that ]);
    });

    builder.addMethod("**", function($that) {
      return this.$("pow", [ $that ]);
    });

    builder.addMethod("<<", function($that) {
      return this.$("leftShift", [ $that ]);
    });

    builder.addMethod(">>", function($that) {
      return this.$("rightShift", [ $that ]);
    });

    builder.addMethod("+>>", function($that) {
      return this.$("unsignedRightShift" , [ $that ]);
    });

    builder.addMethod("<!", function($that) {
      return this.$("firstArg", [ $that ]);
    });

    builder.addMethod("asInt", function() {
      return this.asInteger();
    });

    builder.addMethod("blend", {
      args: "that; blendFrac=0.5"
    }, function($that, $blendFrac) {
      return this.$("+", [ $blendFrac.$("*", [ $that.$("-", [ this ]) ]) ]);
    });

    builder.addMethod("blendAt", {
      args: "index; method=\\clipAt"
    }, function($index, $method) {
      var $iMin;

      $iMin = $index.$("roundUp", [ $int1 ]).asInteger().__dec__();
      return this.perform($method, $iMin).blend(
        this.perform($method, $iMin.__inc__()),
        $index.$("absdif", [ $iMin ])
      );
    });

    builder.addMethod("blendPut", {
      args: "index; val; method=\\wrapPut"
    }, function($index, $val, $method) {
      var $iMin, $ratio;

      $iMin = $index.$("floor").asInteger();
      $ratio = $index.$("absdif", [ $iMin ]);
      this.perform($method, $iMin, $val.$("*", [ $int1 ["-"] ($ratio) ]));
      this.perform($method, $iMin.__inc__(), $val.$("*", [ $ratio ]));

      return this;
    });

    builder.addMethod("fuzzyEqual", {
      args: "that; precision=1.0"
    }, function($that, $precision) {
      return $.Float(0.0).max(
        $.Float(1.0) ["-"] (
          this.$("-", [ $that ]).$("abs").$("/", [ $precision ])
        )
      );
    });

    builder.addMethod("isUGen", sc.FALSE);

    builder.addMethod("numChannels", function() {
      return $int1;
    });

    builder.addMethod("pair", {
      args: "that"
    }, function($that) {
      return $.Array([ this, $that ]);
    });

    builder.addMethod("pairs", {
      args: "that"
    }, function($that) {
      var $list;

      $list = $.Array();
      this.asArray().do($.Func(function($a) {
        return $that.asArray().do($.Func(function($b) {
          $list = $list.add($a.asArray() ["++"] ($b));
          return $list;
        }));
      }));

      return $list;
    });

    builder.addMethod("awake", {
      args: "beats"
    }, function($beats) {
      return this.next($beats);
    });

    builder.addMethod("beats_");
    builder.addMethod("clock_");

    builder.addMethod("performBinaryOpOnSomething", function($aSelector) {
      var aSelector;

      aSelector = $aSelector.__sym__();
      if (aSelector === "==") {
        return $false;
      }
      if (aSelector === "!=") {
        return $true;
      }

      throw new Error(strlib.format("binary operator '#{0}' failed.", aSelector));
    });

    builder.addMethod("performBinaryOpOnSimpleNumber", function($aSelector, $thig, $adverb) {
      return this.performBinaryOpOnSomething($aSelector, $thig, $adverb);
    });
    builder.addMethod("performBinaryOpOnSignal", function($aSelector, $thig, $adverb) {
      return this.performBinaryOpOnSomething($aSelector, $thig, $adverb);
    });
    builder.addMethod("performBinaryOpOnComplex", function($aSelector, $thig, $adverb) {
      return this.performBinaryOpOnSomething($aSelector, $thig, $adverb);
    });
    builder.addMethod("performBinaryOpOnSeqColl", function($aSelector, $thig, $adverb) {
      return this.performBinaryOpOnSomething($aSelector, $thig, $adverb);
    });
    builder.addMethod("performBinaryOpOnUGen", function($aSelector, $thig, $adverb) {
      return this.performBinaryOpOnSomething($aSelector, $thig, $adverb);
    });

    // TODO: implements writeDefFile

    builder.addMethod("isInputUGen", sc.FALSE);
    builder.addMethod("isOutputUGen", sc.FALSE);
    builder.addMethod("isControlUGen", sc.FALSE);
    builder.addMethod("source");
    builder.addMethod("asUGenInput");
    builder.addMethod("asControlInput");

    builder.addMethod("asAudioRateInput", function() {
      if (this.rate().__sym__() !== "audio") {
        return $("K2A").ar(this);
      }
      return this;
    });

    // TODO: implements slotSize
    // TODO: implements slotAt
    // TODO: implements slotPut
    // TODO: implements slotKey
    // TODO: implements slotIndex
    // TODO: implements slotsDo
    // TODO: implements slotValuesDo
    // TODO: implements getSlots
    // TODO: implements setSlots
    // TODO: implements instVarSize
    // TODO: implements instVarAt
    // TODO: implements instVarPut
    // TODO: implements writeArchive
    // TODO: implements $readArchive
    // TODO: implements asArchive
    // TODO: implements initFromArchive
    // TODO: implements archiveAsCompileString
    // TODO: implements archiveAsObject
    // TODO: implements checkCanArchive
    // TODO: implements writeTextArchive
    // TODO: implements $readTextArchive
    // TODO: implements asTextArchive
    // TODO: implements getContainedObjects
    // TODO: implements writeBinaryArchive
    // TODO: implements $readBinaryArchive
    // TODO: implements asBinaryArchive
    // TODO: implements genNext
    // TODO: implements genCurrent
    // TODO: implements $classRedirect
    // TODO: implements help

    builder.addMethod("processRest");
  });
});