<commit_before>(function(){

  var Spine;
  if (typeof exports !== "undefined") {
    Spine = exports;
  } else {
    Spine = this.Spine = {};
  }

  Spine.version = "0.0.4";

  var $ = Spine.$ = this.jQuery || this.Zepto || function(){ return arguments[0]; };

  var makeArray = Spine.makeArray = function(args){
    return Array.prototype.slice.call(args, 0);
  };

  // Shim Array, as these functions aren't in IE
  if (typeof Array.prototype.indexOf === "undefined")
    Array.prototype.indexOf = function(value){
      for ( var i = 0; i < this.length; i++ )
    		if ( this[ i ] === value )
    			return i;
    	return -1;
    };

  var Events = Spine.Events = {
    bind: function(ev, callback) {
      var evs   = ev.split(" ");
      var calls = this._callbacks || (this._callbacks = {});

      for (var i=0; i < evs.length; i++)
        (this._callbacks[evs[i]] || (this._callbacks[evs[i]] = [])).push(callback);

      return this;
    },

    trigger: function() {
      var args = makeArray(arguments);
      var ev   = args.shift();

      var list, calls, i, l;
      if (!(calls = this._callbacks)) return false;
      if (!(list  = this._callbacks[ev])) return false;

      for (i = 0, l = list.length; i < l; i++)
        if (list[i].apply(this, args) === false)
          break;

      return true;
    },

    unbind: function(ev, callback){
      if ( !ev ) {
        this._callbacks = {};
        return this;
      }

      var list, calls, i, l;
      if (!(calls = this._callbacks)) return this;
      if (!(list  = this._callbacks[ev])) return this;

      if ( !callback ) {
        delete this._callbacks[ev];
        return this;
      }

      for (i = 0, l = list.length; i < l; i++)
        if (callback === list[i]) {
          list.splice(i, 1);
          break;
        }

      return this;
    }
  };

  var Log = Spine.Log = {
    trace: true,

    logPrefix: "(App)",

    log: function(){
      if ( !this.trace ) return;
      if (typeof console == "undefined") return;
      var args = makeArray(arguments);
      if (this.logPrefix) args.unshift(this.logPrefix);
      console.log.apply(console, args);
      return this;
    }
  };

  // Classes (or prototypial inheritors)

  if (typeof Object.create !== "function")
      Object.create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
      };

  var moduleKeywords = ["included", "extended"];

  var Class = Spine.Class = {
    inherited: function(){},
    created: function(){},

    prototype: {
      initialize: function(){},
      init: function(){}
    },

    create: function(include, extend){
      var object = Object.create(this);
      object.parent    = this;
      object.prototype = object.fn = Object.create(this.prototype);

      if (include) object.include(include);
      if (extend)  object.extend(extend);

      object.created();
      this.inherited(object);
      return object;
    },

    init: function(){
      var instance = Object.create(this.prototype);
      instance.parent = this;

      instance.initialize.apply(instance, arguments);
      instance.init.apply(instance, arguments);
      return instance;
    },

    proxy: function(func){
      var thisObject = this;
      return(function(){
        return func.apply(thisObject, arguments);
      });
    },

    proxyAll: function(){
      var functions = makeArray(arguments);
      for (var i=0; i < functions.length; i++)
        this[functions[i]] = this.proxy(this[functions[i]]);
    },

    include: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this.fn[key] = obj[key];

      var included = obj.included;
      if (included) included.apply(this);
      return this;
    },

    extend: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this[key] = obj[key];

      var extended = obj.extended;
      if (extended) extended.apply(this);
      return this;
    }
  };

  Class.prototype.proxy    = Class.proxy;
  Class.prototype.proxyAll = Class.proxyAll;
  Class.inst               = Class.init;
  Class.sub                = Class.create;

  // Models

  Spine.guid = function(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    }).toUpperCase();
  };

  var Model = Spine.Model = Class.create();

  Model.extend(Events);

  Model.extend({
    setup: function(name, atts){
      var model = Model.sub();
      if (name) model.name = name;
      if (atts) model.attributes = atts;
      return model;
    },

    created: function(sub){
      this.records = {};

      if (this.attributes)
        this.attributes = makeArray(this.attributes);
      else
        this.attributes = [];

      this.bind("create",  this.proxy(function(record){
        this.trigger("change", "create", record);
      }));
      this.bind("update",  this.proxy(function(record){
        this.trigger("change", "update", record);
      }));
      this.bind("destroy", this.proxy(function(record){
        this.trigger("change", "destroy", record);
      }));
    },

    find: function(id){
      var record = this.records[id];
      if ( !record ) throw("Unknown record");
      return record.clone();
    },

    exists: function(id){
      try {
        return this.find(id);
      } catch (e) {
        return false;
      }
    },

    refresh: function(values){
      this.records = {};

      for (var i=0, il = values.length; i < il; i++) {
        var record = this.init(values[i]);
        record.newRecord = false;
        this.records[record.id] = record;
      }

      this.trigger("refresh");
    },

    select: function(callback){
      var result = [];

      for (var key in this.records)
        if (callback(this.records[key]))
          result.push(this.records[key]);

      return this.cloneArray(result);
    },

    findByAttribute: function(name, value){
      for (var key in this.records)
        if (this.records[key][name] == value)
          return this.records[key].clone();
    },

    findAllByAttribute: function(name, value){
      return(this.select(function(item){
        return(item[name] == value);
      }));
    },

    each: function(callback){
      for (var key in this.records)
        callback(this.records[key]);
    },

    all: function(){
      return this.cloneArray(this.recordsValues());
    },

    first: function(){
      var record = this.recordsValues()[0];
      return(record && record.clone());
    },

    last: function(){
      var values = this.recordsValues()
      var record = values[values.length - 1];
      return(record && record.clone());
    },

    count: function(){
      return this.recordsValues().length;
    },

    deleteAll: function(){
      for (var key in this.records)
        delete this.records[key];
    },

    destroyAll: function(){
      for (var key in this.records)
        this.records[key].destroy();
    },

    update: function(id, atts){
      this.find(id).updateAttributes(atts);
    },

    create: function(atts){
      var record = this.init(atts);
      return record.save();
    },

    destroy: function(id){
      this.find(id).destroy();
    },

    sync: function(callback){
      this.bind("change", callback);
    },

    fetch: function(callback){
      callback ? this.bind("fetch", callback) : this.trigger("fetch");
    },

    toJSON: function(){
      return this.recordsValues();
    },

    fromJSON: function(objects){
      if ( !objects ) return;
      if (typeof objects == "string")
        objects = JSON.parse(objects)
      if (typeof objects.length == "number") {
        var results = [];
        for (var i=0; i < objects.length; i++)
          results.push(this.init(objects[i]));
        return results;
      } else {
        return this.init(objects);
      }
    },

    // Private

    recordsValues: function(){
      var result = [];
      for (var key in this.records)
        result.push(this.records[key]);
      return result;
    },

    cloneArray: function(array){
      var result = [];
      for (var i=0; i < array.length; i++)
        result.push(array[i].dup());
      return result;
    }
  });

  Model.include({
    model: true,
    newRecord: true,

    init: function(atts){
      if (atts) this.load(atts);
    },

    isNew: function(){
      return this.newRecord;
    },

    isValid: function(){
      return(!this.validate());
    },

    validate: function(){ },

    load: function(atts){
      for(var name in atts)
        this[name] = atts[name];
    },

    attributes: function(){
      var result = {};
      for (var i=0; i < this.parent.attributes.length; i++) {
        var attr = this.parent.attributes[i];
        result[attr] = this[attr];
      }
      result.id = this.id;
      return result;
    },

    eql: function(rec){
      return(rec && rec.id === this.id &&
             rec.parent === this.parent);
    },

    save: function(){
      var error = this.validate();
      if ( error ) {
        this.trigger("error", this, error)
        return false;
      }

      this.trigger("beforeSave", this);
      this.newRecord ? this.create() : this.update();
      this.trigger("save", this);
      return this;
    },

    updateAttribute: function(name, value){
      this[name] = value;
      return this.save();
    },

    updateAttributes: function(atts){
      this.load(atts);
      return this.save();
    },

    destroy: function(){
      this.trigger("beforeDestroy", this);
      delete this.parent.records[this.id];
      this.trigger("destroy", this);
    },

    dup: function(){
      var result = this.parent.init(this.attributes());
      result.newRecord = this.newRecord;
      return result;
    },

    clone: function(){
      return Object.create(this);
    },

    reload: function(){
      if ( this.newRecord ) return this;
      var original = this.parent.find(this.id);
      this.load(original.attributes());
      return original;
    },

    toJSON: function(){
      return(this.attributes());
    },

    exists: function(){
      return(this.id && this.id in this.parent.records);
    },

    // Private

    update: function(){
      this.trigger("beforeUpdate", this);
      var records = this.parent.records;
      records[this.id].load(this.attributes());
      this.trigger("update", records[this.id].clone());
    },

    create: function(){
      this.trigger("beforeCreate", this);
      if ( !this.id ) this.id = Spine.guid();
      this.newRecord   = false;
      var records      = this.parent.records;
      records[this.id] = this.dup();
      this.trigger("create", records[this.id].clone());
    },

    bind: function(events, callback){
      return this.parent.bind(events, this.proxy(function(record){
        if ( record && this.eql(record) )
          callback.apply(this, arguments);
      }));
    },

    trigger: function(){
      return this.parent.trigger.apply(this.parent, arguments);
    }
  });

  // Controllers

  var eventSplitter = /^(\w+)\s*(.*)$/;

  var Controller = Spine.Controller = Class.create({
    tag: "div",

    initialize: function(options){
      this.options = options;

      for (var key in this.options)
        this[key] = this.options[key];

      if (!this.el) this.el = document.createElement(this.tag);
      this.el = $(this.el);

      if ( !this.events ) this.events = this.parent.events;
      if ( !this.elements ) this.elements = this.parent.elements;

      if (this.events) this.delegateEvents();
      if (this.elements) this.refreshElements();
      if (this.proxied) this.proxyAll.apply(this, this.proxied);
    },

    $: function(selector){
      return $(selector, this.el);
    },

    delegateEvents: function(){
      for (var key in this.events) {
        var methodName = this.events[key];
        var method     = this.proxy(this[methodName]);

        var match      = key.match(eventSplitter);
        var eventName  = match[1], selector = match[2];

        if (selector === '') {
          this.el.bind(eventName, method);
        } else {
          this.el.delegate(selector, eventName, method);
        }
      }
    },

    refreshElements: function(){
      for (var key in this.elements) {
        this[this.elements[key]] = this.$(key);
      }
    },

    delay: function(func, timeout){
      setTimeout(this.proxy(func), timeout || 0);
    }
  });

  Controller.include(Events);
  Controller.include(Log);

  Spine.App = Class.create();
  Spine.App.extend(Events)
  Controller.fn.App = Spine.App;
})();<commit_msg>fire change event manually so events do not stack up<commit_after>(function(){

  var Spine;
  if (typeof exports !== "undefined") {
    Spine = exports;
  } else {
    Spine = this.Spine = {};
  }

  Spine.version = "0.0.4";

  var $ = Spine.$ = this.jQuery || this.Zepto || function(){ return arguments[0]; };

  var makeArray = Spine.makeArray = function(args){
    return Array.prototype.slice.call(args, 0);
  };

  // Shim Array, as these functions aren't in IE
  if (typeof Array.prototype.indexOf === "undefined")
    Array.prototype.indexOf = function(value){
      for ( var i = 0; i < this.length; i++ )
    		if ( this[ i ] === value )
    			return i;
    	return -1;
    };

  var Events = Spine.Events = {
    bind: function(ev, callback) {
      var evs   = ev.split(" ");
      var calls = this._callbacks || (this._callbacks = {});

      for (var i=0; i < evs.length; i++)
        (this._callbacks[evs[i]] || (this._callbacks[evs[i]] = [])).push(callback);

      return this;
    },

    trigger: function() {
      var args = makeArray(arguments);
      var ev   = args.shift();

      var list, calls, i, l;
      if (!(calls = this._callbacks)) return false;
      if (!(list  = this._callbacks[ev])) return false;

      for (i = 0, l = list.length; i < l; i++)
        if (list[i].apply(this, args) === false)
          break;

      return true;
    },

    unbind: function(ev, callback){
      if ( !ev ) {
        this._callbacks = {};
        return this;
      }

      var list, calls, i, l;
      if (!(calls = this._callbacks)) return this;
      if (!(list  = this._callbacks[ev])) return this;

      if ( !callback ) {
        delete this._callbacks[ev];
        return this;
      }

      for (i = 0, l = list.length; i < l; i++)
        if (callback === list[i]) {
          list.splice(i, 1);
          break;
        }

      return this;
    }
  };

  var Log = Spine.Log = {
    trace: true,

    logPrefix: "(App)",

    log: function(){
      if ( !this.trace ) return;
      if (typeof console == "undefined") return;
      var args = makeArray(arguments);
      if (this.logPrefix) args.unshift(this.logPrefix);
      console.log.apply(console, args);
      return this;
    }
  };

  // Classes (or prototypial inheritors)

  if (typeof Object.create !== "function")
      Object.create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
      };

  var moduleKeywords = ["included", "extended"];

  var Class = Spine.Class = {
    inherited: function(){},
    created: function(){},

    prototype: {
      initialize: function(){},
      init: function(){}
    },

    create: function(include, extend){
      var object = Object.create(this);
      object.parent    = this;
      object.prototype = object.fn = Object.create(this.prototype);

      if (include) object.include(include);
      if (extend)  object.extend(extend);

      object.created();
      this.inherited(object);
      return object;
    },

    init: function(){
      var instance = Object.create(this.prototype);
      instance.parent = this;

      instance.initialize.apply(instance, arguments);
      instance.init.apply(instance, arguments);
      return instance;
    },

    proxy: function(func){
      var thisObject = this;
      return(function(){
        return func.apply(thisObject, arguments);
      });
    },

    proxyAll: function(){
      var functions = makeArray(arguments);
      for (var i=0; i < functions.length; i++)
        this[functions[i]] = this.proxy(this[functions[i]]);
    },

    include: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this.fn[key] = obj[key];

      var included = obj.included;
      if (included) included.apply(this);
      return this;
    },

    extend: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this[key] = obj[key];

      var extended = obj.extended;
      if (extended) extended.apply(this);
      return this;
    }
  };

  Class.prototype.proxy    = Class.proxy;
  Class.prototype.proxyAll = Class.proxyAll;
  Class.inst               = Class.init;
  Class.sub                = Class.create;

  // Models

  Spine.guid = function(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    }).toUpperCase();
  };

  var Model = Spine.Model = Class.create();

  Model.extend(Events);

  Model.extend({
    setup: function(name, atts){
      var model = Model.sub();
      if (name) model.name = name;
      if (atts) model.attributes = atts;
      return model;
    },

    created: function(sub){
      this.records = {};

      if (this.attributes)
        this.attributes = makeArray(this.attributes);
      else
        this.attributes = [];
    },

    find: function(id){
      var record = this.records[id];
      if ( !record ) throw("Unknown record");
      return record.clone();
    },

    exists: function(id){
      try {
        return this.find(id);
      } catch (e) {
        return false;
      }
    },

    refresh: function(values){
      this.records = {};

      for (var i=0, il = values.length; i < il; i++) {
        var record = this.init(values[i]);
        record.newRecord = false;
        this.records[record.id] = record;
      }

      this.trigger("refresh");
    },

    select: function(callback){
      var result = [];

      for (var key in this.records)
        if (callback(this.records[key]))
          result.push(this.records[key]);

      return this.cloneArray(result);
    },

    findByAttribute: function(name, value){
      for (var key in this.records)
        if (this.records[key][name] == value)
          return this.records[key].clone();
    },

    findAllByAttribute: function(name, value){
      return(this.select(function(item){
        return(item[name] == value);
      }));
    },

    each: function(callback){
      for (var key in this.records)
        callback(this.records[key]);
    },

    all: function(){
      return this.cloneArray(this.recordsValues());
    },

    first: function(){
      var record = this.recordsValues()[0];
      return(record && record.clone());
    },

    last: function(){
      var values = this.recordsValues()
      var record = values[values.length - 1];
      return(record && record.clone());
    },

    count: function(){
      return this.recordsValues().length;
    },

    deleteAll: function(){
      for (var key in this.records)
        delete this.records[key];
    },

    destroyAll: function(){
      for (var key in this.records)
        this.records[key].destroy();
    },

    update: function(id, atts){
      this.find(id).updateAttributes(atts);
    },

    create: function(atts){
      var record = this.init(atts);
      return record.save();
    },

    destroy: function(id){
      this.find(id).destroy();
    },

    sync: function(callback){
      this.bind("change", callback);
    },

    fetch: function(callback){
      callback ? this.bind("fetch", callback) : this.trigger("fetch");
    },

    toJSON: function(){
      return this.recordsValues();
    },

    fromJSON: function(objects){
      if ( !objects ) return;
      if (typeof objects == "string")
        objects = JSON.parse(objects)
      if (typeof objects.length == "number") {
        var results = [];
        for (var i=0; i < objects.length; i++)
          results.push(this.init(objects[i]));
        return results;
      } else {
        return this.init(objects);
      }
    },

    // Private

    recordsValues: function(){
      var result = [];
      for (var key in this.records)
        result.push(this.records[key]);
      return result;
    },

    cloneArray: function(array){
      var result = [];
      for (var i=0; i < array.length; i++)
        result.push(array[i].dup());
      return result;
    }
  });

  Model.include({
    model: true,
    newRecord: true,

    init: function(atts){
      if (atts) this.load(atts);
    },

    isNew: function(){
      return this.newRecord;
    },

    isValid: function(){
      return(!this.validate());
    },

    validate: function(){ },

    load: function(atts){
      for(var name in atts)
        this[name] = atts[name];
    },

    attributes: function(){
      var result = {};
      for (var i=0; i < this.parent.attributes.length; i++) {
        var attr = this.parent.attributes[i];
        result[attr] = this[attr];
      }
      result.id = this.id;
      return result;
    },

    eql: function(rec){
      return(rec && rec.id === this.id &&
             rec.parent === this.parent);
    },

    save: function(){
      var error = this.validate();
      if ( error ) {
        this.trigger("error", this, error)
        return false;
      }

      this.trigger("beforeSave", this);
      this.newRecord ? this.create() : this.update();
      this.trigger("save", this);
      return this;
    },

    updateAttribute: function(name, value){
      this[name] = value;
      return this.save();
    },

    updateAttributes: function(atts){
      this.load(atts);
      return this.save();
    },

    destroy: function(){
      this.trigger("beforeDestroy", this);
      delete this.parent.records[this.id];
      this.trigger("destroy", this);
      this.trigger("change", "destroy", this);
    },

    dup: function(){
      var result = this.parent.init(this.attributes());
      result.newRecord = this.newRecord;
      return result;
    },

    clone: function(){
      return Object.create(this);
    },

    reload: function(){
      if ( this.newRecord ) return this;
      var original = this.parent.find(this.id);
      this.load(original.attributes());
      return original;
    },

    toJSON: function(){
      return(this.attributes());
    },

    exists: function(){
      return(this.id && this.id in this.parent.records);
    },

    // Private

    update: function(){
      this.trigger("beforeUpdate", this);
      var records = this.parent.records;
      records[this.id].load(this.attributes());
      var clone = records[this.id].clone();
      this.trigger("update", clone);
      this.trigger("change", "update", clone);
    },

    create: function(){
      this.trigger("beforeCreate", this);
      if ( !this.id ) this.id = Spine.guid();
      this.newRecord   = false;
      var records      = this.parent.records;
      records[this.id] = this.dup();
      var clone        = records[this.id].clone();
      this.trigger("create", clone);
      this.trigger("change", "create", clone);
    },

    bind: function(events, callback){
      return this.parent.bind(events, this.proxy(function(record){
        if ( record && this.eql(record) )
          callback.apply(this, arguments);
      }));
    },

    trigger: function(){
      return this.parent.trigger.apply(this.parent, arguments);
    }
  });

  // Controllers

  var eventSplitter = /^(\w+)\s*(.*)$/;

  var Controller = Spine.Controller = Class.create({
    tag: "div",

    initialize: function(options){
      this.options = options;

      for (var key in this.options)
        this[key] = this.options[key];

      if (!this.el) this.el = document.createElement(this.tag);
      this.el = $(this.el);

      if ( !this.events ) this.events = this.parent.events;
      if ( !this.elements ) this.elements = this.parent.elements;

      if (this.events) this.delegateEvents();
      if (this.elements) this.refreshElements();
      if (this.proxied) this.proxyAll.apply(this, this.proxied);
    },

    $: function(selector){
      return $(selector, this.el);
    },

    delegateEvents: function(){
      for (var key in this.events) {
        var methodName = this.events[key];
        var method     = this.proxy(this[methodName]);

        var match      = key.match(eventSplitter);
        var eventName  = match[1], selector = match[2];

        if (selector === '') {
          this.el.bind(eventName, method);
        } else {
          this.el.delegate(selector, eventName, method);
        }
      }
    },

    refreshElements: function(){
      for (var key in this.elements) {
        this[this.elements[key]] = this.$(key);
      }
    },

    delay: function(func, timeout){
      setTimeout(this.proxy(func), timeout || 0);
    }
  });

  Controller.include(Events);
  Controller.include(Log);

  Spine.App = Class.create();
  Spine.App.extend(Events)
  Controller.fn.App = Spine.App;
})();<|endoftext|>


<commit_before>var paused = true,
    spiro,
    canvas,
    context,
    line_canvas,
    line_context,
    circle_canvas,
    circle_context,
    center,
    point;

function Gear(x, y, radius) {
    'use strict';
    this.x = x;
    this.y = y;
    this.radius = radius;
}

function Line(length, speed) {
    'use strict';
    this.length = length;
    this.speed = speed;
    this.rotation = 0;
}

Line.prototype.step = function() {
    'use strict';
    var x, y, rads;
    this.rotation += this.speed;
    rads = Math.PI * this.rotation / 360;
    x = this.length * Math.cos(rads);
    y = this.length * Math.sin(rads);
    return [x, y];
};

function Spirograph() {
    'use strict';
    this.lines = [];
    this.lines.push(new Line(400, 2));
    this.gears = [];
    this.gears.push(new Gear(center[0], center[1], 400));
    this.size = 0;
}

Spirograph.prototype.add_gear = function(percent, direction) {
    'use strict';
    var line_len, last_line, new_line, new_speed;
    last_line = this.lines[this.lines.length - 1];
    line_len = last_line.length * percent / 100;
    new_speed = Math.abs(last_line.speed / (percent / 100)) / 2;
    if (direction) {
        new_speed *= -1;
    }
    last_line.length *= ((100 - percent) / 100);
    new_line = new Line(line_len, new_speed);
    new_line.rotation = last_line.rotation;
    this.lines.push(new_line);
    this.gears.push(new Gear(center[0] * 2 - line_len, center[1], line_len));
    update_gearlist();
};

Spirograph.prototype.step = function() {
    'use strict';
    var len, i, line, gear, vector, delta;
    delta = [0, 0];
    delta[0] += center[0];
    delta[1] += center[1];
    for (i = 0; i < this.lines.length; i++) {
        this.gears[i].x = delta[0];
        this.gears[i].y = delta[1];
        line = this.lines[i];
        vector = line.step();
        delta[0] += vector[0];
        delta[1] += vector[1];
    }
    return delta;
};

function mainloop() {
    'use strict';
    if (paused === false) {
        draw_lines();
        draw_circles();

        // draw everything on the visible canvas
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.drawImage(circle_canvas, 0, 0);
        context.drawImage(line_canvas, 0, 0);

        setTimeout(mainloop, 10);
    }
}

function draw_lines() {
        // update the line on off-screen canvas
        line_context.moveTo(point[0], point[1]);
        point = spiro.step();
        line_context.lineTo(point[0], point[1]);
        line_context.stroke();
}

function draw_circles() {
        // predraw the circles on off-screen canvas
        var i, lines, gear;
        lines = spiro.lines;
        circle_context.clearRect(0, 0, canvas.width, canvas.height);
        circle_context.beginPath();
        circle_context.arc(point[0], point[1], 3, 0, Math.PI * 2, false);
        for (i = 0; i < spiro.gears.length; i++) {
            gear = spiro.gears[i];
            circle_context.moveTo(gear.x + gear.radius, gear.y);
            circle_context.arc(gear.x, gear.y, gear.radius, 0,
                    Math.PI * 2, false);
        }
        circle_context.stroke();
}

function toggle_pause() {
    'use strict';
    var pause = document.getElementById("pause");
    if (paused === true) {
        paused = false;
        pause.innerHTML = "Pause";
        mainloop();
    } else {
        paused = true;
        pause.innerHTML = "Start";
    }
    return false;
}

function new_gear() {
    var percent = parseFloat(document.getElementById('gearsize').value);
    var direction = document.getElementById('direction').checked;
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    spiro.add_gear(percent, direction);
    draw_circles();
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(circle_canvas, 0, 0);
}

function del_gear() {
    var line = spiro.lines.pop();
    spiro.lines[spiro.lines.length - 1].length += line.length;
    spiro.gears.pop();
    update_gearlist();
}

function readKey(event) {
    'use strict';
    switch (event.keyCode) {
        case 32:
            toggle_pause();
            break;
    }
    // console.log(event.keyCode);
}

function update_gearlist() {
    'use strict';
    var gearlist, content, i, gears, lines;
    gearlist = document.getElementById('list');
    gears = spiro.gears;
    lines = spiro.lines;
    content = "";
    content += 'You have ' + gears.length + ' gears.
';
    for (i = 0; i < gears.length; i++) {
        content += "Gear " + (i + 1) + ": " + gears[i].radius + " ";
        if (lines[i].speed > 0) {
            content += "->";
        } else {
            content += "<-";
        }
        content += "
";
    }
    gearlist.innerHTML = content;
}

function init() {
    'use strict';

    // visible "main" canvas
    canvas = document.getElementById('canv');
    context = canvas.getContext('2d');

    // own context for spirograph line since it doesn't need redrawing
    line_canvas = document.createElement('canvas');
    line_canvas.width = canvas.width;
    line_canvas.height = canvas.height;
    line_context = line_canvas.getContext('2d');
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    line_context.strokeStyle = '#666666';

    // own context for spirograph "gears" since they are redrawn each frame
    circle_canvas = document.createElement('canvas');
    circle_canvas.width = canvas.width;
    circle_canvas.height = canvas.height;
    circle_context = circle_canvas.getContext('2d');
    circle_context.clearRect(0, 0, canvas.width, canvas.height);
    circle_context.strokeStyle = '#888888';

    center = [canvas.width / 2, canvas.height / 2];
    spiro = new Spirograph();
    update_gearlist();
    point = spiro.step();
    paused = true;
    draw_circles();
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(circle_canvas, 0, 0);
    mainloop();
}
<commit_msg>finally working math<commit_after>var paused = true,
    spiro,
    canvas,
    context,
    line_canvas,
    line_context,
    circle_canvas,
    circle_context,
    center,
    point;

function Gear(x, y, radius) {
    'use strict';
    this.x = x;
    this.y = y;
    this.radius = radius;
}

function Line(length, speed) {
    'use strict';
    this.length = length;
    this.speed = speed;
    this.rotation = 0;
}

Line.prototype.step = function() {
    'use strict';
    var x, y, rads;
    this.rotation += this.speed;
    rads = Math.PI * this.rotation / 360;
    x = this.length * Math.cos(rads);
    y = this.length * Math.sin(rads);
    return [x, y];
};

function Spirograph() {
    'use strict';
    this.lines = [];
    this.lines.push(new Line(400, 2));
    this.gears = [];
    this.gears.push(new Gear(center[0], center[1], 400));
    this.size = 0;
}

Spirograph.prototype.add_gear = function(percent, direction) {
    'use strict';
    var line_len, last_line, new_line, new_speed, real_perc;
    last_line = this.lines[this.lines.length - 1];
    line_len = last_line.length * percent / 100;
    real_perc = percent / 100;
    new_speed = Math.abs(last_line.speed / real_perc) * (1 - real_perc);
    if (direction) {
        new_speed *= -1;
    }
    last_line.length *= ((100 - percent) / 100);
    new_line = new Line(line_len, new_speed);
    new_line.rotation = last_line.rotation;
    this.lines.push(new_line);
    this.gears.push(new Gear(center[0] * 2 - line_len, center[1], line_len));
    update_gearlist();
};

Spirograph.prototype.step = function() {
    'use strict';
    var len, i, line, gear, vector, delta;
    delta = [0, 0];
    delta[0] += center[0];
    delta[1] += center[1];
    for (i = 0; i < this.lines.length; i++) {
        this.gears[i].x = delta[0];
        this.gears[i].y = delta[1];
        line = this.lines[i];
        vector = line.step();
        delta[0] += vector[0];
        delta[1] += vector[1];
    }
    return delta;
};

function mainloop() {
    'use strict';
    if (paused === false) {
        draw_lines();
        draw_circles();

        // draw everything on the visible canvas
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.drawImage(circle_canvas, 0, 0);
        context.drawImage(line_canvas, 0, 0);

        setTimeout(mainloop, 10);
    }
}

function draw_lines() {
        // update the line on off-screen canvas
        line_context.moveTo(point[0], point[1]);
        point = spiro.step();
        line_context.lineTo(point[0], point[1]);
        line_context.stroke();
}

function draw_circles() {
        // predraw the circles on off-screen canvas
        var i, lines, gear;
        lines = spiro.lines;
        circle_context.clearRect(0, 0, canvas.width, canvas.height);
        circle_context.beginPath();
        circle_context.arc(point[0], point[1], 3, 0, Math.PI * 2, false);
        for (i = 0; i < spiro.gears.length; i++) {
            gear = spiro.gears[i];
            circle_context.moveTo(gear.x + gear.radius, gear.y);
            circle_context.arc(gear.x, gear.y, gear.radius, 0,
                    Math.PI * 2, false);
        }
        circle_context.stroke();
}

function toggle_pause() {
    'use strict';
    var pause = document.getElementById("pause");
    if (paused === true) {
        paused = false;
        pause.innerHTML = "Pause";
        mainloop();
    } else {
        paused = true;
        pause.innerHTML = "Start";
    }
    return false;
}

function new_gear() {
    var percent = parseFloat(document.getElementById('gearsize').value);
    var direction = document.getElementById('direction').checked;
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    spiro.add_gear(percent, direction);
    draw_circles();
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(circle_canvas, 0, 0);
}

function del_gear() {
    var line = spiro.lines.pop();
    spiro.lines[spiro.lines.length - 1].length += line.length;
    spiro.gears.pop();
    update_gearlist();
}

function readKey(event) {
    'use strict';
    switch (event.keyCode) {
        case 32:
            toggle_pause();
            break;
    }
    // console.log(event.keyCode);
}

function update_gearlist() {
    'use strict';
    var gearlist, content, i, gears, lines;
    gearlist = document.getElementById('list');
    gears = spiro.gears;
    lines = spiro.lines;
    content = "";
    content += 'You have ' + gears.length + ' gears.
';
    for (i = 0; i < gears.length; i++) {
        content += "Gear " + (i + 1) + ": " + gears[i].radius + " ";
        if (lines[i].speed > 0) {
            content += "->";
        } else {
            content += "<-";
        }
        content += "
";
    }
    gearlist.innerHTML = content;
}

function init() {
    'use strict';

    // visible "main" canvas
    canvas = document.getElementById('canv');
    context = canvas.getContext('2d');

    // own context for spirograph line since it doesn't need redrawing
    line_canvas = document.createElement('canvas');
    line_canvas.width = canvas.width;
    line_canvas.height = canvas.height;
    line_context = line_canvas.getContext('2d');
    line_context.clearRect(0, 0, canvas.width, canvas.height);
    line_context.strokeStyle = '#666666';

    // own context for spirograph "gears" since they are redrawn each frame
    circle_canvas = document.createElement('canvas');
    circle_canvas.width = canvas.width;
    circle_canvas.height = canvas.height;
    circle_context = circle_canvas.getContext('2d');
    circle_context.clearRect(0, 0, canvas.width, canvas.height);
    circle_context.strokeStyle = '#888888';

    center = [canvas.width / 2, canvas.height / 2];
    spiro = new Spirograph();
    update_gearlist();
    point = spiro.step();
    paused = true;
    draw_circles();
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(circle_canvas, 0, 0);
    mainloop();
}
<|endoftext|>


<commit_before>/**
 * Based on the work of Niklas von Hertzen
 * http://hertzen.com/experiments/jsfeedback/
 * Modified by Romain Laï-King
 */

/**
 * @author Niklas von Hertzen <niklas at hertzen.com>
 * @created 13.7.2011
 * @website http://hertzen.com
 */

 /**
  * Convert a SVG to PNG.
  * @param svg the svg to convert. See canvg for details on format, but it's either string or through getElement
  * @param width width of the svg
  * @param height height of the svg
  */

 function svg2png(svg,width,height){
    $('body').append("<canvas id='svgCanvas' width='"+width+"' height='"+height+"' ></canvas>");
    var canvas = document.getElementById('svgCanvas');
    canvg(canvas,svg);
	var ctx = document.getElementById('svgCanvas').getContext('2d');
	var img=ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height);
    var base64 = canvas.toDataURL('image/png');
    $('#svgCanvas').remove();
    return base64;
 }

/**
 * Based on http://jsfiddle.net/rRtAq/2039/ and  http://taitems.tumblr.com/post/549973287/drawing-arrows-in-raphaeljs
 * Mixed the 2 to create an arrow
 * This function draw arrows by dragging
 * @class Line
 * @param startX X coordinate of starting point
 * @param startY Y coordinate of starting point
 * @param endX X coordinate of the arrow end
 * @param endY Y coordinate of the arrow end
 * @param raphael the raphael canvas
 */

function Line(startX, startY, endX, endY, raphael) {
    var start = {
        x: startX,
        y: startY
    };
    var end = {
        x: endX,
        y: endY
    };
    var getPath = function() {

        return "M" + start.x + " " + start.y + " L" + end.x + " " + end.y  ;
    };
	//Using the provided arrow end of raphaeljs doesn't work with svg2png, so we draw a path for the arrow
    var getArrowPath = function() {
            size=10;
            return "M "+ end.x + " " + end.y + " L " + (end.x - size) + " " + (end.y - size) +  " L " + (end.x - size) + " " + (end.y + size) + " L " +     end.x + " " + end.y ;
        };
    var redraw = function() {
        var angle = Math.atan2(start.x-end.x,end.y-start.y);
        angle = (angle / (2 * Math.PI)) * 360;

        var angle = Raphael.angle(start.x,start.y,end.x,end.y);
        arrow.attr("path",getArrowPath());
        arrow.transform("r"+(angle+180)+","+end.x+","+end.y);
        node.attr("path", getPath());
    }

    var node = raphael.path(getPath());
    var arrow = raphael.path(getArrowPath());

    arrow.attr("stroke","#ff0000");
    arrow.attr("fill","#ff0000");
    //We use jQuery's attr method for stroke-width, Raphael's attr generates errors in the console log.
    $('#arrowOverlay').find('path').attr("stroke-width","7");
    node.attr("stroke","#ff0000");
    return {
        updateStart: function(x, y) {
            start.x = x;
            start.y = y;
            redraw();
            return this;
        },
        updateEnd: function(x, y) {
            end.x = x;
            end.y = y;
            redraw();
            return this;
        },
        getNode: function(){
            return node;
        },
        getArrow: function(){
            return arrow;
         }
    };
}

(function($) {

	/**
	* Function which create the whole annotation form
	* @param loadingZone  id of the DOM element where we append the loading message
	* @param  callPreview Function called when we want a preview, pass a parameter image in base64
	* @param  callCancel When the user cancel
	*/

	$.fn.feedback = function(callback) {
		var pressedButton=this.selector;
		$(this.selector).click(function(){
			var self = this;
			$(pressedButton).hide();
			//var loadingZone= '#' +  loadingZone;
	        var deleteMode=false;
			var reset = {
				'margin' : 0
			}, createDiv, createDivLeft, createDivTop, overlay, feedbackDiv, line;

			arrowOverlay = $('')
			.css(reset)
			.attr('id','arrowOverlay')
			.appendTo('body');
			var paper = Raphael(document.getElementById('arrowOverlay'),document.width,document.height);

	        arrowOverlay
	        .children('svg')
	        .css({
	            'pointer-events':'none',
	            'z-index':50005,
	            '-moz-user-drag': '-moz-none',
	            '-webkit-user-drag': 'none',
	            'user-drag': 'none',
	            'visibility':'visible'
	            });

	        var svgLayer = arrowOverlay.children('svg');

	         svgLayer.mouseup(
	         function(e) {
	             svgLayer.unbind('mousemove');
	             var st=paper.set();
	             st.push(line.getNode(),line.getArrow());
	            st.click(function() {
	                     if(deleteMode){
	                         st.remove();
	                     }
	                  });
	         });

	       // Safari bug with pointer-events http://code.google.com/p/chromium/issues/detail?id=55741
	       // Should be corrected with the upcomming release of Safari 6 and eventual maintenance release of Safari 5
	       // Opera 12.00 didn't implement pointer-events at the moment and 12.5 doesn't seem to fix this.
	       // Compatiblity mode switch the z-index of the svg+arrowOverlay layers to either the front or behind the overlay.

			compatilibityMode=false;

			var ua=$.browser;
			if (ua.webkit){
				var array= ua.version.split('.');
				if(array[0]<536){
					compatilibityMode=true;
				}
			}
			if (ua.opera){
				compatilibityMode=true;
			}

			function arrowForward(){
	             arrowOverlay.css('z-index',50004);
	             svgLayer.css('z-index',50005);
			}

			function arrowBackground(){
	               arrowOverlay.css('z-index',49997);
	               svgLayer.css('z-index',49997);
			}

			if(compatilibityMode){
				arrowBackground();
	        }

			// Button of the interface
			var color = '#faa';
			var feedbackClass= "feedbackRed";
			var red = $('<button>').text('Red').addClass('btn  btn-small').button('toggle')
			.click(function() {
				changeButton();
				color = '#faa';
				feedbackClass = "feedbackRed";
			});
			var green = $('<button>').text('Green').addClass('btn btn-small')
			.click(function() {
				changeButton();
				color = '#afa';
				feedbackClass = "feedbackGreen";
			});
			var blue = $('<button>').text('Blue').addClass('btn btn-small')
			.click(function() {
				changeButton();
				color = '#aaf';
				feedbackClass = "feedbackBlue";
			});

			//Black mask, doesn't use hex value but a special case
			var black = $('<button>').text('Mask').addClass('btn btn-small')
			.click(function() {
				changeButton();
				color = 'black';
				black.addClass('active');
			});

			var remove=$('<button>').text('X').addClass('btn btn-small')
	        .click(function() {
	            changeButton();
	            deleteMode=true;
	            svgLayer.children().css('pointer-events','all');
	            remove.addClass('active')
	            overlay.children().css({
	                'cursor' : 'pointer'
	            });
	            $('.feedbackBlack').css({
	                'cursor' : 'pointer'
	            });

	        });

			//This is provided for compatiblity mode. The svg layer need to be in front in order for the arrows to be removable.
	        var removeArrow=$('<button>').text('Arrow').addClass('btn btn-small').css('margin-left','10px')
	        .click(function() {
	            changeButton();
	            deleteMode=true;
	            svgLayer.children().css('pointer-events','all');
	            removeArrow.addClass('active');
	            arrowForward();
	        });

	        var crop = $('<button>').text('Crop').addClass('btn btn-small')
	        .click(function() {
	            changeButton();
	            color = 'white';
	            crop.addClass('active');
	        });

	        //Sticky note are based on http://jsfiddle.net/EnigmaMaster/aQMhk/6/
	        var sticky= $('
').addClass('stickyNoteHeader');
	        $('
').text('Move').appendTo(sticky);
	        $('<button>').attr('type','button').addClass('close closeNote').text('x').css('margin-right','5px').appendTo(sticky);
	        $('
').addClass('stickyNoteBody').append('<textarea>').appendTo(sticky);

	        var note = $('<button>').text('Note').addClass('btn btn-small')
	        .click(function() {
	            changeButton();
	            color='none';
	            var clone=sticky.clone();
	            clone.draggable({'containment':'document'})
	            .css({
	            'position':'absolute',
	            'left':'50%',
	            'top':'50%',
	            'margin' :'-75px 0 0 -75px'
	            });
	            $('body').append(clone);
	            $('.closeNote').click(function(){
	                    $(this).parent().remove();
	                });
	            });

	        var arrow=$('<button>').text('Arrow').addClass('btn btn-small')
	            .click(function() {
	                changeButton();
	                svgLayer.css('pointer-events','all');
	                 svgLayer.mousedown(
	                 function(e) {
	                    x = e.pageX;
	                     y = e.pageY;
	                     line = Line(x, y, x, y, paper);
	                     svgLayer.bind('mousemove',function(e) {
	                         x = e.pageX;
	                         y = e.pageY;
	                         line.updateEnd(x, y);
	                     });
	                 });
	                 if(compatilibityMode){
	                 arrowForward();
	                 }

	                arrow.addClass('active');

	        });

			function changeButton(){
				$('#feedbackForm button').removeClass('active');
				svgLayer.css('pointer-events','none');
	            svgLayer.children().css('pointer-events','none');
	            svgLayer.unbind('mousedown');
	            if(compatilibityMode){
					arrowBackground();
	            }
				if(deleteMode){
					deleteMode=false;
					overlay.children().css({
						'cursor' : 'default'
					});
					$('.feedbackBlack').css({
						'cursor' : 'default'
					});
				}
			}

			//Creation of each line of the interface.
			var menuColor=$('
').addClass('feedbackMenuBar')
	        .append("Draw a frame")
	        .addClass('btn-group')
	        .attr('data-toggle','buttons-radio')
	        .append(red)
	        .append(green)
	        .append(blue);

	        var menuArrow=$('
').addClass('feedbackMenuBar')
	        .append("Point an element")
	        .append(arrow);

			var menuMask=$('
').addClass('feedbackMenuBar')
			.append("Hide your informations")
			.append(black);

			var menuRemove=$('
').addClass('feedbackMenuBar')
			.append("Remove an element")
			.append(remove);

			if (compatilibityMode){
			menuRemove.append(removeArrow);
			}

			var menuCrop=$('
').addClass('feedbackMenuBar')
	        .append("Select the visible area")
	        .append(crop);

	        var menuNote=$('
').addClass('feedbackMenuBar')
	        .append("Add text")
	        .append(note);

			var cancel=$('<button>')
			.addClass('btn btn-small')
			.text('Annuler')
			.click(function(){
				feedbackDiv.remove();
				overlay.remove();
				arrowOverlay.remove();
				$('.stickyNoteHeader').remove();
				$('.feedbackBlack').remove();
				$(pressedButton).show();
				//callCancel.call(this);
			});

			Mousetrap.bind('esc',function(){
				cancel.trigger('click');
			});

			//THE big button doing all the work
			var preview = $('<button>').text('Continue').addClass('btn btn-primary').attr('id','feedbackPreview').click(function() {
			//Preprocessing before screenshot
				feedbackDiv.remove();
				arrowForward();
				deleteMode=false;
				var loading=$('').attr('id','tracLoading').text('Loading');
				//$(loadingZone).prepend(loading);
				$('.feedbackRed').css({
					'background' : 'none',
					'border' : 'solid 4px red'
				});
				$('.feedbackGreen').css({
		            'background' : 'none',
		            'border' : 'solid 4px green'
	            });
				$('.feedbackBlue').css({
	                'background' : 'none',
	                'border' : 'solid 4px blue'
	            });
				if ($('.feedbackCrop').length>0){
					var left = parseInt($('.feedbackCrop').css('left'));
		            var top = parseInt($('.feedbackCrop').css('top'));
		            var width = $('.feedbackCrop').width();
		            var height = $('.feedbackCrop').height();
				}

				var svgString = $('

').append(svgLayer.clone()).html();
				svgLayer.remove();
				var arrowImg = svg2png(svgString);
				arrowOverlay.append("");
				overlay.css('background','none') ;
				$('.feedbackCrop').remove();
				feedbackDiv.remove();
				$('.stickyNoteHeader').css('position','absolute');

				$('body').css('cursor','wait');
				window.setTimeout(function() {
					html2canvas( [ document.body ], {
						//parameter of html2canvas...
						onrendered:function(canvas) {
						var ctx = canvas.getContext('2d');
	                        if (typeof left!='undefined'){
	                            var img=ctx.getImageData(left,top,width,height);
	                            ctx.clearRect(0, 0, canvas.width, canvas.height)
	                            canvas.width=width;
	                            canvas.height=height;
	                            ctx.putImageData(img,0,0);
	                        }

							var base64 = canvas.toDataURL('image/png');
	                        $('#feedbackOverlay').remove();
	                        arrowOverlay.remove();
	                        $('.feedbackBlack').remove();
							loading.remove();
							$('.stickyNoteHeader').remove();
							$('body').css('cursor','auto');
							if(typeof callback == 'function'){
								callback.call(this,base64);
							}
							$(pressedButton).show();
						}
					});

				}, 1000);

			});

			var menuBottom=$('
').addClass('feedbackMenuBar').attr('id','feedbackControl')
			.append(preview)
			.append(cancel);

			//For the feedback popup
			feedbackDiv = $('
')
			.draggable({'containment':'document'})
			.attr('id','feedbackForm')
			.append($('
').text('Annotation'))
			.append(menuColor)
			.append(menuArrow)
			.append(menuNote)
			.append(menuMask)
			.append(menuCrop)
			.append(menuRemove)
			.append(menuBottom)
			.css({
				'position':'fixed',
				'bottom':'0px',
				'left':'Opx'
			})
			.appendTo('body');

	        // the overlay for frame drawing, mask and cropping
			overlay = $('
')
			.css(reset)
			.css({
				'width':document.width,
				'height':document.height
			})
			.attr('id','feedbackOverlay')
			.appendTo('body').mousedown(function(e) {
			if(!deleteMode){
				createDiv = $('
')
				.css(reset)
				.css({
					'position' : 'absolute',
					'left' : e.pageX,
					'top' : e.pageY,
					'opacity' : 1,
					'cursor' : 'default',
				});

				if(color=='black'){
					createDiv.css({
						'background' : '#000',
						'z-index' : 50002
						})
					.addClass('feedbackBlack')
					.appendTo('body')
					}
				else if(color=='white'){
					$('.feedbackCrop').remove();
					createDiv.css({
						'background' : '#fff',
						'z-index' : 50000
					})

					.addClass('feedbackCrop')
					.appendTo(overlay);
				}
				else if(color=='none'){
					createDiv.remove();
				}
				else{
					createDiv.css({
						'background' : color,
						'z-index' : 50001
					})
					.addClass(feedbackClass)
					.appendTo(overlay)
				}

				createDivLeft = e.pageX;
				createDivTop = e.pageY;
				//Remove div when clicking on it while in delete mode
				createDiv.click(function() {
					if(deleteMode){
						$(this).remove();
					}
				});

				overlay.bind('mousemove', function(e) {

					createDiv.width(Math.abs(e.pageX - createDivLeft));
					createDiv.height(Math.abs(e.pageY - createDivTop));

					if (e.pageX < createDivLeft) {
						createDiv.css('left', e.pageX);
					}

					if (e.pageY < createDivTop) {
						createDiv.css('top', e.pageY);
					}
				});
				}

			});
			$('body').mouseup(function(e) {
				overlay.unbind('mousemove');
			});
			 $(window).resize(function() {
	                paper.setSize(document.width,document.height);
					overlay.css({
						'width':document.width,
						'height':document.height
					});
	         });
		});
		return this;
	};
})(jQuery); <commit_msg>Renommage jsfeedback<commit_after><|endoftext|>


<commit_before>(function (exports) {
  var util, T, escodegen, Types, uglify;
  if (typeof process !== "undefined") {
    util = require("./util.js");
    T = require("./estransform.js");
    escodegen = require("./escodegen.js");
    Types = require("./types.js");
    uglify = require("uglify-js");

    snarf = require('fs').readFileSync;
  } else {
    util = this.util;
    T = estransform;
    escodegen = this.escodegen;
    Types = this.Types;
    uglify = load("./uglify-js.js");
  }

  /**
   * Import AST nodes
   */
  const Node = T.Node;
  const Program = T.Program;
  const Literal = T.Literal;
  const Identifier = T.Identifier;
  const FunctionDeclaration = T.FunctionDeclaration;
  const FunctionExpression = T.FunctionExpression;
  const AssignmentExpression = T.AssignmentExpression;
  const ExpressionStatement = T.ExpressionStatement;
  const CallExpression = T.CallExpression;
  const VariableDeclaration = T.VariableDeclaration;
  const VariableDeclarator = T.VariableDeclarator;
  const BlockStatement = T.BlockStatement;
  const ReturnStatement = T.ReturnStatement;
  const MemberExpression = T.MemberExpression;
  const IfStatement = T.IfStatement;
  const BinaryExpression = T.BinaryExpression;
  const UnaryExpression = T.UnaryExpression;
  const SequenceExpression = T.SequenceExpression;
  const ArrayExpression = T.ArrayExpression;
  const ForInStatement = T.ForInStatement;

  /**
   * Import utilities.
   */
  const extend = util.extend;
  const assert = util.assert;

  var logger;
  var minReplaceLength;
  var callGraphAvailable;
  var callProfiling;

  exports.initialize = function (o, cmdLineOpts) {
    logger = o.logger;
    if (cmdLineOpts["lazy-minimum"] === true) {
      minReplaceLength = 0;
    } else {
      minReplaceLength = cmdLineOpts["lazy-minimum"];
    }

    callGraphAvailable = cmdLineOpts["call-graph"];

    if (cmdLineOpts["read-profile"] !== "") {
      var profileStr = snarf(cmdLineOpts["read-profile"]);
      callProfiling = new Uint8Array(JSON.parse(profileStr));
    }
  };

  function stringifyNode(node) {
    var s = escodegen.generate(node, {format: {indent: { style: '', base: 0}}});
    if (node instanceof BlockStatement) {
      return uglify('(function()' + s + ')')
        .replace(/^.*?\{/, '').replace(/\}\)$/, '');
    } else {
      return uglify('(' + s + ')')
        .replace(/^\(/, '').replace(/\)$/, '');
    }
  }

  /**
   * Begin rewrite pass
   */

  function shouldLazify(functionNode, length) {
    return (length > minReplaceLength &&
            (!callGraphAvailable || !functionNode.called) &&
            (typeof callProfiling === "undefined" || !callProfiling[functionNode.idNumber]));
  }

  function Laziness() {
    this.functionStrings = Object.create(null);
    this.functionMap = Object.create(null);
    this.isClosure = false;
    this.needsStub = false;
    this.needsStubF = false;
  }

  function stubConstructor(strId, funcId) {
    var tempId = new Identifier("t");
    return new FunctionDeclaration(
      new Identifier("stub"),
      [strId, funcId],
      new BlockStatement([
        new IfStatement(
          new BinaryExpression(
            "===",
            new UnaryExpression(
              "typeof",
              strId
            ),
            new Literal("string")
          ),
          new BlockStatement([
            new VariableDeclaration(
              "var", [
                new VariableDeclarator(
                  tempId,
                  new CallExpression(
                    new Identifier("eval"),
                    [strId]
                  )
                )
              ]
            ),
            new ExpressionStatement(
              new AssignmentExpression(
                new MemberExpression(
                  tempId,
                  new Identifier("prototype"),
                  false,
                  "."
                ),
                "=",
                new MemberExpression(
                  funcId,
                  new Identifier("prototype"),
                  false,
                  "."
                )
              )
            ),
            new ForInStatement(
              new VariableDeclaration(
                "var", [
                  new VariableDeclarator(
                    new Identifier("x")
                  )
                ]
              ),
              funcId,
              new BlockStatement([
                new ExpressionStatement(
                  new AssignmentExpression(
                    new MemberExpression(
                      tempId,
                      new Identifier("x"),
                      true
                    ), "=",
                    new MemberExpression(
                      funcId,
                      new Identifier("x"),
                      true
                    )
                  )
                )
              ])
            ),
            new ReturnStatement(
              tempId
            )
          ])
        ),
        new ReturnStatement(
          funcId
        )
      ])
    );
  }

  function stubFConstructor(strId, funcId, paramsId) {
    var tempId = new Identifier("t");
    return new FunctionDeclaration(
      new Identifier("stubF"),
      [strId, funcId, paramsId],
      new BlockStatement([
        new IfStatement(
          new BinaryExpression(
            "===",
            new UnaryExpression(
              "typeof",
              strId
            ),
            new Literal("string")
          ),
          new BlockStatement([
            new ExpressionStatement(
              new CallExpression(
                new MemberExpression(
                  paramsId,
                  new Identifier("push"),
                  false, "."
                ),
                [strId]
              )
            ),
            new VariableDeclaration(
              "var", [
                new VariableDeclarator(
                  tempId,
                  new CallExpression(
                    new MemberExpression(
                      new Identifier("Function"),
                      new Identifier("apply"),
                      false, "."
                    ),
                    [
                      new Identifier("this"),
                      paramsId
                    ]
                  )
                )
              ]
            ),
            new ExpressionStatement(
              new AssignmentExpression(
                new MemberExpression(
                  tempId,
                  new Identifier("prototype"),
                  false,
                  "."
                ),
                "=",
                new MemberExpression(
                  funcId,
                  new Identifier("prototype"),
                  false,
                  "."
                )
              )
            ),
            new ForInStatement(
              new VariableDeclaration(
                "var", [
                  new VariableDeclarator(
                    new Identifier("x")
                  )
                ]
              ),
              funcId,
              new BlockStatement([
                new ExpressionStatement(
                  new AssignmentExpression(
                    new MemberExpression(
                      tempId,
                      new Identifier("x"),
                      true
                    ), "=",
                    new MemberExpression(
                      funcId,
                      new Identifier("x"),
                      true
                    )
                  )
                )
              ])
            ),
            new ReturnStatement(
              tempId
            )
          ])
        ),
        new ReturnStatement(
          funcId
        )
      ])
    );
  }

  function stub(id, strName, stubName, params) {
    var tempId = new Identifier("t");
    var strId = new Identifier(strName);

    var stubParams = [strId, id];

    if (stubName === "stubF") {
      var paramArray = [];
      for (var i = 0, l = params.length; i < l; i++) {
        paramArray.push(new Literal(params[i].name));
      }
      stubParams.push(new ArrayExpression(paramArray));
    }
    return new BlockStatement([
      // new IfStatement(
      //   new BinaryExpression(
      //     "===",
      //     new UnaryExpression(
      //       "typeof",
      //       strId
      //     ),
      //     new Literal("string")
      //   ),
      //   new BlockStatement([
      //     new VariableDeclaration(
      //       "var", [
      //         new VariableDeclarator(
      //           tempId,
      //           new CallExpression(
      //             new Identifier("eval"),
      //             [strId]
      //           )
      //         )
      //       ]
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         new MemberExpression(
      //           tempId,
      //           new Identifier("prototype"),
      //           false,
      //           "."
      //         ),
      //         "=",
      //         new MemberExpression(
      //           id,
      //           new Identifier("prototype"),
      //           false,
      //           "."
      //         )
      //       )
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         id, "=",
      //         tempId
      //       )
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         strId, "=", new Identifier("null")
      //       )
      //     )
      //   ])
      // ),
      new ExpressionStatement(
        new AssignmentExpression(
          id, "=",
          new CallExpression(
            new Identifier(stubName),
            stubParams
          )
        )
      ),
      new ExpressionStatement(
        new AssignmentExpression(
          strId, "=", new Identifier("null")
        )
      ),
      new ReturnStatement(
        new CallExpression(
          new MemberExpression(
            id,
            new Identifier("call"),
            false,
            "."
          ),
          [new Identifier("this")].concat(params)
        )
      )
    ]);

    // return new CallExpression(
    //   new Identifier("eval"),
    //   [new Identifier(strName)]
    // );
  }

  function passOverList(list, passFunction, o) {
    return list.map(function (node) {
      return node[passFunction](o);
    }).filter(function (node) {
      return node !== null;
    });
  }

  Node.prototype.lazyParsePass = T.makePass("lazyParsePass", "lazyParseNode");

  Program.prototype.lazyParsePass = function (o) {
    o.scope = this.frame;
    o = extend(o);
    o.laziness = new Laziness();

    this.body = passOverList(this.body, 'lazyParsePass', o);

    var strId, strDeclaration;
    for (var name in o.laziness.functionStrings) {
      strId = new Identifier(name, "variable");
      strDeclaration = new VariableDeclaration(
        "var",
        [new VariableDeclarator(strId, new Literal(o.laziness.functionStrings[name]), undefined)]
      );
      this.body.unshift(strDeclaration);
    }

    if (o.laziness.needsStub) {
      this.body.unshift(stubConstructor(o.scope.freshTemp(), o.scope.freshTemp()));
    }
    if (o.laziness.needsStubF) {
      this.body.unshift(stubFConstructor(o.scope.freshTemp(), o.scope.freshTemp(), o.scope.freshTemp()));
    }

    return this;
  };

  function prepend(newStatement, body) {
  }

  var curId = 0;
  FunctionExpression.prototype.lazyParsePass =
  FunctionDeclaration.prototype.lazyParsePass = function (o) {
    var childOpts = extend(o);
    childOpts.scope = this.frame;
    childOpts.laziness = new Laziness();
    this.body = this.body.lazyParsePass(childOpts);
    this.idNumber = curId++; // NOTE: this is AFTER parsing children
                             // (as is the post-order traversal in
                             // callGraph)

    var strId, strDeclaration;
    for (var name in childOpts.laziness.functionStrings) {
      strId = new Identifier(name, "variable");
      strDeclaration = new VariableDeclaration(
        "var",
        [new VariableDeclarator(strId, new Literal(childOpts.laziness.functionStrings[name]), undefined)]
      );
      prepend(strDeclaration, this.body);
      if (this.body instanceof BlockStatement) {
        this.body.body.unshift(strDeclaration);
      } else {
        this.body = new BlockStatement([strDeclaration, this.body]);
      }
    }

    if (childOpts.laziness.needsStub) {
      prepend(stubConstructor(o.scope.freshTemp(), o.scope.freshTemp()), this.body);
    }
    if (childOpts.laziness.needsStubF) {
      prepend(stubFConstructor(o.scope.freshTemp(), o.scope.freshTemp(), o.scope.freshTemp()), this.body);
    }

    if (this instanceof FunctionDeclaration) {
      var functionString = stringifyNode(this);
      if (shouldLazify(this, functionString.length)) {
        if (this.id instanceof Identifier && this.params) {
          for (var i = 0, l = this.params.length; i < l; i++) {
            var param = this.params[i];
            if (param instanceof Identifier && this.id.name === param.name) {
              return this;
            }
          }
        }

        var id = o.scope.freshTemp();
        if (childOpts.laziness.isClosure) {
          o.laziness.functionStrings[id.name] = '(' + functionString + ')';
          this.body = stub(this.id, id.name, "stub", this.params);
          o.laziness.needsStub = true;
        } else {
          functionString = stringifyNode(this.body);
          o.laziness.functionStrings[id.name] = functionString;
          this.body = stub(this.id, id.name, "stubF", this.params);
          o.laziness.needsStubF = true;
        }

        o.laziness.functionMap[this.id.name] = {mangled: id.name, isClosure: childOpts.laziness.isClosure, params: this.params};
      }
      return this;
    } else {
      this.isClosure = childOpts.laziness.isClosure;
      return this;
    }
  }

  Identifier.prototype.lazyParseNode = function (o) {
    if (this.name === "eval") {
      o.laziness.isClosure = true;
      return this;
    }
    if (typeof o.laziness.isClosure !== "undefined" &&
        !o.laziness.isClosure &&
        this.kind === "variable") {
      var scope = o.scope;
      var variable = scope.variables[this.name];
      var parentVar = scope.getVariable(this.name);

      if (!parentVar) {
        // Should be the identifier for a catch clause
        return this;
      }

      if (!variable && !parentVar.external) {
        o.laziness.isClosure = true;
      }
    }
    return this;
  }

  AssignmentExpression.prototype.lazyParseNode = function (o) {
    if (this.right instanceof FunctionExpression) {
      var oldId = this.right.id;
      this.right.id = null;
      var functionString = stringifyNode(this.right);
      if (shouldLazify(this.right, functionString.length)) {
        var id = o.scope.freshTemp();

        if (this.left instanceof Identifier && this.params) {
          for (var i = 0, l = this.params.length; i < l; i++) {
            var param = this.params[i];
            if (param instanceof Identifier && this.left.name === param.name) {
              return this;
            }
          }
        }
        //print('compressing function ' + id.name);

        if (this.right.isClosure) {
          o.laziness.functionStrings[id.name] = '(' + functionString + ')';
          this.right.body = stub(this.left, id.name, "stub", this.right.params);
          o.laziness.needsStub = true;
        } else {
          o.laziness.functionStrings[id.name] = stringifyNode(this.right.body);
          this.right.body = stub(this.left, id.name, "stubF", this.right.params);
          o.laziness.needsStubF = true;
        }

        var funcName;
        if (this.left instanceof Identifier) {
          funcName = this.left.name;
        } else {
          funcName = id.name;
        }
        o.laziness.functionMap[funcName] = {mangled: id.name, isClosure: this.right.isClosure, params: this.right.params};
      } else {
        this.right.id = oldId;
      }
    }
    // else if (this.right instanceof Identifier) {
    //   this.right = resolve(this.right, o);
    // }

    return this;
  }

  CallExpression.prototype.lazyParseNode = function (o) {
    for (var i = 0, l = this.arguments.length; i < l; i++) {
      this.arguments[i] = resolve(this.arguments[i], o);
    }
  }

  function resolve(identifier, o) {
    if (identifier.variable && identifier.variable.type instanceof Types.ArrowType
       && o.laziness.functionMap[identifier.name]) {
      var functionEntry = o.laziness.functionMap[identifier.name];
      var strId = new Identifier(o.laziness.functionMap[identifier.name].mangled);

      var stubCall;
      if (functionEntry.isClosure) {
        stubCall = new CallExpression(
          new Identifier("stub"),
          [strId, identifier]
        );
      } else {
        var paramArray = [];
        for (var i = 0, l = functionEntry.params.length; i < l; i++) {
          paramArray.push(new Literal(functionEntry.params[i].name));
        }
        stubCall = new CallExpression(
          new Identifier("stubF"),
          [strId, identifier, new ArrayExpression(paramArray)]
        );
      }

      return new SequenceExpression([
        new AssignmentExpression(
          identifier, "=",
          stubCall
        ),
        new AssignmentExpression(
          strId, "=",
          new Identifier("null")
        ),
        identifier
      ]);
    } else {
      return identifier;
    }
  }

}).call(this, typeof exports === "undefined" ? (lazyParse = {}) : exports);
<commit_msg>Really got rid of prepend now<commit_after>(function (exports) {
  var util, T, escodegen, Types, uglify;
  if (typeof process !== "undefined") {
    util = require("./util.js");
    T = require("./estransform.js");
    escodegen = require("./escodegen.js");
    Types = require("./types.js");
    uglify = require("uglify-js");

    snarf = require('fs').readFileSync;
  } else {
    util = this.util;
    T = estransform;
    escodegen = this.escodegen;
    Types = this.Types;
    uglify = load("./uglify-js.js");
  }

  /**
   * Import AST nodes
   */
  const Node = T.Node;
  const Program = T.Program;
  const Literal = T.Literal;
  const Identifier = T.Identifier;
  const FunctionDeclaration = T.FunctionDeclaration;
  const FunctionExpression = T.FunctionExpression;
  const AssignmentExpression = T.AssignmentExpression;
  const ExpressionStatement = T.ExpressionStatement;
  const CallExpression = T.CallExpression;
  const VariableDeclaration = T.VariableDeclaration;
  const VariableDeclarator = T.VariableDeclarator;
  const BlockStatement = T.BlockStatement;
  const ReturnStatement = T.ReturnStatement;
  const MemberExpression = T.MemberExpression;
  const IfStatement = T.IfStatement;
  const BinaryExpression = T.BinaryExpression;
  const UnaryExpression = T.UnaryExpression;
  const SequenceExpression = T.SequenceExpression;
  const ArrayExpression = T.ArrayExpression;
  const ForInStatement = T.ForInStatement;

  /**
   * Import utilities.
   */
  const extend = util.extend;
  const assert = util.assert;

  var logger;
  var minReplaceLength;
  var callGraphAvailable;
  var callProfiling;

  exports.initialize = function (o, cmdLineOpts) {
    logger = o.logger;
    if (cmdLineOpts["lazy-minimum"] === true) {
      minReplaceLength = 0;
    } else {
      minReplaceLength = cmdLineOpts["lazy-minimum"];
    }

    callGraphAvailable = cmdLineOpts["call-graph"];

    if (cmdLineOpts["read-profile"] !== "") {
      var profileStr = snarf(cmdLineOpts["read-profile"]);
      callProfiling = new Uint8Array(JSON.parse(profileStr));
    }
  };

  function stringifyNode(node) {
    var s = escodegen.generate(node, {format: {indent: { style: '', base: 0}}});
    if (node instanceof BlockStatement) {
      return uglify('(function()' + s + ')')
        .replace(/^.*?\{/, '').replace(/\}\)$/, '');
    } else {
      return uglify('(' + s + ')')
        .replace(/^\(/, '').replace(/\)$/, '');
    }
  }

  /**
   * Begin rewrite pass
   */

  function shouldLazify(functionNode, length) {
    return (length > minReplaceLength &&
            (!callGraphAvailable || !functionNode.called) &&
            (typeof callProfiling === "undefined" || !callProfiling[functionNode.idNumber]));
  }

  function Laziness() {
    this.functionStrings = Object.create(null);
    this.functionMap = Object.create(null);
    this.isClosure = false;
    this.needsStub = false;
    this.needsStubF = false;
  }

  function stubConstructor(strId, funcId) {
    var tempId = new Identifier("t");
    return new FunctionDeclaration(
      new Identifier("stub"),
      [strId, funcId],
      new BlockStatement([
        new IfStatement(
          new BinaryExpression(
            "===",
            new UnaryExpression(
              "typeof",
              strId
            ),
            new Literal("string")
          ),
          new BlockStatement([
            new VariableDeclaration(
              "var", [
                new VariableDeclarator(
                  tempId,
                  new CallExpression(
                    new Identifier("eval"),
                    [strId]
                  )
                )
              ]
            ),
            new ExpressionStatement(
              new AssignmentExpression(
                new MemberExpression(
                  tempId,
                  new Identifier("prototype"),
                  false,
                  "."
                ),
                "=",
                new MemberExpression(
                  funcId,
                  new Identifier("prototype"),
                  false,
                  "."
                )
              )
            ),
            new ForInStatement(
              new VariableDeclaration(
                "var", [
                  new VariableDeclarator(
                    new Identifier("x")
                  )
                ]
              ),
              funcId,
              new BlockStatement([
                new ExpressionStatement(
                  new AssignmentExpression(
                    new MemberExpression(
                      tempId,
                      new Identifier("x"),
                      true
                    ), "=",
                    new MemberExpression(
                      funcId,
                      new Identifier("x"),
                      true
                    )
                  )
                )
              ])
            ),
            new ReturnStatement(
              tempId
            )
          ])
        ),
        new ReturnStatement(
          funcId
        )
      ])
    );
  }

  function stubFConstructor(strId, funcId, paramsId) {
    var tempId = new Identifier("t");
    return new FunctionDeclaration(
      new Identifier("stubF"),
      [strId, funcId, paramsId],
      new BlockStatement([
        new IfStatement(
          new BinaryExpression(
            "===",
            new UnaryExpression(
              "typeof",
              strId
            ),
            new Literal("string")
          ),
          new BlockStatement([
            new ExpressionStatement(
              new CallExpression(
                new MemberExpression(
                  paramsId,
                  new Identifier("push"),
                  false, "."
                ),
                [strId]
              )
            ),
            new VariableDeclaration(
              "var", [
                new VariableDeclarator(
                  tempId,
                  new CallExpression(
                    new MemberExpression(
                      new Identifier("Function"),
                      new Identifier("apply"),
                      false, "."
                    ),
                    [
                      new Identifier("this"),
                      paramsId
                    ]
                  )
                )
              ]
            ),
            new ExpressionStatement(
              new AssignmentExpression(
                new MemberExpression(
                  tempId,
                  new Identifier("prototype"),
                  false,
                  "."
                ),
                "=",
                new MemberExpression(
                  funcId,
                  new Identifier("prototype"),
                  false,
                  "."
                )
              )
            ),
            new ForInStatement(
              new VariableDeclaration(
                "var", [
                  new VariableDeclarator(
                    new Identifier("x")
                  )
                ]
              ),
              funcId,
              new BlockStatement([
                new ExpressionStatement(
                  new AssignmentExpression(
                    new MemberExpression(
                      tempId,
                      new Identifier("x"),
                      true
                    ), "=",
                    new MemberExpression(
                      funcId,
                      new Identifier("x"),
                      true
                    )
                  )
                )
              ])
            ),
            new ReturnStatement(
              tempId
            )
          ])
        ),
        new ReturnStatement(
          funcId
        )
      ])
    );
  }

  function stub(id, strName, stubName, params) {
    var tempId = new Identifier("t");
    var strId = new Identifier(strName);

    var stubParams = [strId, id];

    if (stubName === "stubF") {
      var paramArray = [];
      for (var i = 0, l = params.length; i < l; i++) {
        paramArray.push(new Literal(params[i].name));
      }
      stubParams.push(new ArrayExpression(paramArray));
    }
    return new BlockStatement([
      // new IfStatement(
      //   new BinaryExpression(
      //     "===",
      //     new UnaryExpression(
      //       "typeof",
      //       strId
      //     ),
      //     new Literal("string")
      //   ),
      //   new BlockStatement([
      //     new VariableDeclaration(
      //       "var", [
      //         new VariableDeclarator(
      //           tempId,
      //           new CallExpression(
      //             new Identifier("eval"),
      //             [strId]
      //           )
      //         )
      //       ]
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         new MemberExpression(
      //           tempId,
      //           new Identifier("prototype"),
      //           false,
      //           "."
      //         ),
      //         "=",
      //         new MemberExpression(
      //           id,
      //           new Identifier("prototype"),
      //           false,
      //           "."
      //         )
      //       )
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         id, "=",
      //         tempId
      //       )
      //     ),
      //     new ExpressionStatement(
      //       new AssignmentExpression(
      //         strId, "=", new Identifier("null")
      //       )
      //     )
      //   ])
      // ),
      new ExpressionStatement(
        new AssignmentExpression(
          id, "=",
          new CallExpression(
            new Identifier(stubName),
            stubParams
          )
        )
      ),
      new ExpressionStatement(
        new AssignmentExpression(
          strId, "=", new Identifier("null")
        )
      ),
      new ReturnStatement(
        new CallExpression(
          new MemberExpression(
            id,
            new Identifier("call"),
            false,
            "."
          ),
          [new Identifier("this")].concat(params)
        )
      )
    ]);

    // return new CallExpression(
    //   new Identifier("eval"),
    //   [new Identifier(strName)]
    // );
  }

  function passOverList(list, passFunction, o) {
    return list.map(function (node) {
      return node[passFunction](o);
    }).filter(function (node) {
      return node !== null;
    });
  }

  Node.prototype.lazyParsePass = T.makePass("lazyParsePass", "lazyParseNode");

  Program.prototype.lazyParsePass = function (o) {
    o.scope = this.frame;
    o = extend(o);
    o.laziness = new Laziness();

    this.body = passOverList(this.body, 'lazyParsePass', o);

    var strId, strDeclaration;
    for (var name in o.laziness.functionStrings) {
      strId = new Identifier(name, "variable");
      strDeclaration = new VariableDeclaration(
        "var",
        [new VariableDeclarator(strId, new Literal(o.laziness.functionStrings[name]), undefined)]
      );
      this.body.unshift(strDeclaration);
    }

    if (o.laziness.needsStub) {
      this.body.unshift(stubConstructor(o.scope.freshTemp(), o.scope.freshTemp()));
    }
    if (o.laziness.needsStubF) {
      this.body.unshift(stubFConstructor(o.scope.freshTemp(), o.scope.freshTemp(), o.scope.freshTemp()));
    }

    return this;
  };

  var curId = 0;
  FunctionExpression.prototype.lazyParsePass =
  FunctionDeclaration.prototype.lazyParsePass = function (o) {
    var childOpts = extend(o);
    childOpts.scope = this.frame;
    childOpts.laziness = new Laziness();
    this.body = this.body.lazyParsePass(childOpts);
    this.idNumber = curId++; // NOTE: this is AFTER parsing children
                             // (as is the post-order traversal in
                             // callGraph)

    var strId, strDeclaration;
    for (var name in childOpts.laziness.functionStrings) {
      strId = new Identifier(name, "variable");
      strDeclaration = new VariableDeclaration(
        "var",
        [new VariableDeclarator(strId, new Literal(childOpts.laziness.functionStrings[name]), undefined)]
      );
      if (this.body instanceof BlockStatement) {
        this.body.body.unshift(strDeclaration);
      } else {
        this.body = new BlockStatement([strDeclaration, this.body]);
      }
    }

    var stub;
    if (childOpts.laziness.needsStub) {
      stub = stubConstructor(o.scope.freshTemp(), o.scope.freshTemp());
      if (this.body instanceof BlockStatement) {
        this.body.body.unshift(stub);
      } else {
        this.body = new BlockStatement([stub, this.body]);
      }
    }
    if (childOpts.laziness.needsStubF) {
      stub = stubFConstructor(o.scope.freshTemp(), o.scope.freshTemp(), o.scope.freshTemp());
      if (this.body instanceof BlockStatement) {
        this.body.body.unshift(stub);
      } else {
        this.body = new BlockStatement([stub, this.body]);
      }
    }

    if (this instanceof FunctionDeclaration) {
      var functionString = stringifyNode(this);
      if (shouldLazify(this, functionString.length)) {
        if (this.id instanceof Identifier && this.params) {
          for (var i = 0, l = this.params.length; i < l; i++) {
            var param = this.params[i];
            if (param instanceof Identifier && this.id.name === param.name) {
              return this;
            }
          }
        }

        var id = o.scope.freshTemp();
        if (childOpts.laziness.isClosure) {
          o.laziness.functionStrings[id.name] = '(' + functionString + ')';
          this.body = stub(this.id, id.name, "stub", this.params);
          o.laziness.needsStub = true;
        } else {
          functionString = stringifyNode(this.body);
          o.laziness.functionStrings[id.name] = functionString;
          this.body = stub(this.id, id.name, "stubF", this.params);
          o.laziness.needsStubF = true;
        }

        o.laziness.functionMap[this.id.name] = {mangled: id.name, isClosure: childOpts.laziness.isClosure, params: this.params};
      }
      return this;
    } else {
      this.isClosure = childOpts.laziness.isClosure;
      return this;
    }
  }

  Identifier.prototype.lazyParseNode = function (o) {
    if (this.name === "eval") {
      o.laziness.isClosure = true;
      return this;
    }
    if (typeof o.laziness.isClosure !== "undefined" &&
        !o.laziness.isClosure &&
        this.kind === "variable") {
      var scope = o.scope;
      var variable = scope.variables[this.name];
      var parentVar = scope.getVariable(this.name);

      if (!parentVar) {
        // Should be the identifier for a catch clause
        return this;
      }

      if (!variable && !parentVar.external) {
        o.laziness.isClosure = true;
      }
    }
    return this;
  }

  AssignmentExpression.prototype.lazyParseNode = function (o) {
    if (this.right instanceof FunctionExpression) {
      var oldId = this.right.id;
      this.right.id = null;
      var functionString = stringifyNode(this.right);
      if (shouldLazify(this.right, functionString.length)) {
        var id = o.scope.freshTemp();

        if (this.left instanceof Identifier && this.params) {
          for (var i = 0, l = this.params.length; i < l; i++) {
            var param = this.params[i];
            if (param instanceof Identifier && this.left.name === param.name) {
              return this;
            }
          }
        }
        //print('compressing function ' + id.name);

        if (this.right.isClosure) {
          o.laziness.functionStrings[id.name] = '(' + functionString + ')';
          this.right.body = stub(this.left, id.name, "stub", this.right.params);
          o.laziness.needsStub = true;
        } else {
          o.laziness.functionStrings[id.name] = stringifyNode(this.right.body);
          this.right.body = stub(this.left, id.name, "stubF", this.right.params);
          o.laziness.needsStubF = true;
        }

        var funcName;
        if (this.left instanceof Identifier) {
          funcName = this.left.name;
        } else {
          funcName = id.name;
        }
        o.laziness.functionMap[funcName] = {mangled: id.name, isClosure: this.right.isClosure, params: this.right.params};
      } else {
        this.right.id = oldId;
      }
    }
    // else if (this.right instanceof Identifier) {
    //   this.right = resolve(this.right, o);
    // }

    return this;
  }

  CallExpression.prototype.lazyParseNode = function (o) {
    for (var i = 0, l = this.arguments.length; i < l; i++) {
      this.arguments[i] = resolve(this.arguments[i], o);
    }
  }

  function resolve(identifier, o) {
    if (identifier.variable && identifier.variable.type instanceof Types.ArrowType
       && o.laziness.functionMap[identifier.name]) {
      var functionEntry = o.laziness.functionMap[identifier.name];
      var strId = new Identifier(o.laziness.functionMap[identifier.name].mangled);

      var stubCall;
      if (functionEntry.isClosure) {
        stubCall = new CallExpression(
          new Identifier("stub"),
          [strId, identifier]
        );
      } else {
        var paramArray = [];
        for (var i = 0, l = functionEntry.params.length; i < l; i++) {
          paramArray.push(new Literal(functionEntry.params[i].name));
        }
        stubCall = new CallExpression(
          new Identifier("stubF"),
          [strId, identifier, new ArrayExpression(paramArray)]
        );
      }

      return new SequenceExpression([
        new AssignmentExpression(
          identifier, "=",
          stubCall
        ),
        new AssignmentExpression(
          strId, "=",
          new Identifier("null")
        ),
        identifier
      ]);
    } else {
      return identifier;
    }
  }

}).call(this, typeof exports === "undefined" ? (lazyParse = {}) : exports);
<|endoftext|>


<commit_before>"use strict";

const nodepath = require('path');
const Ajv = require('ajv');
const ajv = new Ajv({
  useDefaults: true,
  format: false,
  extendRefs: true,
});
const Response = require('./response');
const Context = require('./context');

const DEFAULTS = {
  handler: _ => Promise.resolve(null),
  title: '',
  description: '',
  inputs: null,
  inputSchema: null,
  outputSchema: {},
  authorizers: {},
  security: {},
}

/**
 * Creates a new Action
 * @class Action
 * @param {Object} options
 * @param {Function} options.handler
 * @param {string} options.title
 * @param {string} options.description
 * @param {Object} options.inputSchema - JSON Schema
 * @param {Array} options.inputs
 * @param {Object} options.inputs[] - JSON Schema
 * @param {Object} outputSchema - JSON Schema
 * @param {Object} authorizers
 */

const Action = module.exports = function(opts) {
  opts = opts || {};
  for (let key in DEFAULTS) {
    this[key] = opts[key] || DEFAULTS[key];
  }

  if (opts.inputs) {
    this.inputSchema = {type: 'object', properties: {}};
    this.inputSchema.required = opts.inputs
      .filter(i => i.default === undefined)
      .map(i => i.title);
    if (!this.inputSchema.required.length) {
      delete this.inputSchema.required;
    }
    opts.inputs.forEach(input => {
      this.inputSchema.properties[input.title] = input;
    });
  }
  this.inputSchema = this.inputSchema || {};
  this.validateInput = ajv.compile(this.inputSchema);
}

Action.fromName = function(name, directory) {
  let isFile = /^\.?\//.test(name);
  if (isFile) return require(nodepath.join(directory, name));
  let slash = name.indexOf('/');
  if (slash === -1) throw new Error("Could not find action " + name);
  const Integration = require('./integration');
  let integration = Integration.fromName(name.substring(0, slash));
  let action = integration.action(name.substring(slash + 1, name.length));
  return action;
}

Action.prototype.clone = function(opts) {
  opts = opts || {};
  for (let key in DEFAULTS) {
    opts[key] = opts[key] === undefined ? this[key] : opts[key];
  }
  return new Action(opts);
}

Action.prototype.run = function(input, ctx) {
  ctx = ctx || new Context();
  if (input === undefined && this.inputSchema.type === 'object') {
    input = {};
  }
  let valid = this.validateInput(input);
  if (!valid) {
    let error = new Error(ajv.errorsText(this.validateInput.errors));
    error.statusCode = 400;
    return Promise.reject(error);
  }
  let allAuthorizers = {};
  if (this.project) {
    for (let key in this.project.authorizers) {
      allAuthorizers[key] = this.project.authorizers[key];
    }
  }
  for (let key in this.authorizers) allAuthorizers[key] = this.authorizers[key];
  let promise = Promise.all(Object.keys(allAuthorizers).map(key => {
    let authorizer = allAuthorizers[key];
    if (authorizer === this) throw new Error("Action has itself as an authorizer");
    return authorizer.run(input, ctx)
      .then(acct => {
        if (acct instanceof Response) throw acct;
        if (acct) ctx.accounts[key] = acct;
      });
  }));

  return promise.then(_ => {
    let ret = this.handler(input, ctx);
    if (ret === undefined) throw new Error("Handler must return a Promise, Response, or value");
    return ret;
  });
}
<commit_msg>set args.input to null if undefined<commit_after>"use strict";

const nodepath = require('path');
const Ajv = require('ajv');
const ajv = new Ajv({
  useDefaults: true,
  format: false,
  extendRefs: true,
});
const Response = require('./response');
const Context = require('./context');

const DEFAULTS = {
  handler: _ => Promise.resolve(null),
  title: '',
  description: '',
  inputs: null,
  inputSchema: null,
  outputSchema: {},
  authorizers: {},
  security: {},
}

/**
 * Creates a new Action
 * @class Action
 * @param {Object} options
 * @param {Function} options.handler
 * @param {string} options.title
 * @param {string} options.description
 * @param {Object} options.inputSchema - JSON Schema
 * @param {Array} options.inputs
 * @param {Object} options.inputs[] - JSON Schema
 * @param {Object} outputSchema - JSON Schema
 * @param {Object} authorizers
 */

const Action = module.exports = function(opts) {
  opts = opts || {};
  for (let key in DEFAULTS) {
    this[key] = opts[key] || DEFAULTS[key];
  }

  if (opts.inputs) {
    this.inputSchema = {type: 'object', properties: {}};
    this.inputSchema.required = opts.inputs
      .filter(i => i.default === undefined)
      .map(i => i.title);
    if (!this.inputSchema.required.length) {
      delete this.inputSchema.required;
    }
    opts.inputs.forEach(input => {
      this.inputSchema.properties[input.title] = input;
    });
  }
  this.inputSchema = this.inputSchema || {};
  this.validateInput = ajv.compile(this.inputSchema);
}

Action.fromName = function(name, directory) {
  let isFile = /^\.?\//.test(name);
  if (isFile) return require(nodepath.join(directory, name));
  let slash = name.indexOf('/');
  if (slash === -1) throw new Error("Could not find action " + name);
  const Integration = require('./integration');
  let integration = Integration.fromName(name.substring(0, slash));
  let action = integration.action(name.substring(slash + 1, name.length));
  return action;
}

Action.prototype.clone = function(opts) {
  opts = opts || {};
  for (let key in DEFAULTS) {
    opts[key] = opts[key] === undefined ? this[key] : opts[key];
  }
  return new Action(opts);
}

Action.prototype.run = function(input, ctx) {
  ctx = ctx || new Context();
  if (input === undefined) input = null;
  let valid = this.validateInput(input);
  if (!valid) {
    let error = new Error(ajv.errorsText(this.validateInput.errors));
    error.statusCode = 400;
    return Promise.reject(error);
  }
  let allAuthorizers = {};
  if (this.project) {
    for (let key in this.project.authorizers) {
      allAuthorizers[key] = this.project.authorizers[key];
    }
  }
  for (let key in this.authorizers) allAuthorizers[key] = this.authorizers[key];
  let promise = Promise.all(Object.keys(allAuthorizers).map(key => {
    let authorizer = allAuthorizers[key];
    if (authorizer === this) throw new Error("Action has itself as an authorizer");
    return authorizer.run(input, ctx)
      .then(acct => {
        if (acct instanceof Response) throw acct;
        if (acct) ctx.accounts[key] = acct;
      });
  }));

  return promise.then(_ => {
    let ret = this.handler(input, ctx);
    if (ret === undefined) throw new Error("Handler must return a Promise, Response, or value");
    return ret;
  });
}
<|endoftext|>