<commit_before>(function() {
    extend = function ( t, s, b ) {
        if (s) {
            for ( var k in s ) {
                if (!b || !t.hasOwnProperty(k)) {
                    t[k] = s[k];
                }
            }
        }
        return t;
    };

    var randomArray = function(array,source){
        if(array.length == 0) {
            return null;
        }
        var key =  parseInt(Math.random()*array.length);
        var res = array[key];
        if(source) {
            if(!source[res]) {
                var cloneArr = array.concat([]);
                cloneArr.splice(key,1);
                return randomArray(cloneArr,source);
            } else {
                return res;
            }
        } else {
            return res;
        }
    }
    var Snake = function(cfg){
        var _default = {
            columns : 25,
            rows : 25,
            unitLength : 15,
            containerId : '',
            defCss : 'snake',
            activeCssList : ['a','b','c'],
            allowBack : false,
            speed : 400,
            score : 1, //每吃一个食物所加的分数,
            range : 3, //出现特殊食物的机率
            maxUsers : 5
        };

        this.config = function(cfg){
            if(cfg) {
                extend(_default,cfg);
            }
            return  _default;
        }
    }

    Snake.prototype = {
        __fill : {}, //记录所有块。

        __map : {}, //此处记录块有没有被占用

        __keyMap : [],

        __foodPlugins : [],

        __timer  : null,

        __foodNums : 0,

        __events : {},

        players : {},

        alives : 0,

        setKey :  function(l,t){
            return l + '_' + t;
        },
        draw : function(){
            var cfg = this.config();
            var container = document.getElementById(cfg.containerId);

            if(!container) {
                return;
            }

            for(var j = 1; j <= cfg.rows; j ++) {
                for(var i = 1; i <= cfg.columns ; i++) {
                    var key = this.setKey(i,j);
                    var span = document.createElement('span');
                    span.className = cfg.defCss;
                    span.style.display = 'inline-block';
                    span.style.width = cfg.unitLength + 'px';
                    span.style.height = cfg.unitLength + 'px';

                    this.__fill[key] = span;
                    this.__fill[key]._offset = {left : i, top :j};
                    this.__map[key] = 1;
                    this.__keyMap.push(key);
                    container.appendChild(span);
                }
            }
            container.style.width = cfg.unitLength * cfg.columns + 'px'
            container.style.height = cfg.unitLength * cfg.rows + 'px'
        },

        createBlock : function(l,t,c){
            var cfg = this.config();
            var key = this.setKey(l,t);
            if(!this.__fill[key] || (!this.__fill[key].plugin && this.__map[key] == 0)) {
                return null;
            }
            this.__fill[key].className = c || '';
            this.__map[key] = 0;
            return this.__fill[key];
        },

        deleteBlock : function(l,t) {
            var cfg = this.config();
            var key = this.setKey(l,t);
            if(!this.__fill[key]) {
                return;
            }
            this.__fill[key].className = cfg.defCss;
            this.__fill[key].plugin = null;
            this.__map[key] = 1;
        },

        randomBlock : function(c){
            var key = randomArray(this.__keyMap,this.__map);
            if(key) {
                var _key = key.split('_');
                return this.createBlock(_key[0],_key[1],c);
            }
            return null
        },

        regFood : function(o){
            this.__foodPlugins.push(o);
        },

        regBaseFood : function(json){
            var cfg = this.config();
            var _json = extend({
                fp : 0,
                effect : function(player,snake,food){
                    snake.body.unshift(food);
                },
                score : cfg.score,
                cssName : 'b'
            },json || {});
            this.__foodPlugins.unshift(_json);
        },

        createFood : function(c){
            var cfg = this.config();
            var range = Math.ceil(Math.random()*10)
            var plugin;
            if(range <= cfg.range && this.__foodPlugins.length >1) {
                var _food = this.__foodPlugins.concat([]);
                _food.shift();
                plugin = randomArray(_food);
            } else {
                plugin = this.__foodPlugins[0];
            }
            var fBody = this.randomBlock(plugin.cssName);
            fBody.plugin = plugin;
            this.__foodNums ++;
            return fBody;
        },
        removeFood : function(food){
            this.deleteBlock(food);
            this.__foodNums --;
            food.plugin = null;
            if(this.__foodNums <0) {
                this.__foodNums = 0;
            }
        },
        eatFood : function(player,snake,food){

            var cfg = this.config();
            food.plugin.effect.apply(this,arguments);
            player.scores += player.baseScore * food.plugin.score;
            this.evtFire('eat',arguments);
            this.removeFood(food);
            this.createFood();
        },
        createSnake : function(length, direction, id, c){
            var snakeBody = [];
            var cfg = this.config();
            switch(direction) {
                case 'up' :
                    while(length --) {
                        var l = Math.max(parseInt(cfg.columns/cfg.maxUsers),1) * id;
                        var t = cfg.rows - length;
                        snakeBody.push(this.createBlock(l,t,c));
                    }
                break;

                case 'down' :
                    while(length --) {
                        var l = Math.max(parseInt(cfg.columns/cfg.maxUsers),1) * id;
                        var t = length;
                        snakeBody.push(this.createBlock(l,t ,c));
                    }
                break;

                case 'left' :
                    while(length --) {
                        var l = cfg.columns - length;
                        var t = Math.max(parseInt(cfg.rows/cfg.maxUsers),1) * id;
                        snakeBody.push(this.createBlock(l,t,c));
                    }
                break;

                case 'right' :
                    while(length --) {
                        var l = cfg.columns + length;
                        var t = Math.max(parseInt(cfg.rows/cfg.maxUsers),1) * id;
                        snakeBody.push(this.createBlock(l, t ,c));
                    }
                break;
            }
            return snakeBody;
        },

        addPlayers : function(players){
            var _player = {
                length : 2, //初始长度
                direction : 'left',//初始方向
                name : 'user' + +new Date, //玩家名称
                scores : 0, //初始分数
                cssName : 'defUser', //初始样式
                baseScore : 1,
            };
            var cfg = this.config();
            var players = players || [{}];
            for(var p=0; p<players.length; p++){
                var item = extend(_player,players[p]);
                if(!this.players[item.name]) {
                    this.players[item.name] = {
                        snake : {
                            cssName : item.cssName,
                            body : this.createSnake(item.length,item.direction,this.alives + 1,item.cssName),
                            speed : cfg.speed,
                            status : 'alive',
                            direction : item.direction
                        },
                        name : item.name,
                        scores : item.scores,
                        baseScore : item.baseScore, //分数倍数
                        id : this.alives + 1
                    };
                    this.alives ++;
                }

            }

            this.evtFire('addplayer',[this.players]);
        },

        removePlayer : function(playerName){
            if(this.players[playerName]){
                this.removeSnake(this.players[playerName].snake.body);
                this.players[playerName] = null;
                delete this.players[playerName];
                this.alives --
            }
        },

        removeSnake : function(snakeBody){
            for(var i = 0; i<snakeBody.length; i++) {
                var item = snakeBody[i];
                this.deleteBlock(item._offset.left,item._offset.top);
            }
        },

        __fastInit : function(){ //测试用
            this.draw() //画布
            var cfg = this.config();
            this.regBaseFood();
            this.addPlayers({

            });//增加玩家
            this.createFood();
        },

        init : function(){
            this.draw();
            var cfg = this.config();
            this.regBaseFood();

            //this.createFood();
            var _t = this;
        },

        snakeMove : function(){
            var cfg = this.config();

            if(this.alives == 0) {
                this.evtFire('gameover');
                this.pause();
            }
            for(var i in this.players) {
                var player = this.players[i];
                if(!player) {
                    continue;
                }
                if(player.snake.status == 'alive') {
                    var snake = player.snake.body;
                    var nextHead;
                    if(player.snake._direction) {
                        player.snake.direction = player.snake._direction;
                        player.snake._direction = null;
                    }
                    switch(player.snake.direction) {
                        case 'up' :
                            nextHead = this.createBlock(snake[0]._offset.left,snake[0]._offset.top - 1, player.snake.cssName);
                        break;
                        case 'down' :
                            nextHead = this.createBlock(snake[0]._offset.left,snake[0]._offset.top + 1, player.snake.cssName);
                        break;
                        case 'left' :
                            console.info(snake[0])
                            nextHead = this.createBlock(snake[0]._offset.left - 1,snake[0]._offset.top, player.snake.cssName);
                        break;
                        case 'right' :
                            nextHead = this.createBlock(snake[0]._offset.left + 1,snake[0]._offset.top, player.snake.cssName);
                        break;
                    }

                    if(!nextHead) {
                        console.info('game over');
                        player.snake.status = 'died';
                        return ;
                    }

                    if(nextHead.plugin) { //吃食物
                        this.eatFood(player,player.snake,nextHead);
                    } else {

                        var disBlock = snake.pop()
                        this.deleteBlock( disBlock._offset.left,disBlock._offset.top);
                        snake.unshift(nextHead);
                    }
                } else if(player.snake.status == 'died') {
                    var _player = extend({},player);
                    this.removePlayers(player.name);
                    this.evtFire('died',[_player]);
                }
            }
        },

        //运动相关
        run : function(){
            var _t =this;
            var cfg = _t.config();
            var cFood =  [0,1,2,2,3,3][this.alives];
            for(var i = 0; i< cFood; i++) {
                this.createFood();
            }
            _t.evtFire('start');
            if(_t.__timer) {
                clearInterval(this.__timer);
            }
            _t.__timer = setInterval(function(){
                _t.snakeMove();
            _t.evtFire('starting');
            },cfg.speed);
        },
        pause : function () {
            clearInterval(this.__timer);
            this.__timer = null
            this.evtFire('pause');
        },
        setDirection : function(snake,direction) { //设置移动方向
            var cfg  = this.config();
            if(snake.status != 'alive') {
                return;
            }
            var oldD = snake.direction, newD = direction;
            var rule = {
                'up' : 1,
                'left' : 2,
                'down' : 3,
                'right' : 4
            };

            if(!rule[newD]) {
                return false;
            }
            if(!cfg.allowBack && Math.abs(rule[oldD] - rule[newD]) == 2) {//不能直接向后转弯
                return false
            }
            snake._direction = direction;
            return true;
        },

        bind : function(evt, alias, handler){
            if(!this.__events[evt]) {
                this.__events[evt] = {};
            }
            this.__events[evt][alias] = handler;
        },
        unbind : function(evt,alias){
            if(!this.__events[evt]) {
                return;
            }
            if(alias) {
                this.__events[evt][alias] = null;
            } else {
                this.__events[evt] = null;
            }
        },
        evtFire : function(evt,data){
            if(!this.__events[evt]) {
                return;
            }

            for(var alias in  this.__events[evt]) {
                var func = this.__events[evt][alias];
                if(typeof func == 'function') {
                    this.__events[evt][alias].apply(this,data);
                }
            }
        }
    }
    window.Snake = Snake;
} ());

<commit_msg>addplayer<commit_after>(function() {
    extend = function ( t, s, b ) {
        if (s) {
            for ( var k in s ) {
                if (!b || !t.hasOwnProperty(k)) {
                    t[k] = s[k];
                }
            }
        }
        return t;
    };

    var randomArray = function(array,source){
        if(array.length == 0) {
            return null;
        }
        var key =  parseInt(Math.random()*array.length);
        var res = array[key];
        if(source) {
            if(!source[res]) {
                var cloneArr = array.concat([]);
                cloneArr.splice(key,1);
                return randomArray(cloneArr,source);
            } else {
                return res;
            }
        } else {
            return res;
        }
    }
    var Snake = function(cfg){
        var _default = {
            columns : 25,
            rows : 25,
            unitLength : 15,
            containerId : '',
            defCss : 'snake',
            activeCssList : ['a','b','c'],
            allowBack : false,
            speed : 400,
            score : 1, //每吃一个食物所加的分数,
            range : 3, //出现特殊食物的机率
            maxUsers : 5
        };

        this.config = function(cfg){
            if(cfg) {
                extend(_default,cfg);
            }
            return  _default;
        }
    }

    Snake.prototype = {
        __fill : {}, //记录所有块。

        __map : {}, //此处记录块有没有被占用

        __keyMap : [],

        __foodPlugins : [],

        __timer  : null,

        __foodNums : 0,

        __events : {},

        players : {},

        alives : 0,

        setKey :  function(l,t){
            return l + '_' + t;
        },
        draw : function(){
            var cfg = this.config();
            var container = document.getElementById(cfg.containerId);

            if(!container) {
                return;
            }

            for(var j = 1; j <= cfg.rows; j ++) {
                for(var i = 1; i <= cfg.columns ; i++) {
                    var key = this.setKey(i,j);
                    var span = document.createElement('span');
                    span.className = cfg.defCss;
                    span.style.display = 'inline-block';
                    span.style.width = cfg.unitLength + 'px';
                    span.style.height = cfg.unitLength + 'px';

                    this.__fill[key] = span;
                    this.__fill[key]._offset = {left : i, top :j};
                    this.__map[key] = 1;
                    this.__keyMap.push(key);
                    container.appendChild(span);
                }
            }
            container.style.width = cfg.unitLength * cfg.columns + 'px'
            container.style.height = cfg.unitLength * cfg.rows + 'px'
        },

        createBlock : function(l,t,c){
            var cfg = this.config();
            var key = this.setKey(l,t);
            if(!this.__fill[key] || (!this.__fill[key].plugin && this.__map[key] == 0)) {
                return null;
            }
            this.__fill[key].className = c || '';
            this.__map[key] = 0;
            return this.__fill[key];
        },

        deleteBlock : function(l,t) {
            var cfg = this.config();
            var key = this.setKey(l,t);
            if(!this.__fill[key]) {
                return;
            }
            this.__fill[key].className = cfg.defCss;
            this.__fill[key].plugin = null;
            this.__map[key] = 1;
        },

        randomBlock : function(c){
            var key = randomArray(this.__keyMap,this.__map);
            if(key) {
                var _key = key.split('_');
                return this.createBlock(_key[0],_key[1],c);
            }
            return null
        },

        regFood : function(o){
            this.__foodPlugins.push(o);
        },

        regBaseFood : function(json){
            var cfg = this.config();
            var _json = extend({
                fp : 0,
                effect : function(player,snake,food){
                    snake.body.unshift(food);
                },
                score : cfg.score,
                cssName : 'b'
            },json || {});
            this.__foodPlugins.unshift(_json);
        },

        createFood : function(c){
            var cfg = this.config();
            var range = Math.ceil(Math.random()*10)
            var plugin;
            if(range <= cfg.range && this.__foodPlugins.length >1) {
                var _food = this.__foodPlugins.concat([]);
                _food.shift();
                plugin = randomArray(_food);
            } else {
                plugin = this.__foodPlugins[0];
            }
            var fBody = this.randomBlock(plugin.cssName);
            fBody.plugin = plugin;
            this.__foodNums ++;
            return fBody;
        },
        removeFood : function(food){
            this.deleteBlock(food);
            this.__foodNums --;
            food.plugin = null;
            if(this.__foodNums <0) {
                this.__foodNums = 0;
            }
        },
        eatFood : function(player,snake,food){

            var cfg = this.config();
            food.plugin.effect.apply(this,arguments);
            player.scores += player.baseScore * food.plugin.score;
            this.evtFire('eat',arguments);
            this.removeFood(food);
            this.createFood();
        },
        createSnake : function(length, direction, id, c){
            var snakeBody = [];
            var cfg = this.config();
            switch(direction) {
                case 'up' :
                    while(length --) {
                        var l = Math.max(parseInt(cfg.columns/cfg.maxUsers),1) * id;
                        var t = cfg.rows - length;
                        snakeBody.push(this.createBlock(l,t,c));
                    }
                break;

                case 'down' :
                    while(length --) {
                        var l = Math.max(parseInt(cfg.columns/cfg.maxUsers),1) * id;
                        var t = length;
                        snakeBody.push(this.createBlock(l,t ,c));
                    }
                break;

                case 'left' :
                    while(length --) {
                        var l = cfg.columns - length;
                        var t = Math.max(parseInt(cfg.rows/cfg.maxUsers),1) * id;
                        snakeBody.push(this.createBlock(l,t,c));
                    }
                break;

                case 'right' :
                    while(length --) {
                        var l = cfg.columns + length;
                        var t = Math.max(parseInt(cfg.rows/cfg.maxUsers),1) * id;
                        snakeBody.push(this.createBlock(l, t ,c));
                    }
                break;
            }
            return snakeBody;
        },

        addPlayer : function(player){
            var _player = {
                length : 2, //初始长度
                direction : 'left',//初始方向
                name : 'user' + +new Date, //玩家名称
                scores : 0, //初始分数
                cssName : 'defUser', //初始样式
                baseScore : 1,
            };
            var cfg = this.config();
            var item = extend(_playear,player || {});

            if(!this.players[item.name]) {
                this.players[item.name] = {
                    snake : {
                        cssName : item.cssName,
                        body : this.createSnake(item.length,item.direction,this.alives + 1,item.cssName),
                        speed : cfg.speed,
                        status : 'alive',
                        direction : item.direction
                    },
                    name : item.name,
                    scores : item.scores,
                    baseScore : item.baseScore, //分数倍数
                    id : this.alives + 1
                };
                this.alives ++;
                this.evtFire('addplayer',[this.players]);
            }
        },

        addPlayers : function(players){
            for(var p=0; p<players.length; p++){
                this.addPlayer(players[p])
            }

        },

        removePlayer : function(playerName){
            if(this.players[playerName]){
                this.removeSnake(this.players[playerName].snake.body);
                this.players[playerName] = null;
                delete this.players[playerName];
                this.alives --
            }
        },

        removeSnake : function(snakeBody){
            for(var i = 0; i<snakeBody.length; i++) {
                var item = snakeBody[i];
                this.deleteBlock(item._offset.left,item._offset.top);
            }
        },

        __fastInit : function(){ //测试用
            this.draw() //画布
            var cfg = this.config();
            this.regBaseFood();
            this.addPlayers({

            });//增加玩家
            this.createFood();
        },

        init : function(){
            this.draw();
            var cfg = this.config();
            this.regBaseFood();

            //this.createFood();
            var _t = this;
        },

        snakeMove : function(){
            var cfg = this.config();

            if(this.alives == 0) {
                this.evtFire('gameover');
                this.pause();
            }
            for(var i in this.players) {
                var player = this.players[i];
                if(!player) {
                    continue;
                }
                if(player.snake.status == 'alive') {
                    var snake = player.snake.body;
                    var nextHead;
                    if(player.snake._direction) {
                        player.snake.direction = player.snake._direction;
                        player.snake._direction = null;
                    }
                    switch(player.snake.direction) {
                        case 'up' :
                            nextHead = this.createBlock(snake[0]._offset.left,snake[0]._offset.top - 1, player.snake.cssName);
                        break;
                        case 'down' :
                            nextHead = this.createBlock(snake[0]._offset.left,snake[0]._offset.top + 1, player.snake.cssName);
                        break;
                        case 'left' :
                            nextHead = this.createBlock(snake[0]._offset.left - 1,snake[0]._offset.top, player.snake.cssName);
                        break;
                        case 'right' :
                            nextHead = this.createBlock(snake[0]._offset.left + 1,snake[0]._offset.top, player.snake.cssName);
                        break;
                    }

                    if(!nextHead) {
                        console.info('game over');
                        player.snake.status = 'died';
                        return ;
                    }

                    if(nextHead.plugin) { //吃食物
                        this.eatFood(player,player.snake,nextHead);
                    } else {

                        var disBlock = snake.pop()
                        this.deleteBlock( disBlock._offset.left,disBlock._offset.top);
                        snake.unshift(nextHead);
                    }
                } else if(player.snake.status == 'died') {
                    var _player = extend({},player);
                    this.removePlayers(player.name);
                    this.evtFire('died',[_player]);
                }
            }
        },

        //运动相关
        run : function(){
            var _t =this;
            var cfg = _t.config();
            var cFood =  [0,1,2,2,3,3][this.alives];
            for(var i = 0; i< cFood; i++) {
                this.createFood();
            }
            _t.evtFire('start');
            if(_t.__timer) {
                clearInterval(this.__timer);
            }
            _t.__timer = setInterval(function(){
                _t.snakeMove();
            _t.evtFire('starting');
            },cfg.speed);
        },
        pause : function () {
            clearInterval(this.__timer);
            this.__timer = null
            this.evtFire('pause');
        },
        setDirection : function(snake,direction) { //设置移动方向
            var cfg  = this.config();
            if(snake.status != 'alive') {
                return;
            }
            var oldD = snake.direction, newD = direction;
            var rule = {
                'up' : 1,
                'left' : 2,
                'down' : 3,
                'right' : 4
            };

            if(!rule[newD]) {
                return false;
            }
            if(!cfg.allowBack && Math.abs(rule[oldD] - rule[newD]) == 2) {//不能直接向后转弯
                return false
            }
            snake._direction = direction;
            return true;
        },

        bind : function(evt, alias, handler){
            if(!this.__events[evt]) {
                this.__events[evt] = {};
            }
            this.__events[evt][alias] = handler;
        },
        unbind : function(evt,alias){
            if(!this.__events[evt]) {
                return;
            }
            if(alias) {
                this.__events[evt][alias] = null;
            } else {
                this.__events[evt] = null;
            }
        },
        evtFire : function(evt,data){
            if(!this.__events[evt]) {
                return;
            }

            for(var alias in  this.__events[evt]) {
                var func = this.__events[evt][alias];
                if(typeof func == 'function') {
                    this.__events[evt][alias].apply(this,data);
                }
            }
        }
    }
    window.Snake = Snake;
} ());

<|endoftext|>


<commit_before>// Copyright (c) 2014, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

Polymer('cde-polymer-designer', {
  // NOTE: Make sure this is in-sync with ide/web/manifest.json.
  _STORAGE_PARTITION: 'cde-polymer-designer',
  // NOTE: We must use the full path as viewed by a client app.
  _LOCAL_ENTRY_POINT:
      'packages/cde_polymer_designer/src/polymer_designer/index.html',
  _ONLINE_ENTRY_POINT:
      'https://www.polymer-project.org/tools/designer/',

  /**
   * Specifies whether to use the local, compiled-in copy of Polymer Designer
   * or the online one.
   *
   * @attribute attr
   * @type string
   */
  entryPoint: 'local',

  /**
   * Fires after the <webview> has been created, its UI tweaked and the proxy
   * script injected into it.
   *
   * @type: Promise
   */
  _webviewReady: null,

  /**
   * Fires when the asynchronous code export requested from the proxy has
   * completed.
   *
   * @type: Promise<String>
   */
  _codeExported: null,

  /**
   * The dynamically created <webview> element.
   */
  _webview: null,

  ready: function() {
  },

  attached: function() {
    assert(entryPoint in ['local', 'inline']);

    _registerDesignerProxyListener();
  },

  /**
   * Initializes this object from an idle state:
   * - Creates a new <webview>.
   * - Inserts it into the shadow DOM.
   * - Navigates it to the Polymer Designer local or online entry point.
   * - Registers an event listener to complete initialization once the
   * <webview> is up and running.
   *
   * @type: Promise
   */
  load: function() {
    this._webviewReady = new Promise();

    // TODO(ussuri): BUG #3466.
    setTimeout(function() {
      this._webview = document.createElement('webview');
      this._webview.addEventListener(
          'contentload', this._onWebviewContentLoad);
      this._webview.partition = this._STORAGE_PARTITION;
      this._webview.src =
          this.entryPoint == 'local' ?
          this._LOCAL_ENTRY_POINT :
          this._ONLINE_ENTRY_POINT;
      this.shadowRoot.append(this._webview);
    }, 500);

    // TODO(ussuri): Try enabling once BUG #3467 is resolved.
    // this._webviewReady.then(function() {
    //   _webview.setZoom(0.8);
    // });

    return this._webviewReady;
  },

  /**
   * Reinitializes the object from a running state.
   *
   * @type: Promise
   */
  reload: function() {
    this.unload();
    return this.load();
  },

  /**
   * Returns the object to the idle state:
   * - terminates the running <webview>, if any.
   * - removes the <webview> element from the shadow DOM.
   * - Resets the [Completer]s.
   *
   * @type: void
   */
  unload: function() {
    if (this._webview != null) {
      this._webview.terminate();
      this._webview.remove();
    }
    this._webview = this._webviewReady = this._codeExported = null;
  },

  /**
   * Sets the internal design code inside the Polymer Designer running within
   * the <webview>. This results in PD re-rendering the design, if it can
   * parse the code. If it can't, it resets the design to an empty one.
   *
   * @type: Promise
   */
  setCode: function(code) {
    // Escape all special charachters and enclose in double-quotes.
    code = JSON.encode(code);
    // Just in case we are called too early, wait until the webview is ready.
    return this._webviewReady.then((_) {
      // TODO(ussuri): This doesn't work without a delay:
      // find some way to detect when PD is fully ready.
      return setTimeout(function() {
        // The request is received and handled by the JS proxy script injected
        // into [_webview>] by [_injectDesignerProxy].
        return this._executeScriptInWebview(function() {
          window.postMessage(
              {action: 'import_code', code: ''' + code + r'''}, '*');
        });
      }, 500);
    });
  },

  /**
   * Extracts and returns to the caller the current design code from the
   * Polymer Designer.
   *
   * @type: Promise<String>
   */
  getCode: function() {
    if (_codeExported == null) {
      _codeExported = new Completer<String>();
      // Just in case we are called too early, wait until the webview is ready.
      _webviewReady.future.then((_) {
        // The request is received and handled by [_designerProxy], which
        // asynchronously returns the result via a chrome.runtime.sendMessage,
        // received and handled by [_designProxyListener], which completes the
        // [_codeExported] completer with the code string.
        _executeScriptInWebview(r'''
            window.postMessage({action: 'export_code'}, '*');
        ''');
      });
    } else {
      // There already is a pending [getCode] request: just fall through
      // to returning the same future to the new caller.
    }
    return _codeExported.future;
  },

  /**
   * Tweaks the Polymer Designer UI and inject a proxy script.
   *
   * @type: void
   */
  _onWebviewContentLoad: function(event) {
    _tweakDesignerUI();
    _injectDesignerProxy().then((_) => _webviewReady.complete());
  },

  /**
   * Tweaks the Polymer Designer UI by inserting some CSS into [_webview]:
   * - Reduces font sizes.
   * - Reduces toolbar sizes.
   * - Removes some buttons we don't need.
   *
   * @type: void
   */
  _tweakDesignerUI: function() {
    // TODO(ussuri): Some of this will become unnecessary once BUG #3467 is
    // resolved.
    _insertCssIntoWebview(r'''
        /* Reduce the initial font sizes */
        html /deep/ *, html /deep/ #tabs > * {
          font-size: 13px;
        }
        html /deep/ #tabs {
          padding-top: 0;
          padding-bottom: 0;
        }
        html /deep/ core-toolbar,
        html /deep/ core-toolbar::shadow > #topBar {
          height: 40px;
        }
        /* Hide some UI elements we don't need */
        #designer::shadow > #appbar > * {
          display: none;
        }
        #designer::shadow > #appbar > .design-controls {
          display: block;
        }
        #designer::shadow > #appbar > .design-controls > .separator:first-child {
          display: none;
        }
    ''');
  },

  /**
   * Handles responses sent from inside [_webview] by [_designerProxy].
   *
   * @type: void
   */
  _designerProxyListener: function(event) {
    // TODO(ussuri): Check the sender?
    if (event.message['type'] == 'export_code_response') {
      _codeExported.complete("${event.message['code']}");
      // Null the completer so new [getCode] requests can work properly.
      _codeExported = null;
    }
  },

  /**
   * Registers a listener for responses sent by [_designerProxy].
   *
   * @type: void
   */
  _registerDesignerProxyListener: function() {
    chrome.runtime.onMessage.listen(_designerProxyListener);
  },

  /**
   * The proxy script injected into the scripting context ("main world") of
   * [_webview] in order to receive and handle requests on behalf of this object.
   *
   * @type: string
   */
  _designerProxy: function() {
      // This will receive events sent by the main code via window.postMessage
      // executed inside [_webview]'s DOM.
      window.addEventListener("message", function(event) {
        var designer = document.querySelector('#designer');
        var action = event.data['action'];
        if (action === 'export_code') {
          chrome.runtime.sendMessage(
              {type: 'export_code_response', code: designer.html});
        } else if (action === 'import_code') {
          // After this, PD will detect the new design code and try to parse
          // and render it.
          designer.loadHtml(event.data['code']);
        }
      });
  },

  /**
   * Injects [_designerProxy] into the scripting context ("main world") of
   * [_webview]. This is necessary in contrast to the "isolated world" in
   * order for the proxy to have access to the JS context of the Designer
   * (within "isolated world", the proxy would only see the Designer's DOM).
   *
   * @type: Promise
   */
  _injectDesignerProxy: function() {
    // TODO(ussuri): Check the sender?
    return _injectScriptIntoWebviewMainWorld(_designerProxy);
  },

  /**
   * Effectively injects and runs a given script in the [_webview]'s scripting
   * context ("main world"). This is achieved by creating a new <script> tag in
   * the [_webview]'s DOM and setting its inner HTML to the script's code.
   * Doing that will cause the script to run and be able to register event
   * listeners which will have access to various Polymer Designer's JS
   * properties.
   *
   * @type: Promise
   */
  _injectScriptIntoWebviewMainWorld: function(script) {
    // Escape all special charachters and enclose in double-quotes.
    script = JSON.encode('(function() { $script; })()');
    return _executeScriptInWebview(r'''
        (function() {
          var scriptTag = document.createElement('script');
          scriptTag.innerHTML = ''' + script + r''';
          document.body.appendChild(scriptTag);
        })();
    ''');
  },

  /**
   * Executes a script in [_webview]'s "isolated world", which give the script
   * access only to the webview's DOM, but not the JS context.
   *
   * @type: Promise<dynamic>
   */
  _executeScriptInWebview: function(script) {
    final js.JsObject jsScript = new js.JsObject.jsify({'code': script});
    final completer = new Completer();
    _webview.callMethod('executeScript',
        [jsScript, ([result]) => completer.complete(result)]);
    return completer.future;
  },

  /**
   * Inserts a fragment of CSS into [_webview]'s DOM.
   *
   * @type: void
   */
  _insertCssIntoWebview: function(css) {
    _webview.insertCSS(css);
  }
}
<commit_msg>Done rewriting in JS: haven't checked; still use 2-way async export code<commit_after>// Copyright (c) 2014, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

Polymer('cde-polymer-designer', {
  // NOTE: Make sure this is in-sync with ide/web/manifest.json.
  _STORAGE_PARTITION: 'cde-polymer-designer',
  // NOTE: We must use the full path as viewed by a client app.
  _LOCAL_ENTRY_POINT:
      'packages/cde_polymer_designer/src/polymer_designer/index.html',
  _ONLINE_ENTRY_POINT:
      'https://www.polymer-project.org/tools/designer/',

  /**
   * Specifies whether to use the local, compiled-in copy of Polymer Designer
   * or the online one.
   *
   * @attribute attr
   * @type string
   */
  entryPoint: 'local',

  /**
   * Fires after the <webview> has been created, its UI tweaked and the proxy
   * script injected into it.
   *
   * @type: Promise
   */
  _webviewReady: null,
  _webviewReadyResolve: null,

  /**
   * Fires when the asynchronous code export requested from the proxy has
   * completed.
   *
   * @type: Promise<String>
   */
  _codeExported: null,
  _codeExportedResolve: null,

  /**
   * The dynamically created <webview> element.
   */
  _webview: null,

  ready: function() {
  },

  attached: function() {
    assert(entryPoint in ['local', 'inline']);

    this._registerDesignerProxyListener();
  },

  /**
   * Initializes this object from an idle state:
   * - Creates a new <webview>.
   * - Inserts it into the shadow DOM.
   * - Navigates it to the Polymer Designer local or online entry point.
   * - Registers an event listener to complete initialization once the
   * <webview> is up and running.
   *
   * @type: Promise
   */
  load: function() {
    this._webviewReady = new Promise(function(resolve, reject) {
      this._webviewReadyResolve = resolve;
      // TODO(ussuri): BUG #3466.
      setTimeout(function() {
        this._webview = document.createElement('webview');
        this._webview.addEventListener('contentload', this._onWebviewContentLoad);
        this._webview.partition = this._STORAGE_PARTITION;
        this._webview.src =
            this.entryPoint == 'local' ?
            this._LOCAL_ENTRY_POINT :
            this._ONLINE_ENTRY_POINT;
        this.shadowRoot.append(this._webview);
      }, 500);
    });
    return this._webviewReady;
  },

  /**
   * Reinitializes the object from a running state.
   *
   * @type: Promise
   */
  reload: function() {
    this.unload();
    return this.load();
  },

  /**
   * Returns the object to the idle state:
   * - terminates the running <webview>, if any.
   * - removes the <webview> element from the shadow DOM.
   * - Resets the [Completer]s.
   *
   * @type: void
   */
  unload: function() {
    if (this._webview != null) {
      this._webview.terminate();
      this._webview.remove();
    }
    this._webview = this._webviewReady = this._webviewReadyResolve = null;
    this._codeExported = null;
  },

  /**
   * Sets the internal design code inside the Polymer Designer running within
   * the <webview>. This results in PD re-rendering the design, if it can
   * parse the code. If it can't, it resets the design to an empty one.
   *
   * @type: Promise
   */
  setCode: function(code) {
    // Escape all special charachters and enclose in double-quotes.
    code = JSON.encode(code);
    // Just in case we are called too early, wait until the webview is ready.
    return this._webviewReady.then(function () {
      // TODO(ussuri): This doesn't work without a delay:
      // find some way to detect when PD is fully ready.
      return setTimeout(function() {
        // The request is received and handled by the JS proxy script injected
        // into [_webview>] by [_injectDesignerProxy].
        return this._executeScriptInWebview(function() {
          window.postMessage({action: 'import_code', code: code}, '*');
        });
      }, 500);
    });
  },

  /**
   * Extracts and returns to the caller the current design code from the
   * Polymer Designer.
   *
   * @type: Promise<String>
   */
  getCode: function() {
    if (this._codeExported == null) {
      this._codeExported = new Promise(function(resolve, reject) {
        this._codeExportedResolve = resolve;
        // The request is received and handled by [_designerProxy], which
        // asynchronously returns the result via a chrome.runtime.sendMessage,
        // received and handled by [_designProxyListener], which completes the
        // [_codeExported] completer with the code string.
        this._executeScriptInWebview(function() {
          window.postMessage({action: 'export_code'}, '*');
        });
        // .then(function(result) {
        //   resolve(result);
        // });
      });
    } else {
      // There already is a pending [getCode] request: just fall through
      // to returning the same promise to the new caller.
    }
    return this._codeExported;
  },

  /**
   * Tweaks the Polymer Designer UI and inject a proxy script.
   *
   * @type: void
   */
  _onWebviewContentLoad: function(event) {
    this._tweakDesignerUI();
    this._injectDesignerProxy().then(function() {
      this._webviewReadyResolve();
    });
  },

  /**
   * Tweaks the Polymer Designer UI by inserting some CSS into [_webview]:
   * - Reduces font sizes.
   * - Reduces toolbar sizes.
   * - Removes some buttons we don't need.
   *
   * @type: void
   */
  _tweakDesignerUI: function() {
    // TODO(ussuri): Some of this will become unnecessary once BUG #3467 is
    // resolved.
    this._insertCssIntoWebview('\
        /* Reduce the initial font sizes */\
        html /deep/ *, html /deep/ #tabs > * {\
          font-size: 13px;\
        }\
        html /deep/ #tabs {\
          padding-top: 0;\
          padding-bottom: 0;\
        }\
        html /deep/ core-toolbar,\
        html /deep/ core-toolbar::shadow > #topBar {\
          height: 40px;\
        }\
        /* Hide some UI elements we do not need */\
        #designer::shadow > #appbar > * {\
          display: none;\
        }\
        #designer::shadow > #appbar > .design-controls {\
          display: block;\
        }\
        #designer::shadow > #appbar > .design-controls > .separator:first-child {\
          display: none;\
        }'
    );
  },

  /**
   * Handles messages/responses sent from inside [_webview] by [_designerProxy].
   *
   * @type: void
   */
  _designerProxyListener: function(event) {
    // TODO(ussuri): Check the sender?
    if (event.message['type'] == 'export_code_response') {
      this._codeExportedResolve("${event.message['code']}");
      // Null the completer so new [getCode] requests can work properly.
      this._codeExported = null;
    }
  },

  /**
   * Registers a listener for responses sent by [_designerProxy].
   *
   * @type: void
   */
  _registerDesignerProxyListener: function() {
    chrome.runtime.onMessage.listen(this._designerProxyListener);
  },

  /**
   * The proxy script injected into the scripting context ("main world") of
   * [_webview] in order to receive and handle requests on behalf of this object.
   *
   * @type: string
   */
  _designerProxy: function() {
      // This will receive events sent by the main code via window.postMessage
      // executed inside [_webview]'s DOM.
      window.addEventListener("message", function(event) {
        var designer = document.querySelector('#designer');
        var action = event.data['action'];
        if (action === 'export_code') {
          chrome.runtime.sendMessage(
              {type: 'export_code_response', code: designer.html});
        } else if (action === 'import_code') {
          // After this, PD will detect the new design code and try to parse
          // and render it.
          designer.loadHtml(event.data['code']);
        }
      });
  },

  /**
   * Injects [_designerProxy] into the scripting context ("main world") of
   * [_webview]. This is necessary in contrast to the "isolated world" in
   * order for the proxy to have access to the JS context of the Designer
   * (within "isolated world", the proxy would only see the Designer's DOM).
   *
   * @type: Promise
   */
  _injectDesignerProxy: function() {
    // TODO(ussuri): Check the sender?
    return this._injectScriptIntoWebviewMainWorld(this._designerProxy);
  },

  /**
   * Effectively injects and runs a given script in the [_webview]'s scripting
   * context ("main world"). This is achieved by creating a new <script> tag in
   * the [_webview]'s DOM and setting its inner HTML to the script's code.
   * Doing that will cause the script to run and be able to register event
   * listeners which will have access to various Polymer Designer's JS
   * properties.
   *
   * @type: Promise
   */
  _injectScriptIntoWebviewMainWorld: function(script) {
    // Escape all special charachters and enclose in double-quotes.
    return _executeScriptInWebview(function() {
      var scriptTag = document.createElement('script');
      scriptTag.innerHTML = '(function() { $script; })()';
      document.body.appendChild(scriptTag);
    });
  },

  /**
   * Executes a script in [_webview]'s "isolated world", which give the script
   * access only to the webview's DOM, but not the JS context.
   *
   * @type: Promise<array of any>
   */
  _executeScriptInWebview: function(script) {
    script = '(function() {' + JSON.stringify(script) + '; })()';
    return new Promise(function(resolve, reject) {
      this._webview.executeScript(
          {code: script},
          function(result) { resolve(result); }
      );
    });
  },

  /**
   * Inserts a fragment of CSS into [_webview]'s DOM.
   *
   * @type: void
   */
  _insertCssIntoWebview: function(css) {
    _webview.insertCSS({code: css});
  }
}
<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp');

var browserSync = require('browser-sync');
var httpProxy = require('http-proxy');

/* This configuration allow you to configure browser sync to proxy your backend */
var proxyTarget = 'http://server/context/'; // The location of your backend
var proxyApiPrefix = 'api'; // The element in the URL which differentiate between API request and static file request

var proxy = httpProxy.createProxyServer({
  target: proxyTarget
});

function proxyMiddleware(req, res, next) {
  if (req.url.indexOf(proxyApiPrefix) !== -1) {
    proxy.web(req, res);
  } else {
    next();
  }
}

function browserSyncInit(baseDir, files, browser) {
  browser = browser === undefined ? 'default' : browser;

  browserSync.instance = browserSync.init(files, {
    startPath: '/index.html',
    server: {
      baseDir: baseDir,
      middleware: proxyMiddleware
    },
    browser: browser
  });

}

gulp.task('serve', ['watch'], function () {
  browserSyncInit([
    'app',
    '.tmp'
  ], [
    'app/*.html',
    '.tmp/styles/**/*.css',
    'app/scripts/**/*.js',
    'app/partials/**/*.html',
    'app/images/**/*'
  ]);
});

gulp.task('serve:dist', ['build'], function () {
  browserSyncInit('dist');
});

gulp.task('serve:e2e', function () {
  browserSyncInit(['app', '.tmp'], null, []);
});

gulp.task('serve:e2e-dist', ['watch'], function () {
  browserSyncInit('dist', null, []);
});
<commit_msg>Gulp is now proxying backend<commit_after>'use strict';

var gulp = require('gulp');

var browserSync = require('browser-sync');
var httpProxy = require('http-proxy');

/* This configuration allow you to configure browser sync to proxy your backend */
var proxyTarget = 'http://localhost:4000/api/'; // The location of your backend
var proxyApiPrefix = 'api'; // The element in the URL which differentiate between API request and static file request

var proxy = httpProxy.createProxyServer({
  target: proxyTarget
});

function proxyMiddleware(req, res, next) {
  if (req.url.indexOf(proxyApiPrefix) !== -1) {
    proxy.web(req, res);
  } else {
    next();
  }
}

function browserSyncInit(baseDir, files, browser) {
  browser = browser === undefined ? 'default' : browser;

  browserSync.instance = browserSync.init(files, {
    startPath: '/index.html',
    server: {
      baseDir: baseDir,
      middleware: proxyMiddleware
    },
    browser: browser
  });

}

gulp.task('serve', ['watch'], function () {
  browserSyncInit([
    'app',
    '.tmp'
  ], [
    'app/*.html',
    '.tmp/styles/**/*.css',
    'app/scripts/**/*.js',
    'app/partials/**/*.html',
    'app/images/**/*'
  ]);
});

gulp.task('serve:dist', ['build'], function () {
  browserSyncInit('dist');
});

gulp.task('serve:e2e', function () {
  browserSyncInit(['app', '.tmp'], null, []);
});

gulp.task('serve:e2e-dist', ['watch'], function () {
  browserSyncInit('dist', null, []);
});
<|endoftext|>


<commit_before>const tap = require('tap');
const fastly = require('../lib/fastly.js');
const fastly_api_key =  process.env.FASTLY_API_KEY || ''

tap.test('fastly constructor', function(t){
	var flib = new fastly(fastly_api_key);

	t.type(flib.request, 'object', 'request => object');
	t.type(flib.request.options, 'object', '.options => object');
	t.match(flib.request.options, {
		method: 'GET',
		hostname: 'api.fastly.com',
		path: '/',
		headers: {
			'Fastly-Key': api_key,
			'Accept': 'application/json'
		}
	}, 'options => match the pattern');
	t.end();
})

tap.test('utilities', function(t) {

	var flib = new fastly(fastly_api_key);

	t.type(flib, 'object', 'constructor');
	t.type(flib.content, 'function', 'method content');
	t.type(flib.datacenters, 'function', 'method datacenters');
	t.type(flib.docs, 'function', 'method docs');
	t.type(flib.public_ip_list, 'function', 'method public_ip_list');
	t.end();
});

tap.test('purge', function(t) {

	var flib = new fastly(fastly_api_key);

	t.type(flib, 'object', 'constructor');
	t.type(flib.purge, 'function', 'purge method');
	t.type(flib.purge_all, 'function', 'purge all method');
	t.type(flib.purge_by_key, 'function', 'purge by key method');
	t.type(flib.purge_multiple, 'function', 'purge multiple method');
	t.end();
});
<commit_msg>Update test<commit_after>const tap = require('tap');
const fastly = require('../lib/fastly.js');
const fastly_api_key =  process.env.FASTLY_API_KEY || ''

tap.test('fastly constructor', function(t){
	var flib = new fastly(fastly_api_key);

	t.type(flib.request, 'object', 'request => object');
	t.type(flib.request.options, 'object', '.options => object');
	t.match(flib.request.options, {
		method: 'GET',
		hostname: 'api.fastly.com',
		path: '/',
		headers: {
			'Fastly-Key': fastly_api_key,
			'Accept': 'application/json'
		}
	}, 'options => match the pattern');
	t.end();
})

tap.test('utilities', function(t) {

	var flib = new fastly(fastly_api_key);

	t.type(flib, 'object', 'constructor');
	t.type(flib.content, 'function', 'method content');
	t.type(flib.datacenters, 'function', 'method datacenters');
	t.type(flib.docs, 'function', 'method docs');
	t.type(flib.public_ip_list, 'function', 'method public_ip_list');
	t.end();
});

tap.test('purge', function(t) {

	var flib = new fastly(fastly_api_key);

	t.type(flib, 'object', 'constructor');
	t.type(flib.purge, 'function', 'purge method');
	t.type(flib.purge_all, 'function', 'purge all method');
	t.type(flib.purge_by_key, 'function', 'purge by key method');
	t.type(flib.purge_multiple, 'function', 'purge multiple method');
	t.end();
});
<|endoftext|>


<commit_before>import glob from 'glob'
import { exclude as exclusionList } from '../config.json'

/**
 * @private USE ONLY!
 *
 * Asynchronous glob-based directory listing
 *
 * NOTE: For better performance we implicitly exclude the **content** of these
 * folders:
 *
 *  - `node_modules`
 *  - `.git`
 *  - `.svn`
 *
 * However, you would still gets their names listed, e.g: node_modules,
 * which enables us to perform matching checks as neccessary.
 *
 * Also, it doesn't follow symbolic links!
 *
 * @example
 * ls('path/to/my/node/module')
 *  .then(files => { console.log(files) })
 * // outputs(e.g): ['package.json','src/index.js', ...]
 *
 * ls('path/to/my/node/module', ['package.json'])
 *  .then(files => { console.log(files) })
 * // outputs(e.g): ['src/index.js', ...]
 *
 * @param  {String}  dir            target directory
 * @param  {Array}   [exclude=[]]   of glob patterns to exclude
 * @return {Promise}                with folders/files list in `dir`
 */
const ls = async (dir, exclude = []) => {
  return new Promise((resolve, reject) => {
    glob(
      '**', // matches anything
      {
        ignore: [...exclude, ...exclusionList],
        cwd: dir,
        follow: false // don't follow symbolic links
      }, (err, files) => {
        if (err) {
          reject(err)
        } else {
          resolve(files)
        }
      }
    )
  })
}

export default ls
<commit_msg>docs(util/ls): Point to config.json instead of listing all patterns<commit_after>import glob from 'glob'
import { exclude as exclusionList } from '../config.json'

/**
 * @private USE ONLY!
 *
 * Asynchronous glob-based directory listing
 *
 * NOTE: For better performance we implicitly exclude all patterns specified
 * inside `exclude` list in lib/config.json`
 *
 * However, you would still gets their names listed, e.g: node_modules,
 * which enables us to perform matching checks as neccessary.
 *
 * Also, it doesn't follow symbolic links!
 *
 * @example
 * ls('path/to/my/node/module')
 *  .then(files => { console.log(files) })
 * // outputs(e.g): ['package.json','src/index.js', ...]
 *
 * ls('path/to/my/node/module', ['package.json'])
 *  .then(files => { console.log(files) })
 * // outputs(e.g): ['src/index.js', ...]
 *
 * @param  {String}  dir            target directory
 * @param  {Array}   [exclude=[]]   of glob patterns to exclude
 * @return {Promise}                with folders/files list in `dir`
 */
const ls = async (dir, exclude = []) => {
  return new Promise((resolve, reject) => {
    glob(
      '**', // matches anything
      {
        ignore: [...exclude, ...exclusionList],
        cwd: dir,
        follow: false // don't follow symbolic links
      }, (err, files) => {
        if (err) {
          reject(err)
        } else {
          resolve(files)
        }
      }
    )
  })
}

export default ls
<|endoftext|>


<commit_before>/*global angular, requestAnimationFrame*/

angular.module('andrejson').controller('munchersController', function ($scope, $log, $timeout, $window) {
	'use strict';
	var sim = {};
	/**
	 * Loops over all functions needed to progress the simulation.
	 */
	sim.loop = function () {
		if (Date.now() - sim.global.stamp > 1000) {
			sim.global.stamp = Date.now();
			$log.info('fps: ' + sim.global.frames + ', tps: ' + sim.global.ticks);
			sim.global.frames = 0;
			sim.global.ticks = 0;
		}
		if (sim.global.frameFinished && Date.now() - sim.global.frameStamp > 1000 / $scope.simConfig.fps) {
			sim.global.frameFinished = false;
			sim.global.frameStamp = Date.now();
			requestAnimationFrame(function () {
				sim.draw();
				sim.global.frames += 1;
				sim.global.frameFinished = true;
			});
		}
		if (Date.now() - sim.global.tickStamp > 1000 / $scope.simConfig.tps) {
			sim.global.tickStamp += 1000 / $scope.simConfig.tps;
			sim.tick();
			sim.global.ticks += 1;
		}
		$timeout(sim.loop, 4);
	};

	/**
	 * Redraws the screen and all its entities.
	 */
	sim.draw = function () {
		sim.drawBackground();
		sim.drawAnimal(sim.global.testAnimal.node, sim.global.testAnimal.xPos, sim.global.testAnimal.yPos);
		if ($scope.simConfig.showParticles) {
			sim.drawPhysicsParticles(sim.global.testAnimal);
		}
	};

	/**
	 * Subroutine to draw. Draws the background.
	 */
	sim.drawBackground = function () {
		$scope.ctx.beginPath();
		$scope.ctx.rect(0, 0, $scope.windowWidth, $scope.windowHeight);
		$scope.ctx.fillStyle = $scope.simConfig.backgroundColor;
		$scope.ctx.fill();
	};

	sim.drawAnimal = function (node, xPos, yPos) {
		var i, conn, xChild, yChild;
		for (i = 0; i < node.children; i += 1) {
			conn = node.connections[i]; //An alias for the current connection.
			xChild = xPos + Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			yChild = yPos + Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			$scope.ctx.beginPath();
			$scope.ctx.moveTo(xPos, yPos);
			$scope.ctx.lineTo(xChild, yChild);
			$scope.ctx.strokeStyle = $scope.simConfig.connectionColor;
			$scope.ctx.stroke();
			sim.drawAnimal(conn.node, xChild, yChild);
		}
		$scope.ctx.beginPath();
		$scope.ctx.arc(xPos, yPos, $scope.simConfig.nodeSize, 0, 2 * Math.PI);
		$scope.ctx.fillStyle = $scope.simConfig.nodeColor;
		$scope.ctx.fill();
	};

	sim.drawPhysicsParticles = function (animal) {
		var i;
		$scope.ctx.fillStyle = $scope.simConfig.forceColor;
		for (i = 0; i < animal.forces.xPos.length; i += 1) {
			$scope.ctx.beginPath();
			$scope.ctx.arc(animal.forces.xPos[i], animal.forces.yPos[i], $scope.simConfig.nodeSize / 2, 0, 2 * Math.PI);
			$scope.ctx.fill();
			$scope.ctx.beginPath();
			$scope.ctx.moveTo(animal.forces.xPos[i], animal.forces.yPos[i]);
			$scope.ctx.lineTo(animal.forces.xPos[i] + animal.forces.xF[i], animal.forces.xPos[i] + animal.forces.yF[i]);
			$scope.ctx.stroke();
		}
		$scope.ctx.beginPath();
		$scope.ctx.arc(animal.xMass, animal.yMass, $scope.simConfig.nodeSize / 2, 0, 2 * Math.PI);
		$scope.ctx.fillStyle = $scope.simConfig.massColor;
		$scope.ctx.fill();
	};

	sim.tick = function () {
		sim.moveAnimal(sim.global.testAnimal);
	};

	sim.moveAnimal = function (animal) {
		sim.flapChildren(animal.node, 0);
		sim.calculateMassCenter(animal);
		animal.resetForces();
		sim.calculateFlapForce(animal.forces, animal.node, animal.xPos, animal.yPos);
		//TODO: Calculate forces from drag.
		//TODO: Calculate new velocity from forces.
		animal.xPos += animal.xVel;
		animal.yPos += animal.yVel;
	};

	sim.calculateFlapForce = function (accum, node, x, y) {
		var i, conn, deltaX, deltaY;
		for (i = 0; i < node.children; i += 1) {
			conn = node.connections[i];
			deltaX = Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			deltaY = Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			accum.xPos.push(x + deltaX * Math.pow(1 / 2, 1 / 3));
			accum.yPos.push(y + deltaY * Math.pow(1 / 2, 1 / 3));
			accum.xF.push(conn.expSign * Math.pow(conn.length, 3) * Math.pow(conn.expanding ? conn.expVel : conn.conVel, 2) * Math.cos(Math.PI / 2 - node.angle - conn.angle));
			accum.yF.push(-conn.expSign * Math.pow(conn.length, 3) * Math.pow(conn.expanding ? conn.expVel : conn.conVel, 2) * Math.sin(Math.PI / 2 - node.angle - conn.angle));
			sim.calculateFlapForce(accum, conn.node, x + deltaX, y + deltaY);
		}
	};

	sim.calculateMassCenter = function (animal) {
		var accum = {x: [], y: [] }; //An accumulator for node positions.
		accum.x.push(animal.xPos);
		accum.y.push(animal.yPos);
		(function crawl(accum, node, x, y) { //Anonymous function that crawls down all descendant nodes.
			var i, conn, nodeX, nodeY;
			for (i = 0; i < node.children; i += 1) {
				conn = node.connections[i];
				nodeX = x + Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				nodeY = y + Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				accum.x.push(nodeX);
				accum.y.push(nodeY);
				crawl(accum, conn.node, nodeX, nodeY);
			}
		}(accum, animal.node, animal.xPos, animal.yPos));
		animal.xMass = accum.x.reduce(function (a, b) {return a + b; }, 0) / accum.x.length;
		animal.yMass = accum.y.reduce(function (a, b) {return a + b; }, 0) / accum.y.length;
	};

	sim.flapChildren = function (node, angleChange) {
		var i, conn;
		for (i = 0; i < node.children; i += 1) {
			conn = node.connections[i]; //Creating an alias for the targeted connection
			if (conn.frameCount === (conn.expanding ? conn.expFrames : conn.conFrames)) {
				conn.expanding = !conn.expanding;
				conn.frameCount = 0;
			}
			conn.frameCount += 1;
			conn.angle += conn.expanding ? conn.expVel : conn.conVel;
			conn.node.angle += angleChange + (conn.expanding ? conn.expVel : conn.conVel);
			sim.flapChildren(conn.node, angleChange + (conn.expanding ? conn.expVel : conn.conVel));
		}
	};

	sim.animal = function () {
		this.node = new sim.node();
		this.xVel = 0;
		this.yVel = 0;
		this.xPos = $scope.windowWidth / 2;	//The x-wise position of the root node.
		this.yPos = $scope.windowHeight / 2;//The y-wise position of the root node.
		this.xMass = 0;	//The x-wise center of mass.
		this.yMass = 0;	//The y-wise center of mass.
		this.forces = {xPos: [], yPos: [], xF: [], yF: []};
	};

	sim.animal.prototype.resetForces = function () {
		this.forces = {xPos: [], yPos: [], xF: [], yF: []};
	};

	sim.node = function () {
		this.angle = 0;
		this.children = 0;
		this.connections = [];
		this.addChild = function (connection) {
			this.children += 1;
			this.connections.push(connection);
		};
	};

	sim.connection = function (conAngle, expAngle, expFrames, conFrames, length) {
		this.angle = conAngle;							//The current angle.
		this.expVel = (expAngle - conAngle) / expFrames;//The expansion angle velocity.
		this.conVel = (conAngle - expAngle) / conFrames;//The contraction angle velocity.
		this.expanding = true;							//Set to true when the wing is expanding.
		this.expSign = (expAngle > conAngle ? 1 : -1);	//The sign of the angle velocity when expanding.
		this.expFrames = expFrames;						//How many frames the wing expands.
		this.conFrames = conFrames;						//How many frames the wing contracts.
		this.frameCount = 0;							//Counter to keep track of when to change direction.
		this.length = length;							//The length of the connection to the child node.
		this.node = new sim.node();						//The child node.
	};

	//Keeps track of all state info.
	sim.global = {
		stamp: undefined,
		frames: 0,
		frameStamp: undefined,
		frameFinished: true,
		ticks: 0,
		tickStamp: undefined,
		testAnimal: undefined
	};

	//Keeps track of all configurations for the simulation
	$scope.simConfig = {
		fps: 60,
		tps: 10,
		backgroundColor: "rgba(210,210,210,1)",
		nodeColor: "rgba(20,110,150,0.9)",
		connectionColor: "rgba(50,50,50,0.8)",
		massColor: "rgba(255,0,0,1)",
		forceColor: "rgba(255,0,255,1)",
		maxFlapSpeed: 1, //Chosen arbitrarily, may likely need to change later.
		nodeSize: 7,
		lengthScale: 0.35,
		showParticles: true
	};

	/**
	 * Should be called to initialize the simulation.
	 */
	sim.init = function () {
		sim.global.testAnimal = new sim.animal();
		sim.global.testAnimal.node.addChild(new sim.connection(0, 1, 6, 10, 100));
		sim.global.testAnimal.node.addChild(new sim.connection(Math.PI, Math.PI - 1, 6, 10, 100));
		sim.global.testAnimal.node.connections[0].node.addChild(new sim.connection(0, 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[1].node.addChild(new sim.connection(Math.PI, Math.PI - 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[0].node.connections[0].node.addChild(new sim.connection(0, 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[1].node.connections[0].node.addChild(new sim.connection(Math.PI, Math.PI - 0.3, 6, 10, 100));
		sim.global.stamp = Date.now();
		sim.global.frameStamp = Date.now();
		sim.global.tickStamp = Date.now();
		sim.loop();
	};

	/**
	 * Automatically called when page has loaded.
	 */
	$timeout(function () {
		$scope.windowWidth = $window.innerWidth * 0.8;
		$scope.windowHeight = $window.innerHeight * 0.8;
		sim.init();
	});
});<commit_msg>Adds movement from flap forces. Rotation is not yet implemented though.<commit_after>/*global angular, requestAnimationFrame*/

angular.module('andrejson').controller('munchersController', function ($scope, $log, $timeout, $window) {
	'use strict';
	var sim = {};
	/**
	 * Loops over all functions needed to progress the simulation.
	 */
	sim.loop = function () {
		if (Date.now() - sim.global.stamp > 1000) {
			sim.global.stamp = Date.now();
			$log.info('fps: ' + sim.global.frames + ', tps: ' + sim.global.ticks);
			sim.global.frames = 0;
			sim.global.ticks = 0;
		}
		if (sim.global.frameFinished && Date.now() - sim.global.frameStamp > 1000 / $scope.simConfig.fps) {
			sim.global.frameFinished = false;
			sim.global.frameStamp = Date.now();
			requestAnimationFrame(function () {
				sim.draw();
				sim.global.frames += 1;
				sim.global.frameFinished = true;
			});
		}
		if (Date.now() - sim.global.tickStamp > 1000 / $scope.simConfig.tps) {
			sim.global.tickStamp += 1000 / $scope.simConfig.tps;
			sim.tick();
			sim.global.ticks += 1;
		}
		$timeout(sim.loop, 4);
	};

	/**
	 * Redraws the screen and all its entities.
	 */
	sim.draw = function () {
		sim.drawBackground();
		sim.drawAnimal(sim.global.testAnimal.node, sim.global.testAnimal.xPos, sim.global.testAnimal.yPos);
		if ($scope.simConfig.showParticles) {
			sim.drawPhysicsParticles(sim.global.testAnimal);
		}
	};

	/**
	 * Subroutine to draw. Draws the background.
	 */
	sim.drawBackground = function () {
		$scope.ctx.beginPath();
		$scope.ctx.rect(0, 0, $scope.windowWidth, $scope.windowHeight);
		$scope.ctx.fillStyle = $scope.simConfig.backgroundColor;
		$scope.ctx.fill();
	};

	sim.drawAnimal = function (node, xPos, yPos) {
		var i, conn, xChild, yChild;
		for (i = 0; i < node.children; i += 1) {
			conn = node.connections[i]; //An alias for the current connection.
			xChild = xPos + Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			yChild = yPos + Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
			$scope.ctx.beginPath();
			$scope.ctx.moveTo(xPos, yPos);
			$scope.ctx.lineTo(xChild, yChild);
			$scope.ctx.strokeStyle = $scope.simConfig.connectionColor;
			$scope.ctx.stroke();
			sim.drawAnimal(conn.node, xChild, yChild);
		}
		$scope.ctx.beginPath();
		$scope.ctx.arc(xPos, yPos, $scope.simConfig.nodeSize, 0, 2 * Math.PI);
		$scope.ctx.fillStyle = $scope.simConfig.nodeColor;
		$scope.ctx.fill();
	};

	sim.drawPhysicsParticles = function (animal) {
		var i;
		$scope.ctx.fillStyle = $scope.simConfig.forceColor;
		$scope.ctx.strokeStyle = $scope.simConfig.forceColor;
		for (i = 0; i < animal.forces.xPos.length; i += 1) {
			$scope.ctx.beginPath();
			$scope.ctx.arc(animal.forces.xPos[i], animal.forces.yPos[i], $scope.simConfig.nodeSize / 2, 0, 2 * Math.PI);
			$scope.ctx.fill();
			$scope.ctx.beginPath();
			$scope.ctx.moveTo(animal.forces.xPos[i], animal.forces.yPos[i]);
			$scope.ctx.lineTo(animal.forces.xPos[i] + animal.forces.xF[i] / 50, animal.forces.yPos[i] + animal.forces.yF[i] / 50);
			$scope.ctx.stroke();
		}
		$scope.ctx.beginPath();
		$scope.ctx.arc(animal.xMass, animal.yMass, $scope.simConfig.nodeSize / 2, 0, 2 * Math.PI);
		$scope.ctx.fillStyle = $scope.simConfig.massColor;
		$scope.ctx.fill();
		$scope.ctx.beginPath();
		$scope.ctx.moveTo(animal.xMass, animal.yMass);
		$scope.ctx.lineTo(animal.xMass + animal.xAccForce / 50, animal.yMass + animal.yAccForce / 50);
		$scope.ctx.strokeStyle = $scope.simConfig.forceColor;
		$scope.ctx.stroke();
	};

	sim.tick = function () {
		sim.moveAnimal(sim.global.testAnimal);
	};

	sim.moveAnimal = function (animal) {
		sim.flapChildren(animal.node, 0);
		sim.calculateMassCenter(animal);
		animal.resetForces();
		sim.calculateFlapForce(animal);
		//TODO: Make it so calculateFlapForce takes all children into consideration when a flap happens.
		//TODO: Calculate forces from drag.
		sim.calculateAcceleration(animal);
		animal.xPos += animal.xVel;
		animal.yPos += animal.yVel;
	};

	sim.flapChildren = function (node, angleChange) {
		var i, conn;
		for (i = 0; i < node.children; i += 1) {
			conn = node.connections[i]; //Creating an alias for the targeted connection
			if (conn.frameCount === (conn.expanding ? conn.expFrames : conn.conFrames)) {
				conn.expanding = !conn.expanding;
				conn.frameCount = 0;
			}
			conn.frameCount += 1;
			conn.angle += conn.expanding ? conn.expVel : conn.conVel;
			conn.node.angle += angleChange + (conn.expanding ? conn.expVel : conn.conVel);
			sim.flapChildren(conn.node, angleChange + (conn.expanding ? conn.expVel : conn.conVel));
		}
	};

	sim.calculateMassCenter = function (animal) {
		var accum = {x: [], y: [] }; //An accumulator for node positions.
		accum.x.push(animal.xPos);
		accum.y.push(animal.yPos);
		(function crawl(accum, node, x, y) { //Anonymous function that crawls down all descendant nodes.
			var i, conn, nodeX, nodeY;
			for (i = 0; i < node.children; i += 1) {
				conn = node.connections[i];
				nodeX = x + Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				nodeY = y + Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				accum.x.push(nodeX);
				accum.y.push(nodeY);
				crawl(accum, conn.node, nodeX, nodeY);
			}
		}(accum, animal.node, animal.xPos, animal.yPos));
		animal.xMass = accum.x.reduce(function (a, b) {return a + b; }, 0) / accum.x.length;
		animal.yMass = accum.y.reduce(function (a, b) {return a + b; }, 0) / accum.y.length;
	};

	sim.calculateFlapForce = function (animal) {
		(function crawl(accum, node, x, y) {
			var i, conn, deltaX, deltaY;
			for (i = 0; i < node.children; i += 1) {
				conn = node.connections[i];
				deltaX = Math.cos(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				deltaY = Math.sin(node.angle + conn.angle) * conn.length * $scope.simConfig.lengthScale;
				accum.xPos.push(x + deltaX * Math.pow(1 / 2, 1 / 3));
				accum.yPos.push(y + deltaY * Math.pow(1 / 2, 1 / 3));
				accum.xF.push((conn.expanding ? 1 : -1) * conn.expSign * Math.pow(conn.length, 3) * Math.pow(conn.expanding ? conn.expVel : conn.conVel, 2) * Math.cos(Math.PI / 2 - node.angle - conn.angle));
				accum.yF.push((conn.expanding ? -1 : 1) * conn.expSign * Math.pow(conn.length, 3) * Math.pow(conn.expanding ? conn.expVel : conn.conVel, 2) * Math.sin(Math.PI / 2 - node.angle - conn.angle));
				crawl(accum, conn.node, x + deltaX, y + deltaY);
			}
		}(animal.forces, animal.node, animal.xPos, animal.yPos));
	};

	sim.calculateAcceleration = function (animal) {
		var i, angle, m1, m2, f1, f2;
		for (i = 0; i < animal.forces.xPos.length; i += 1) {
			m1 = animal.xMass - animal.forces.xPos[i];
			m2 = animal.yMass - animal.forces.yPos[i];
			f1 = animal.forces.xF[i] - animal.forces.xPos[i];
			f2 = animal.forces.yF[i] - animal.forces.yPos[i];
			angle = Math.acos((m1 * f1 + m2 * f2) / Math.sqrt((m1 * m1 + m2 * m2) * (f1 * f1 + f2 * f2)));
			animal.xAccForce += Math.cos(angle - Math.PI / 2) * animal.forces.xF[i];
			animal.yAccForce += Math.cos(angle - Math.PI / 2) * animal.forces.yF[i];

		}
		animal.xVel += $scope.simConfig.accConstant * animal.xAccForce;
		animal.yVel += $scope.simConfig.accConstant * animal.yAccForce;
		//TODO: Calculate new velocity from forces.
		//TODO: Calculate rotation from forces.
	};

	sim.animal = function () {
		this.node = new sim.node();
		this.xVel = 0;
		this.yVel = 0;
		this.xPos = $scope.windowWidth / 2;	//The x-wise position of the root node.
		this.yPos = $scope.windowHeight / 2;//The y-wise position of the root node.
		this.xMass = 0;	//The x-wise center of mass.
		this.yMass = 0;	//The y-wise center of mass.
		this.forces = {xPos: [], yPos: [], xF: [], yF: []};
		this.xAccForce = 0;
		this.yAccForce = 0;
		this.xRotForce = 0;
		this.yRotForce = 0;
	};

	sim.animal.prototype.resetForces = function () {
		this.forces = {xPos: [], yPos: [], xF: [], yF: []};
		this.xAccForce = 0;
		this.yAccForce = 0;
		this.xRotForce = 0;
		this.yRotForce = 0;
	};

	sim.node = function () {
		this.angle = 0;
		this.children = 0;
		this.connections = [];
		this.addChild = function (connection) {
			this.children += 1;
			this.connections.push(connection);
		};
	};

	sim.connection = function (conAngle, expAngle, expFrames, conFrames, length) {
		this.angle = conAngle;							//The current angle.
		this.expVel = (expAngle - conAngle) / expFrames;//The expansion angle velocity.
		this.conVel = (conAngle - expAngle) / conFrames;//The contraction angle velocity.
		this.expanding = true;							//Set to true when the wing is expanding.
		this.expSign = (expAngle > conAngle ? 1 : -1);	//The sign of the angle velocity when expanding.
		this.expFrames = expFrames;						//How many frames the wing expands.
		this.conFrames = conFrames;						//How many frames the wing contracts.
		this.frameCount = 0;							//Counter to keep track of when to change direction.
		this.length = length;							//The length of the connection to the child node.
		this.node = new sim.node();						//The child node.
	};

	//Keeps track of all state info.
	sim.global = {
		stamp: undefined,
		frames: 0,
		frameStamp: undefined,
		frameFinished: true,
		ticks: 0,
		tickStamp: undefined,
		testAnimal: undefined
	};

	//Keeps track of all configurations for the simulation
	$scope.simConfig = {
		fps: 60,
		tps: 10,
		backgroundColor: "rgba(210,210,210,1)",
		nodeColor: "rgba(20,110,150,0.9)",
		connectionColor: "rgba(50,50,50,0.8)",
		massColor: "rgba(255,0,0,1)",
		forceColor: "rgba(255,0,255,1)",
		nodeSize: 7,
		lengthScale: 0.35,
		accConstant: 0.000005,
		showParticles: true
	};

	/**
	 * Should be called to initialize the simulation.
	 */
	sim.init = function () {
		sim.global.testAnimal = new sim.animal();
		sim.global.testAnimal.node.addChild(new sim.connection(0, Math.PI / 2, 6, 10, 100));
		sim.global.testAnimal.node.addChild(new sim.connection(Math.PI, Math.PI / 2, 6, 10, 100));
		/*sim.global.testAnimal.node.connections[0].node.addChild(new sim.connection(0, 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[1].node.addChild(new sim.connection(Math.PI, Math.PI - 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[0].node.connections[0].node.addChild(new sim.connection(0, 0.3, 6, 10, 100));
		sim.global.testAnimal.node.connections[1].node.connections[0].node.addChild(new sim.connection(Math.PI, Math.PI - 0.3, 6, 10, 100));*/
		sim.global.stamp = Date.now();
		sim.global.frameStamp = Date.now();
		sim.global.tickStamp = Date.now();
		sim.loop();
	};

	/**
	 * Automatically called when page has loaded.
	 */
	$timeout(function () {
		$scope.windowWidth = $window.innerWidth * 0.8;
		$scope.windowHeight = $window.innerHeight * 0.8;
		sim.init();
	});
});<|endoftext|>


<commit_before>// @flow
'use strict';

import Grammar from 'swarm-ron-grammar';
import UUID, {ZERO as ZERO_UUID, ERROR, COMMENT} from 'swarm-ron-uuid';

export {default as UUID} from 'swarm-ron-uuid';
export {ERROR as UUID_ERROR} from 'swarm-ron-uuid';
export {default as Batch} from './batch';
import Batch from './batch';

export type Atom = string | number | boolean | null | UUID;

const TRUE_UUID = UUID.fromString('true');
const FALSE_UUID = UUID.fromString('false');
const NULL_UUID = UUID.fromString('0');

// A RON op object. Typically, an Op is hosted in a frame.
// Frames are strings, so Op is sort of a Frame iterator.
export default class Op {
  type: UUID;
  object: UUID;
  event: UUID;
  location: UUID;

  values: string;
  parsed_values: Array<Atom> | void;

  term: string;
  source: ?string;

  constructor(type: UUID, object: UUID, event: UUID, location: UUID, values: ?string, term: ?string): Op {
    this.type = type;
    this.object = object;
    this.event = event;
    this.location = location;
    this.values = values || '';
    this.parsed_values = undefined;

    this.term = term || ';';
    this.source = null; // FIXME remove
    return this;
  }

  value(i: number): Atom {
    if (!this.parsed_values) this.parsed_values = ron2js(this.values);
    return this.parsed_values[i];
  }

  isHeader(): boolean {
    return this.term === '!';
  }

  isQuery(): boolean {
    return this.term === '?';
  }

  isRegular(): boolean {
    return !this.isHeader() && !this.isQuery();
  }

  isError(): boolean {
    return this.event.value === ERROR.value;
  }

  isComment(): boolean {
    return this.type.eq(COMMENT);
  }

  // Get op UUID by index (0-3)
  uuid(i: 0 | 1 | 2 | 3): UUID {
    switch (i) {
      case 0:
        return this.type;
      case 1:
        return this.object;
      case 2:
        return this.event;
      case 3:
        return this.location;
      default:
        throw new Error('incorrect uuid index');
    }
  }

  key(): string {
    return '*' + this.type.value + '#' + this.object.value;
  }

  toString(ctxOp: ?Op): string {
    let ret = '';
    const ctx = ctxOp || ZERO;
    let expComma = ctx.term !== ';';

    for (const u of [0, 1, 2, 3]) {
      const uuid = this.uuid(u);
      const same = ctx.uuid(u);
      if (uuid.eq(same)) continue;
      let str = uuid.toString(same);
      ret += UUID_SEPS[u];
      ret += str;
    }

    ret += this.values;
    if (!this.values || (expComma && this.term !== ',') || (!expComma && this.term !== ';')) {
      ret += this.term;
    }
    return ret;
  }

  clone(): Op {
    return new Op(this.type, this.object, this.event, this.location, this.values, this.term);
  }

  equal(other: Op): boolean {
    return (
      this.uuid(0).eq(other.uuid(0)) &&
      this.uuid(1).eq(other.uuid(1)) &&
      this.uuid(2).eq(other.uuid(2)) &&
      this.uuid(3).eq(other.uuid(3)) &&
      this.values === other.values &&
      this.term === other.term
    );
  }

  static fromString(body: string, context: ?Op, offset: ?number): ?Op {
    let ctx = context || ZERO;
    const off = offset || 0;
    RE.lastIndex = off;
    const m: string[] | void = RE.exec(body);
    if (!m || m[0] === '' || m.index !== off) return null;
    if (m[1] === COMMENT.value) ctx = ZERO;
    let term = m[6];
    if (!term) {
      if (ctx.term === '!') {
        term = ',';
      } else {
        term = ctx.term;
      }
    }

    const ret = new Op(
      UUID.fromString(m[1], ctx.type),
      UUID.fromString(m[2], ctx.object),
      UUID.fromString(m[3], ctx.event),
      UUID.fromString(m[4], ctx.location),
      m[5],
      term,
    );
    ret.source = m[0];
    return ret;
  }
}

// Flip quotes.
export function flipQuotes(v: string): string {
  if (!v) return v;
  if (typeof v !== 'string') {
    throw new Error('unexpected type: ' + typeof v);
  }

  if (v[0] === '"') {
    return "'" + v.slice(1, -1) + "'";
  } else if (v[0] === "'") {
    return '"' + v.slice(1, -1) + '"';
  } else {
    throw new Error('malformed input');
  }
}

// Parse RON value atoms.
export function ron2js(values: string): Array<Atom> {
  VALUE_RE.lastIndex = 0;
  let m: string[] | void;
  const ret = [];

  while ((m = (VALUE_RE.exec(values): string[]))) {
    if (m[1]) {
      ret.push(parseInt(m[1]));
    } else if (m[2]) {
      ret.push(JSON.parse('"' + m[2] + '"')); // VALUE_RE returns match w/o single quotes
    } else if (m[3]) {
      ret.push(parseFloat(m[3]));
    } else if (m[4]) {
      switch (m[4]) {
        case TRUE_UUID.value:
          ret.push(true);
          break;
        case FALSE_UUID.value:
          ret.push(false);
          break;
        case NULL_UUID.value:
          ret.push(null);
          break;
        default:
          ret.push(UUID.fromString(m[4]));
      }
    }
  }
  return ret;
}

// Serialize JS primitives into RON atoms.
export function js2ron(values: Array<Atom>): string {
  const ret = values.map(v => {
    if (v === undefined) return UUID_ATOM_SEP + ZERO_UUID.toString();
    if (v === null) return UUID_ATOM_SEP + NULL_UUID.toString();

    switch (v.constructor) {
      case String:
        return flipQuotes(JSON.stringify(v));
      case Number:
        return Number.isInteger(v) ? INT_ATOM_SEP + v.toString() : FLOAT_ATOM_SEP + v.toString();
      case UUID:
        return UUID_ATOM_SEP + v.toString();
      case Boolean:
        return UUID_ATOM_SEP + (v ? TRUE_UUID : FALSE_UUID).toString();
      default:
        if (v === Op.FRAME_ATOM) return FRAME_SEP;
        if (v === Op.QUERY_ATOM) return QUERY_SEP;
        throw new Error('unsupported type');
    }
  });
  return ret.join('');
}

export const RE = new RegExp(Grammar.OP.source, 'g');
export const VALUE_RE = new RegExp(Grammar.ATOM.source, 'g');
export const ZERO = new Op(ZERO_UUID, ZERO_UUID, ZERO_UUID, ZERO_UUID, '>0');

export const END = new Op(ERROR, ERROR, ERROR, ERROR, '>~');
export const PARSE_ERROR = new Op(ERROR, ERROR, ERROR, ERROR, '>parseerror');
export const REDEF_SEPS = '`';
export const UUID_SEPS = '*#@:';
export const FRAME_ATOM = Symbol('FRAME');
export const QUERY_ATOM = Symbol('QUERY');
export const INT_ATOM_SEP = '=';
export const FLOAT_ATOM_SEP = '^';
export const UUID_ATOM_SEP = '>';
export const FRAME_SEP = '!';
export const QUERY_SEP = '?';

export class Frame {
  body: string;
  last: Op;

  constructor(str: ?string): Frame {
    this.body = str ? str.toString() : '';
    this.last = ZERO;
    return this;
  }

  // Append a new op to the frame
  push(op: Op): void {
    if (this.last.isComment()) {
      this.last = ZERO;
    }

    this.body += op.toString(this.last);
    this.last = op;
  }

  pushWithTerm(op: Op, term: ',' | '!' | '?' | ';'): void {
    if (this.last.isComment()) {
      this.last = ZERO;
    }

    const clone = op.clone();
    clone.term = term;

    this.body += clone.toString(this.last);
    this.last = clone;
  }

  /*:: @@iterator(): Iterator<Op> { return ({}: any); } */

  // $FlowFixMe - computed property
  [Symbol.iterator](): Iterator<Op> {
    return new Cursor(this.body);
  }

  toString(): string {
    return this.body;
  }

  mapUUIDs(fn: (UUID, number, number, Op) => UUID) {
    this.body = mapUUIDs(this.body, fn);
    for (const op of this) {
      this.last = op;
    }
  }

  equal(other: Frame): boolean {
    if (this.toString() === other.toString()) return true;
    const cursor = new Cursor(other.toString());
    for (const op of this) {
      const oop = cursor.op;
      if (!oop || !op.equal(oop)) return false;
      cursor.next();
    }
    return cursor.next().done;
  }

  isFullState(): boolean {
    for (const op of this) return op.isHeader() && op.uuid(3).isZero();
    return false;
  }

  isPayload(): boolean {
    for (const op of this) if (op.isRegular()) return true;
    return false;
  }

  filter(p: Op => boolean): Frame {
    const ret = new Frame();
    for (const op of this) if (p(op)) ret.push(op);
    return ret;
  }

  ID(): UUID {
    for (const op of this) return op.uuid(1);
    return ZERO_UUID;
  }
}

// Substitute UUIDs in all of the frame's ops.
// Typically used for macro expansion.
export function mapUUIDs(rawFrame: string, fn: (UUID, number, number, Op) => UUID): string {
  const ret = new Frame();
  let index = -1;
  for (const op of new Frame(rawFrame)) {
    index++;
    ret.push(
      new Op(
        fn(op.type, 0, index, op) || op.type,
        fn(op.object, 1, index, op) || op.object,
        fn(op.event, 2, index, op) || op.event,
        fn(op.location, 3, index, op) || op.location,
        op.values,
        op.term,
      ),
    );
  }
  return ret.toString();
}

// Crop a frame, i.e. make a new [from,till) frame
export function slice(from: Cursor, till: Cursor): string {
  if (!from.op) return '';
  if (from.body !== till.body) throw new Error('iterators of different frames');
  let ret = from.op.toString();
  ret += from.body.substring(from.offset + from.length, till.op ? till.offset : undefined);
  return ret;
}

export class Cursor implements Iterator<Op> {
  body: string;
  offset: number;
  length: number;
  op: ?Op;
  ctx: ?Op;

  constructor(body: ?string): Cursor {
    this.body = body ? body.toString() : '';
    this.offset = 0;
    this.length = 0;
    this.op = this.nextOp();
    return this;
  }

  toString(): string {
    return this.body;
  }

  clone(): Cursor {
    const ret = new Cursor(this.body);
    ret.offset = this.offset;
    ret.length = this.length;
    ret.op = this.op;
    ret.ctx = this.ctx;
    return ret;
  }

  nextOp(): ?Op {
    this.offset += this.length;
    if (this.offset === this.body.length) {
      this.op = null;
      this.length = 1;
    } else {
      const op = Op.fromString(this.body, this.ctx, this.offset);
      this.ctx = op;
      if (op) {
        if (op.isComment()) this.ctx = ZERO;
        if (op.source) this.length = op.source.length;
      }
      this.op = op;
    }
    return this.op;
  }

  /*:: @@iterator(): Iterator<Op> { return ({}: any); } */

  // $FlowFixMe - computed property
  [Symbol.iterator](): Iterator<Op> {
    return this;
  }

  next(): IteratorResult<Op, void> {
    const ret = this.op;
    if (ret) {
      this.nextOp();
      return {done: false, value: ret};
    } else {
      return {done: true};
    }
  }
}
<commit_msg>proper single-quote escaping<commit_after>// @flow
'use strict';

import Grammar from 'swarm-ron-grammar';
import UUID, {ZERO as ZERO_UUID, ERROR, COMMENT} from 'swarm-ron-uuid';

export {default as UUID} from 'swarm-ron-uuid';
export {ERROR as UUID_ERROR} from 'swarm-ron-uuid';
export {default as Batch} from './batch';
import Batch from './batch';

export type Atom = string | number | boolean | null | UUID;

const TRUE_UUID = UUID.fromString('true');
const FALSE_UUID = UUID.fromString('false');
const NULL_UUID = UUID.fromString('0');

// A RON op object. Typically, an Op is hosted in a frame.
// Frames are strings, so Op is sort of a Frame iterator.
export default class Op {
  type: UUID;
  object: UUID;
  event: UUID;
  location: UUID;

  values: string;
  parsed_values: Array<Atom> | void;

  term: string;
  source: ?string;

  constructor(type: UUID, object: UUID, event: UUID, location: UUID, values: ?string, term: ?string): Op {
    this.type = type;
    this.object = object;
    this.event = event;
    this.location = location;
    this.values = values || '';
    this.parsed_values = undefined;

    this.term = term || ';';
    this.source = null; // FIXME remove
    return this;
  }

  value(i: number): Atom {
    if (!this.parsed_values) this.parsed_values = ron2js(this.values);
    return this.parsed_values[i];
  }

  isHeader(): boolean {
    return this.term === '!';
  }

  isQuery(): boolean {
    return this.term === '?';
  }

  isRegular(): boolean {
    return !this.isHeader() && !this.isQuery();
  }

  isError(): boolean {
    return this.event.value === ERROR.value;
  }

  isComment(): boolean {
    return this.type.eq(COMMENT);
  }

  // Get op UUID by index (0-3)
  uuid(i: 0 | 1 | 2 | 3): UUID {
    switch (i) {
      case 0:
        return this.type;
      case 1:
        return this.object;
      case 2:
        return this.event;
      case 3:
        return this.location;
      default:
        throw new Error('incorrect uuid index');
    }
  }

  key(): string {
    return '*' + this.type.value + '#' + this.object.value;
  }

  toString(ctxOp: ?Op): string {
    let ret = '';
    const ctx = ctxOp || ZERO;
    let expComma = ctx.term !== ';';

    for (const u of [0, 1, 2, 3]) {
      const uuid = this.uuid(u);
      const same = ctx.uuid(u);
      if (uuid.eq(same)) continue;
      let str = uuid.toString(same);
      ret += UUID_SEPS[u];
      ret += str;
    }

    ret += this.values;
    if (!this.values || (expComma && this.term !== ',') || (!expComma && this.term !== ';')) {
      ret += this.term;
    }
    return ret;
  }

  clone(): Op {
    return new Op(this.type, this.object, this.event, this.location, this.values, this.term);
  }

  equal(other: Op): boolean {
    return (
      this.uuid(0).eq(other.uuid(0)) &&
      this.uuid(1).eq(other.uuid(1)) &&
      this.uuid(2).eq(other.uuid(2)) &&
      this.uuid(3).eq(other.uuid(3)) &&
      this.values === other.values &&
      this.term === other.term
    );
  }

  static fromString(body: string, context: ?Op, offset: ?number): ?Op {
    let ctx = context || ZERO;
    const off = offset || 0;
    RE.lastIndex = off;
    const m: string[] | void = RE.exec(body);
    if (!m || m[0] === '' || m.index !== off) return null;
    if (m[1] === COMMENT.value) ctx = ZERO;
    let term = m[6];
    if (!term) {
      if (ctx.term === '!') {
        term = ',';
      } else {
        term = ctx.term;
      }
    }

    const ret = new Op(
      UUID.fromString(m[1], ctx.type),
      UUID.fromString(m[2], ctx.object),
      UUID.fromString(m[3], ctx.event),
      UUID.fromString(m[4], ctx.location),
      m[5],
      term,
    );
    ret.source = m[0];
    return ret;
  }
}

// Parse RON value atoms.
export function ron2js(values: string): Array<Atom> {
  VALUE_RE.lastIndex = 0;
  let m: string[] | void;
  const ret = [];

  while ((m = (VALUE_RE.exec(values): string[]))) {
    if (m[1]) {
      ret.push(parseInt(m[1]));
    } else if (m[2]) {
      ret.push(JSON.parse('"' + m[2] + '"')); // VALUE_RE returns match w/o single quotes
    } else if (m[3]) {
      ret.push(parseFloat(m[3]));
    } else if (m[4]) {
      switch (m[4]) {
        case TRUE_UUID.value:
          ret.push(true);
          break;
        case FALSE_UUID.value:
          ret.push(false);
          break;
        case NULL_UUID.value:
          ret.push(null);
          break;
        default:
          ret.push(UUID.fromString(m[4]));
      }
    }
  }
  return ret;
}

// Serialize JS primitives into RON atoms.
export function js2ron(values: Array<Atom>): string {
  const ret = values.map(v => {
    if (v === undefined) return UUID_ATOM_SEP + ZERO_UUID.toString();
    if (v === null) return UUID_ATOM_SEP + NULL_UUID.toString();

    switch (v.constructor) {
      case String:
        const json = JSON.stringify(v);
        const escq = json.replace(/'/g, "\\u0027");
        return "'" + escq.substr(1, escq.length-2) + "'";
      case Number:
        return Number.isInteger(v) ? INT_ATOM_SEP + v.toString() : FLOAT_ATOM_SEP + v.toString();
      case UUID:
        return UUID_ATOM_SEP + v.toString();
      case Boolean:
        return UUID_ATOM_SEP + (v ? TRUE_UUID : FALSE_UUID).toString();
      default:
        if (v === Op.FRAME_ATOM) return FRAME_SEP;
        if (v === Op.QUERY_ATOM) return QUERY_SEP;
        throw new Error('unsupported type');
    }
  });
  return ret.join('');
}

export const RE = new RegExp(Grammar.OP.source, 'g');
export const VALUE_RE = new RegExp(Grammar.ATOM.source, 'g');
export const ZERO = new Op(ZERO_UUID, ZERO_UUID, ZERO_UUID, ZERO_UUID, '>0');

export const END = new Op(ERROR, ERROR, ERROR, ERROR, '>~');
export const PARSE_ERROR = new Op(ERROR, ERROR, ERROR, ERROR, '>parseerror');
export const REDEF_SEPS = '`';
export const UUID_SEPS = '*#@:';
export const FRAME_ATOM = Symbol('FRAME');
export const QUERY_ATOM = Symbol('QUERY');
export const INT_ATOM_SEP = '=';
export const FLOAT_ATOM_SEP = '^';
export const UUID_ATOM_SEP = '>';
export const FRAME_SEP = '!';
export const QUERY_SEP = '?';

export class Frame {
  body: string;
  last: Op;

  constructor(str: ?string): Frame {
    this.body = str ? str.toString() : '';
    this.last = ZERO;
    return this;
  }

  // Append a new op to the frame
  push(op: Op): void {
    if (this.last.isComment()) {
      this.last = ZERO;
    }

    this.body += op.toString(this.last);
    this.last = op;
  }

  pushWithTerm(op: Op, term: ',' | '!' | '?' | ';'): void {
    if (this.last.isComment()) {
      this.last = ZERO;
    }

    const clone = op.clone();
    clone.term = term;

    this.body += clone.toString(this.last);
    this.last = clone;
  }

  /*:: @@iterator(): Iterator<Op> { return ({}: any); } */

  // $FlowFixMe - computed property
  [Symbol.iterator](): Iterator<Op> {
    return new Cursor(this.body);
  }

  toString(): string {
    return this.body;
  }

  mapUUIDs(fn: (UUID, number, number, Op) => UUID) {
    this.body = mapUUIDs(this.body, fn);
    for (const op of this) {
      this.last = op;
    }
  }

  equal(other: Frame): boolean {
    if (this.toString() === other.toString()) return true;
    const cursor = new Cursor(other.toString());
    for (const op of this) {
      const oop = cursor.op;
      if (!oop || !op.equal(oop)) return false;
      cursor.next();
    }
    return cursor.next().done;
  }

  isFullState(): boolean {
    for (const op of this) return op.isHeader() && op.uuid(3).isZero();
    return false;
  }

  isPayload(): boolean {
    for (const op of this) if (op.isRegular()) return true;
    return false;
  }

  filter(p: Op => boolean): Frame {
    const ret = new Frame();
    for (const op of this) if (p(op)) ret.push(op);
    return ret;
  }

  ID(): UUID {
    for (const op of this) return op.uuid(1);
    return ZERO_UUID;
  }
}

// Substitute UUIDs in all of the frame's ops.
// Typically used for macro expansion.
export function mapUUIDs(rawFrame: string, fn: (UUID, number, number, Op) => UUID): string {
  const ret = new Frame();
  let index = -1;
  for (const op of new Frame(rawFrame)) {
    index++;
    ret.push(
      new Op(
        fn(op.type, 0, index, op) || op.type,
        fn(op.object, 1, index, op) || op.object,
        fn(op.event, 2, index, op) || op.event,
        fn(op.location, 3, index, op) || op.location,
        op.values,
        op.term,
      ),
    );
  }
  return ret.toString();
}

// Crop a frame, i.e. make a new [from,till) frame
export function slice(from: Cursor, till: Cursor): string {
  if (!from.op) return '';
  if (from.body !== till.body) throw new Error('iterators of different frames');
  let ret = from.op.toString();
  ret += from.body.substring(from.offset + from.length, till.op ? till.offset : undefined);
  return ret;
}

export class Cursor implements Iterator<Op> {
  body: string;
  offset: number;
  length: number;
  op: ?Op;
  ctx: ?Op;

  constructor(body: ?string): Cursor {
    this.body = body ? body.toString() : '';
    this.offset = 0;
    this.length = 0;
    this.op = this.nextOp();
    return this;
  }

  toString(): string {
    return this.body;
  }

  clone(): Cursor {
    const ret = new Cursor(this.body);
    ret.offset = this.offset;
    ret.length = this.length;
    ret.op = this.op;
    ret.ctx = this.ctx;
    return ret;
  }

  nextOp(): ?Op {
    this.offset += this.length;
    if (this.offset === this.body.length) {
      this.op = null;
      this.length = 1;
    } else {
      const op = Op.fromString(this.body, this.ctx, this.offset);
      this.ctx = op;
      if (op) {
        if (op.isComment()) this.ctx = ZERO;
        if (op.source) this.length = op.source.length;
      }
      this.op = op;
    }
    return this.op;
  }

  /*:: @@iterator(): Iterator<Op> { return ({}: any); } */

  // $FlowFixMe - computed property
  [Symbol.iterator](): Iterator<Op> {
    return this;
  }

  next(): IteratorResult<Op, void> {
    const ret = this.op;
    if (ret) {
      this.nextOp();
      return {done: false, value: ret};
    } else {
      return {done: true};
    }
  }
}
<|endoftext|>