<commit_before>// NML VM
// Runs NML AST, stored in a MObject
// A VM would exist for each script/verb, and would be reset/replaced with a
// new VM when those scripts/verbs are updated/edited.

var async = require('async');

var OPERATORS          = require('./constants').OPERATORS;
var TYPE_COMPATIBILITY = require('./constants').TYPE_COMPATIBILITY;
var OP_ORDER           = require('./constants').OP_ORDER;

var EndOfScriptError = require('./errors').EndOfScriptError;

var VM = function(app, mobj, ast) {
  this._app = app;
  this.mobj = mobj; // MOO Object

  // Initialize the state
  this.state = {
    ast: ast,
    localVars: {},
    ip: [0] // Instruction pointer/path; this is an array as the AST is a tree with many levels
            // Example: [2, 4, 10] for the second object, then fourth, then tenth object
  }
}

VM.findIns = function(ip, ast) {
  // Find the instruction specified by instruction path IP
  if(ip.length <= 1) {
    return ast[ip[0]];
  } else {
    if(typeof(ast[ip[0]]) === 'object' && ast[ip[0]].block !== undefined) {
      return VM.findIns(ip.slice(1, ip.length), ast[ip[0]].block);
    } else {
      return ast; // Path not valid, just return the ast at it's current state
    }
  }
}

// NML type array
var traverseArray = function(arr, path) {
  if(path.length <= 1) {
    return arr.ctx[path[0] || 0];
  } else {
    return traverseArray(arr.ctx[path[0]], path.slice(1, path.length));
  }
}

// Resolve possible indirect values, like variables, properties, etc
// Async because it _might_ hit the DB
VM.prototype.resolveValue = function(value, callback) {
  if(typeof(value) !== 'object') return callback(null, value); // This is a direct value, just return it.
  if(value instanceof Array) { // Not arrays, expression groups. Arrays are {type: 'array', ...}
    // If the array has a single item, return that instead of the whole array
    // This helps collapse expressions for evalExpr
    if(value.length === 1) return this.evalExpr(value[0], callback);
    return this.evalExpr(value, callback);
  }
  switch(value.type) {
    case 'var': // Local variable, return it from our state.
      // Return an element of the array if provided with an index
      if(value.ctx) {
        if(typeof(this.state.localVars[value.name]) !== 'object' || this.state.localVars[value.name].type !== 'array') return callback(new Error('attempted to index nonindexable local var'));
        return callback(null, traverseArray(this.state.localVars[value.name], value.ctx));
      }
      return callback(null, this.state.localVars[value.name]);
    case 'prop': // This is a property, fetch it from the object
      // Return an element of the array if provided with an index
      if(value.ctx) return this.mobj.getProp(value.name, function(err, val) {
        if(err) return callback(err);
        if(typeof(val) !== 'object' || val.type !== 'array') return callback(new Error('attempted to index nonindexable object prop'));
        return callback(null, traverseArray(val, value.ctx));
      });
      return this.mobj.getProp(value.name, callback);
    case 'objectIdAlias': // This is an object id alias, ask the app for what it means
      return callback(null, this._app.resolveObjIdAlias(value.value));
    default: // This may be an object like a virtual null, just pass it through
      return callback(null, value);
  }
}

// Evaluates expresisons, which can include anything including verbcalls
// Async because resolveValue _might_ hit the DB
VM.prototype.evalExpr = function(expr, finalCallback) {
  expr = expr.slice(); // Shallow copy expr, as we will be modifying it
  // First, if the second component is not an operator and the whole expression
  // has no JS objects/hashes, then just return the expression joined by space
  // UNLESS, the first component is a number
  if(OPERATORS[expr[1]] === undefined && typeof(expr[0]) !== 'number') {
    var noObjects = true;
    for(var obj of expr) { if(typeof(obj) === 'object') { noObjects = false; break; }}
    if(noObjects) return finalCallback(null, expr.join(' '));
  }

  var parseAllOps = function(opTypeIndex) {
    var nextOpType = function() {
      if(opTypeIndex + 1 >= OP_ORDER.length || expr.length < 2) {
        finalCallback(null, expr[0]); // The result should be the only thing remaining
                                      // in the expression.
      } else {
        parseAllOps(opTypeIndex + 1);
      }
    }.bind(this);
    var op = OP_ORDER[opTypeIndex];

    var nextOp = function() { // We shouldn't need an index of where to search
                              // for the next op if we delete the op and replace
                              // it with it's result
      var opIndex = expr.indexOf(op);
      if(opIndex === -1) return nextOpType(); // Find the next op type if there is no more of this op type
      async.map([expr[opIndex-1], expr[opIndex+1]], this.resolveValue.bind(this), function(err, results) {
        if(err) return finalCallback(err);
        // We have our resolved results in the results array, left = [0], right = [1]
        var left = results[0], right = results[1];
        // Check types
        if(TYPE_COMPATIBILITY[op] !== undefined && TYPE_COMPATIBILITY[op][typeof(right)].indexOf(typeof(left)) < 0) return finalCallback(new Error('type ' + typeof(left) + ' cannot be opped ' + op + ' with ' + typeof(right)));
        // Splice out the op and the right side from the expression
        expr.splice(opIndex, 2);
        // Set the opIndex - 1 (left side which is still intact) to the result
        expr[opIndex - 1] = OPERATORS[op](left, right);
        // Find the next op of this type
        nextOp();
      }.bind(this));
    }.bind(this);

    nextOp();
  }.bind(this);

  parseAllOps(0);
}

VM.prototype.stepOnce = function(callback) {
  // First find the Instruction
  var ins = VM.findIns(this.state.ip, this.state.ast); // _that was easy_

  // Finishes job by incrementing the IP accordingly
  var jobDone = function(err, results) {
    if(err) return callback(err);
    // If this is a branching instruction and we need to branch (results[0]),
    // branch instead of incrementing the IP
    if((ins.type === 'if' || ins.type === 'while') && results && ins.block.length > 0) {
      this.state.ip.push(0); // This simply bumps us into the next instruction to the right on the AST
      return callback(null);
    } else {
      // Otherwise, we need to decide what to do
      var progressIP = function() {
        // While we are at the end of the current block (if we are)
        while(this.state.ip.length > 1 &&
              this.state.ip.slice(-1)[0] ===
              VM.findIns(this.state.ip.slice(0, -1), this.state.ast).block.length - 1) {
          if(VM.findIns(this.state.ip.slice(0, -1), this.state.ast).type === 'while') {
            // Consider looping around
            // Reevaluate the condition and if it is true, go back to the top of
            // the loop (set ip[ip.length - 1] = 0)
            // Otherwise pop off the path from ip
            this.evalExpr(VM.findIns(this.state.ip.slice(0, -1), this.state.ast).expr, function(err, value) {
              if(err) return callback(err); // If we couldn't eval the expression, error
              if(value) {
                // Restart loop
                this.state.ip[this.state.ip.length - 1] = 0;
                return callback(null);
              } else {
                // Pop off the current state to return to where we were
                this.state.ip.pop(); // Pop off the current state

                return progressIP(); // This runs through the whole escape block loop again
              }
            }.bind(this));
            return;
          } else { // Probably an if statement, just pop off the stack
            this.state.ip.pop(); // Pop off the current state
          }
        }

        // Are we at the end of the script
        if(this.state.ip.length === 1 && this.state.ip[0] === this.state.ast.length - 1)
          return callback(new EndOfScriptError()); // Error with endOfScript

        // Carry on, increment by one
        this.state.ip[this.state.ip.length - 1]++;
        callback(null); // Call the callback, everything is done
      }.bind(this);

      progressIP();
    }
  }.bind(this);

  switch(ins.type) {
    case 'while':
    case 'if':
      // while/if statement. Check if expression provided evaluates to true
      // and if so, callback with branch set to true
      this.evalExpr(ins.expr, function(err, value) {
        if(err) return jobDone(err); // If we couldn't eval the expression, error
        var out = false;
        if(value) out = true; // Evaluate the expression using JS's if evaluation
        return jobDone(null, out);
      });
      break;
    case 'assign':
      async.waterfall([
      function(c) { this.evalExpr(ins.src, c); }.bind(this),
      function(value, c) { // We have evaluated the right side of the assignment, set the value.
        if(ins.op === '=') {
          // Simple assignment
          if(ins.dst.type === 'var') {
            // Local variable, just set it here
            this.state.localVars[ins.dst.name] = value;
            c();
          } else if(ins.dst.type === 'prop') {
            // Object prop, set it and pass it the stepOnce callback.
            this.mobj.setProp(ins.dst.name, value, c);
          } else {
            c('unspecifiable error (cac96aa5-fade-4263-a56c-e4cd883447cc)');
          }
        } else {
          // Complex assignment, runs an expression.
          // We need to resolve the dst so we can type check
          this.resolveValue(ins.dst, function(err, currentValue) {
            if(TYPE_COMPATIBILITY[ins.op][typeof(value)].indexOf(typeof(currentValue)) < 0) return c('type ' + typeof(value) + ' cannot be opped ' + ins.op + ' with ' + typeof(currentValue));
            // It's all valid, do the assignment
            currentValue = OPERATORS[ins.op](currentValue, value);
            // Set currentValue back to where it came from
            if(ins.dst.type === 'prop') {
              this.mobj.setProp(ins.dst.name, currentValue, c);
            } else if(ins.dst.type === 'var') {
              this.state.localVars[ins.dst.name] = currentValue;
              return c();
            } else {
              return c('unspecifiable error (13041b5b-7392-400d-9623-44347db5f81e)');
            }
          }.bind(this));
        }
      }.bind(this)], jobDone);
      break;
    case 'verbcall':
      jobDone(); // TODO actually implement the verbcalls
      break;
  }
}

VM.prototype.step = function(times, callback) {
  async.times(times, function(i, next) {
    this.stepOnce(next);
  }.bind(this), callback);
}

module.exports = VM;
<commit_msg>functioning NML.VM#step which doesn't use a possibly faulty async.times<commit_after>// NML VM
// Runs NML AST, stored in a MObject
// A VM would exist for each script/verb, and would be reset/replaced with a
// new VM when those scripts/verbs are updated/edited.

var async = require('async');

var OPERATORS          = require('./constants').OPERATORS;
var TYPE_COMPATIBILITY = require('./constants').TYPE_COMPATIBILITY;
var OP_ORDER           = require('./constants').OP_ORDER;

var EndOfScriptError = require('./errors').EndOfScriptError;

var VM = function(app, mobj, ast) {
  this._app = app;
  this.mobj = mobj; // MOO Object

  // Initialize the state
  this.state = {
    ast: ast,
    localVars: {},
    ip: [0] // Instruction pointer/path; this is an array as the AST is a tree with many levels
            // Example: [2, 4, 10] for the second object, then fourth, then tenth object
  }
}

VM.findIns = function(ip, ast) {
  // Find the instruction specified by instruction path IP
  if(ip.length <= 1) {
    return ast[ip[0]];
  } else {
    if(typeof(ast[ip[0]]) === 'object' && ast[ip[0]].block !== undefined) {
      return VM.findIns(ip.slice(1, ip.length), ast[ip[0]].block);
    } else {
      return ast; // Path not valid, just return the ast at it's current state
    }
  }
}

// NML type array
var traverseArray = function(arr, path) {
  if(path.length <= 1) {
    return arr.ctx[path[0] || 0];
  } else {
    return traverseArray(arr.ctx[path[0]], path.slice(1, path.length));
  }
}

// Resolve possible indirect values, like variables, properties, etc
// Async because it _might_ hit the DB
VM.prototype.resolveValue = function(value, callback) {
  if(typeof(value) !== 'object') return callback(null, value); // This is a direct value, just return it.
  if(value instanceof Array) { // Not arrays, expression groups. Arrays are {type: 'array', ...}
    // If the array has a single item, return that instead of the whole array
    // This helps collapse expressions for evalExpr
    if(value.length === 1) return this.evalExpr(value[0], callback);
    return this.evalExpr(value, callback);
  }
  switch(value.type) {
    case 'var': // Local variable, return it from our state.
      // Return an element of the array if provided with an index
      if(value.ctx) {
        if(typeof(this.state.localVars[value.name]) !== 'object' || this.state.localVars[value.name].type !== 'array') return callback(new Error('attempted to index nonindexable local var'));
        return callback(null, traverseArray(this.state.localVars[value.name], value.ctx));
      }
      return callback(null, this.state.localVars[value.name]);
    case 'prop': // This is a property, fetch it from the object
      // Return an element of the array if provided with an index
      if(value.ctx) return this.mobj.getProp(value.name, function(err, val) {
        if(err) return callback(err);
        if(typeof(val) !== 'object' || val.type !== 'array') return callback(new Error('attempted to index nonindexable object prop'));
        return callback(null, traverseArray(val, value.ctx));
      });
      return this.mobj.getProp(value.name, callback);
    case 'objectIdAlias': // This is an object id alias, ask the app for what it means
      return callback(null, this._app.resolveObjIdAlias(value.value));
    default: // This may be an object like a virtual null, just pass it through
      return callback(null, value);
  }
}

// Evaluates expresisons, which can include anything including verbcalls
// Async because resolveValue _might_ hit the DB
VM.prototype.evalExpr = function(expr, finalCallback) {
  expr = expr.slice(); // Shallow copy expr, as we will be modifying it
  // First, if the second component is not an operator and the whole expression
  // has no JS objects/hashes, then just return the expression joined by space
  // UNLESS, the first component is a number
  if(OPERATORS[expr[1]] === undefined && typeof(expr[0]) !== 'number') {
    var noObjects = true;
    for(var obj of expr) { if(typeof(obj) === 'object') { noObjects = false; break; }}
    if(noObjects) return finalCallback(null, expr.join(' '));
  }

  var parseAllOps = function(opTypeIndex) {
    var nextOpType = function() {
      if(opTypeIndex + 1 >= OP_ORDER.length || expr.length < 2) {
        finalCallback(null, expr[0]); // The result should be the only thing remaining
                                      // in the expression.
      } else {
        parseAllOps(opTypeIndex + 1);
      }
    }.bind(this);
    var op = OP_ORDER[opTypeIndex];

    var nextOp = function() { // We shouldn't need an index of where to search
                              // for the next op if we delete the op and replace
                              // it with it's result
      var opIndex = expr.indexOf(op);
      if(opIndex === -1) return nextOpType(); // Find the next op type if there is no more of this op type
      async.map([expr[opIndex-1], expr[opIndex+1]], this.resolveValue.bind(this), function(err, results) {
        if(err) return finalCallback(err);
        // We have our resolved results in the results array, left = [0], right = [1]
        var left = results[0], right = results[1];
        // Check types
        if(TYPE_COMPATIBILITY[op] !== undefined && TYPE_COMPATIBILITY[op][typeof(right)].indexOf(typeof(left)) < 0) return finalCallback(new Error('type ' + typeof(left) + ' cannot be opped ' + op + ' with ' + typeof(right)));
        // Splice out the op and the right side from the expression
        expr.splice(opIndex, 2);
        // Set the opIndex - 1 (left side which is still intact) to the result
        expr[opIndex - 1] = OPERATORS[op](left, right);
        // Find the next op of this type
        nextOp();
      }.bind(this));
    }.bind(this);

    nextOp();
  }.bind(this);

  parseAllOps(0);
}

VM.prototype.stepOnce = function(callback) {
  // First find the Instruction
  var ins = VM.findIns(this.state.ip, this.state.ast); // _that was easy_

  // Finishes job by incrementing the IP accordingly
  var jobDone = function(err, results) {
    if(err) return callback(err);
    // If this is a branching instruction and we need to branch (results[0]),
    // branch instead of incrementing the IP
    if((ins.type === 'if' || ins.type === 'while') && results && ins.block.length > 0) {
      this.state.ip.push(0); // This simply bumps us into the next instruction to the right on the AST
      return callback(null);
    } else {
      // Otherwise, we need to decide what to do
      var progressIP = function() {
        // While we are at the end of the current block (if we are)
        while(this.state.ip.length > 1 &&
              this.state.ip.slice(-1)[0] ===
              VM.findIns(this.state.ip.slice(0, -1), this.state.ast).block.length - 1) {
          if(VM.findIns(this.state.ip.slice(0, -1), this.state.ast).type === 'while') {
            // Consider looping around
            // Reevaluate the condition and if it is true, go back to the top of
            // the loop (set ip[ip.length - 1] = 0)
            // Otherwise pop off the path from ip
            this.evalExpr(VM.findIns(this.state.ip.slice(0, -1), this.state.ast).expr, function(err, value) {
              if(err) return callback(err); // If we couldn't eval the expression, error
              if(value) {
                // Restart loop
                this.state.ip[this.state.ip.length - 1] = 0;
                return callback(null);
              } else {
                // Pop off the current state to return to where we were
                this.state.ip.pop(); // Pop off the current state

                return progressIP(); // This runs through the whole escape block loop again
              }
            }.bind(this));
            return;
          } else { // Probably an if statement, just pop off the stack
            this.state.ip.pop(); // Pop off the current state
          }
        }

        // Are we at the end of the script
        if(this.state.ip.length === 1 && this.state.ip[0] === this.state.ast.length - 1)
          return callback(new EndOfScriptError()); // Error with endOfScript

        // Carry on, increment by one
        this.state.ip[this.state.ip.length - 1]++;
        callback(null); // Call the callback, everything is done
      }.bind(this);

      progressIP();
    }
  }.bind(this);

  switch(ins.type) {
    case 'while':
    case 'if':
      // while/if statement. Check if expression provided evaluates to true
      // and if so, callback with branch set to true
      this.evalExpr(ins.expr, function(err, value) {
        if(err) return jobDone(err); // If we couldn't eval the expression, error
        var out = false;
        if(value) out = true; // Evaluate the expression using JS's if evaluation
        return jobDone(null, out);
      });
      break;
    case 'assign':
      async.waterfall([
      function(c) { this.evalExpr(ins.src, c); }.bind(this),
      function(value, c) { // We have evaluated the right side of the assignment, set the value.
        if(ins.op === '=') {
          // Simple assignment
          if(ins.dst.type === 'var') {
            // Local variable, just set it here
            this.state.localVars[ins.dst.name] = value;
            c();
          } else if(ins.dst.type === 'prop') {
            // Object prop, set it and pass it the stepOnce callback.
            this.mobj.setProp(ins.dst.name, value, c);
          } else {
            c('unspecifiable error (cac96aa5-fade-4263-a56c-e4cd883447cc)');
          }
        } else {
          // Complex assignment, runs an expression.
          // We need to resolve the dst so we can type check
          this.resolveValue(ins.dst, function(err, currentValue) {
            if(TYPE_COMPATIBILITY[ins.op][typeof(value)].indexOf(typeof(currentValue)) < 0) return c('type ' + typeof(value) + ' cannot be opped ' + ins.op + ' with ' + typeof(currentValue));
            // It's all valid, do the assignment
            currentValue = OPERATORS[ins.op](currentValue, value);
            // Set currentValue back to where it came from
            if(ins.dst.type === 'prop') {
              this.mobj.setProp(ins.dst.name, currentValue, c);
            } else if(ins.dst.type === 'var') {
              this.state.localVars[ins.dst.name] = currentValue;
              return c();
            } else {
              return c('unspecifiable error (13041b5b-7392-400d-9623-44347db5f81e)');
            }
          }.bind(this));
        }
      }.bind(this)], jobDone);
      break;
    case 'verbcall':
      jobDone(); // TODO actually implement the verbcalls
      break;
  }
}

VM.prototype.step = function(times, callback) {
  var loop = function(i) {
    this.stepOnce(function(err) {
      if(err) return callback(err);
      if(i < times - 1)
        return loop(i + 1);
      callback(null);
    });
  }.bind(this);

  loop(0);
}

module.exports = VM;
<|endoftext|>


<commit_before>'use strict';
const logger = require('winston');
const config = require('../config');
const moment = require('moment');

module.exports = {

  guildCreate: (client, guild) => {
    const str = `${client.user.username} joined Discord guild: **${guild.name}** ` +
      `- Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildDelete: (client, guild) => {
    const str = `${client.user.username} parted Discord guild: **${guild.name}** ` +
      `-  Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildMemberAdd: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting welcome messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.RichEmbed()
      .setColor('#1ed21e')
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL)
      .setFooter('User Joined')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  guildMemberRemove: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting part messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.RichEmbed()
      .setColor('#d7d71e')
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL)
      .setFooter('User Left')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageDeleted: async (client, message) => {
    if (!message.content) return;
    const deletedAt = Date.now();
    const deletedAfterDuration = moment.duration(deletedAt - message.createdTimestamp);

    // Determine if there are any channels interested in this notification
    // Only matches channels in the same guild the message was deleted in
    const clientGuildMember = message.guild.members.get(client.user.id);
    if (!clientGuildMember) return;
    if (!clientGuildMember.hasPermission(['VIEW_AUDIT_LOG'])) return;
    const channels = await client.mongo.auditChannels.find({ server_id: message.guild.id });
    // Exit if there are channels to notify
    if (!channels) return;
    const entry = await message.guild.fetchAuditLogs({ type: 'MESSAGE_DELETE' }).then(audit => audit.entries.first());

    let executor;
    let self;
    if (entry &&
      entry.extra.channel.id === message.channel.id &&
      entry.target.id === message.author.id &&
      deletedAt - entry.createdTimestamp < 5000 &&
      entry.extra.count >= 1) {
      executor = entry.executor;
      self = false;
    } else {
      executor = message.author;
      self = true;
    }

    if (self === true && (executor.bot || executor.id === client.user.id)) return;

    logger.debug(`Posting deletion audit messages in (${channels.length}) registered channels`);

    const embed = new client.Discord.RichEmbed()
      .setAuthor(`${message.author.tag} (${message.author.id})`, message.author.displayAvatarURL)
      .setTitle(`#${message.channel.name} (${message.channel.id}) (${message.id})`)
      .setURL(`https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`)
      .setDescription(message.cleanContent)
      .setFooter(`Deleted by ${self ? 'SELF' : executor.tag} after ${deletedAfterDuration.humanize()}`)
      .setTimestamp();
    if (!self) embed.setColor('#d76db7');
    channels.forEach(c => {
      const channel = message.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageDelete', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageUpdated: async (client, oldMessage, newMessage) => {
    return;
    if (oldMessage.author.id === client.user.id) return;
    const channels = await client.mongo.auditChannels.find({ server_id: oldMessage.guild.id });
    if (!channels) return;
    const embed = new client.Discord.RichEmbed()
      .setAuthor(`${oldMessage.author.tag} (${oldMessage.author.id})`, oldMessage.author.displayAvatarURL)
      .setFooter(`Updated: (${oldMessage.id})`)
      .setTimestamp();
    if (oldMessage.content) embed.addField('Original', oldMessage.cleanContent);
    if (newMessage.content) embed.addField('Updated', newMessage.cleanContent);
    channels.forEach(c => {
      const channel = oldMessage.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageUpdate', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },
};
<commit_msg>add condition to notify function<commit_after>'use strict';
const logger = require('winston');
const config = require('../config');
const moment = require('moment');

module.exports = {

  guildCreate: (client, guild) => {
    const str = `${client.user.username} joined Discord guild: **${guild.name}** ` +
      `- Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildDelete: (client, guild) => {
    const str = `${client.user.username} parted Discord guild: **${guild.name}** ` +
      `-  Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildMemberAdd: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting welcome messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.RichEmbed()
      .setColor('#1ed21e')
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL)
      .setFooter('User Joined')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  guildMemberRemove: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting part messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.RichEmbed()
      .setColor('#d7d71e')
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL)
      .setFooter('User Left')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageDeleted: async (client, message) => {
    if (!message.content) return;
	if (!message.guild) return;
    const deletedAt = Date.now();
    const deletedAfterDuration = moment.duration(deletedAt - message.createdTimestamp);

    // Determine if there are any channels interested in this notification
    // Only matches channels in the same guild the message was deleted in
    const clientGuildMember = message.guild.members.get(client.user.id);
    if (!clientGuildMember) return;
    if (!clientGuildMember.hasPermission(['VIEW_AUDIT_LOG'])) return;
    const channels = await client.mongo.auditChannels.find({ server_id: message.guild.id });
    // Exit if there are channels to notify
    if (!channels) return;
    const entry = await message.guild.fetchAuditLogs({ type: 'MESSAGE_DELETE' }).then(audit => audit.entries.first());

    let executor;
    let self;
    if (entry &&
      entry.extra.channel.id === message.channel.id &&
      entry.target.id === message.author.id &&
      deletedAt - entry.createdTimestamp < 5000 &&
      entry.extra.count >= 1) {
      executor = entry.executor;
      self = false;
    } else {
      executor = message.author;
      self = true;
    }

    if (self === true && (executor.bot || executor.id === client.user.id)) return;

    logger.debug(`Posting deletion audit messages in (${channels.length}) registered channels`);

    const embed = new client.Discord.RichEmbed()
      .setAuthor(`${message.author.tag} (${message.author.id})`, message.author.displayAvatarURL)
      .setTitle(`#${message.channel.name} (${message.channel.id}) (${message.id})`)
      .setURL(`https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`)
      .setDescription(message.cleanContent)
      .setFooter(`Deleted by ${self ? 'SELF' : executor.tag} after ${deletedAfterDuration.humanize()}`)
      .setTimestamp();
    if (!self) embed.setColor('#d76db7');
    channels.forEach(c => {
      const channel = message.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageDelete', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageUpdated: async (client, oldMessage, newMessage) => {
    return;
    if (oldMessage.author.id === client.user.id) return;
    const channels = await client.mongo.auditChannels.find({ server_id: oldMessage.guild.id });
    if (!channels) return;
    const embed = new client.Discord.RichEmbed()
      .setAuthor(`${oldMessage.author.tag} (${oldMessage.author.id})`, oldMessage.author.displayAvatarURL)
      .setFooter(`Updated: (${oldMessage.id})`)
      .setTimestamp();
    if (oldMessage.content) embed.addField('Original', oldMessage.cleanContent);
    if (newMessage.content) embed.addField('Updated', newMessage.cleanContent);
    channels.forEach(c => {
      const channel = oldMessage.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageUpdate', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },
};
<|endoftext|>


<commit_before>
function whenthen(value, criteria, when, then) {
        for (var n in criteria) {
            var criterium = criteria[n];

            if (match(criterium, value) == when)
                return then;
        }

        return !then;
}

var operators = {
    $lt: function (value, criterium) { return value < criterium; },
    $gt: function (value, criterium) { return value > criterium; },
    $lte: function (value, criterium) { return value <= criterium; },
    $gte: function (value, criterium) { return value >= criterium; },
    $ne: function (value, criterium) { return value != criterium; },
    $eq: function (value, criterium) { return value == criterium; },
    $in: function (value, criterium) { return criterium.indexOf(value) >= 0; },
    $nin: function (value, criterium) { return criterium.indexOf(value) < 0; },
    $not: function (value, criterium) { return !match(criterium, value); },
    $or: function (value, criteria) { return whenthen(value, criteria, true, true); },
    $nor: function (value, criteria) { return whenthen(value, criteria, true, false); },
    $and: function (value, criteria) { return whenthen(value, criteria, false, false); },
    $exists: function (value, criteria) {
        var exists = (value !== undefined);
        return exists == criteria;
    },
}

function Store() {
    var values = {};
    var maxid = 0;

    this.clear = function() {
        values = {};
        maxid = 0;
    };

    this.get = function (id) {
        if (!values[id])
            return null;

        return clone(values[id]);
    };

    this.put = function (id, item) {
        values[id] = clone(item);
    };

    this.remove = function (id) {
        delete values[id];
    };

    this.add = function (item) {
        var newitem = clone(item);
        newitem.id = ++maxid;
        values[newitem.id] = newitem;
        return newitem.id;
    };

    this.update = function (id, newvalues) {
        var item = values[id];

        for (var n in newvalues)
            item[n] = newvalues[n];
    };

    this.find = function (criteria, fields) {
        return find(values, criteria, fields);
    };

    this.aggregate = function () {
        return aggregate(values, Array.prototype.slice.call(arguments));
    }
}

function find(values, criteria, fields) {
    var result = [];

    for (var n in values) {
        var value = values[n];

        if (match(criteria, value))
            result.push(clone(value, fields));
    }

    return result;
}

function aggregate(values, specs) {
    if (!specs || !specs.length) {
        if (Array.isArray(values))
            return values;

        var result = [];

        for (var n in values)
            result.push(clone(values[n]));

        return result;
    }

    var spec = specs.shift();

    var result = [];

    if (spec && spec.$match)
        return aggregate(find(values, spec.$match), specs);

    var nv = 0;

    for (var n in values) {
        var value = values[n];
        nv++;

        if (spec && spec.$skip)
            if (nv <= spec.$skip)
                continue;

        if (spec && spec.$project)
            result.push(clone(value, spec.$project));
        else
            result.push(clone(value));

        if (spec && spec.$limit)
            if (result.length >= spec.$limit)
                break;
    }

    return aggregate(result, specs);
}

function match(criteria, value)
{
    for (var n in criteria) {
        var criterium = criteria[n];

        if (n[0] == '$') {
            if (operators[n])
                if (operators[n](value, criterium))
                    continue;
                else
                    return false;

            throw new Error("Invalid operator '" + n + "'");
        }

        if (typeof criterium == 'object' && !Array.isArray(criterium))
            return match(criterium, value[n]);

        if (criterium !== value[n])
            return false;
    }

    return true;
}

function clone(item, fields) {
    var cloned = {};
    var keys = null;

    if (fields)
        keys = Object.keys(fields);

    if (!keys || keys.length == 0)
        for (var n in item)
            cloned[n] = item[n];
    else {
        var docopy = true;

        for (var n in fields)
            if (fields[n] === 1 || fields[n] === true)
                docopy = false;

        for (var n in item) {
            if (fields[n] === 0 || fields[n] === false)
                continue;
            else if ((fields[n] === 1 || fields[n] === true) || (docopy && fields[n] === undefined))
                cloned[n] = item[n];
        }
    }

    return cloned;
}

function createStore() {
    return new Store();
}

module.exports = {
    createStore: createStore
}

<commit_msg>Aggregate with sort, WIP<commit_after>
function whenthen(value, criteria, when, then) {
        for (var n in criteria) {
            var criterium = criteria[n];

            if (match(criterium, value) == when)
                return then;
        }

        return !then;
}

var operators = {
    $lt: function (value, criterium) { return value < criterium; },
    $gt: function (value, criterium) { return value > criterium; },
    $lte: function (value, criterium) { return value <= criterium; },
    $gte: function (value, criterium) { return value >= criterium; },
    $ne: function (value, criterium) { return value != criterium; },
    $eq: function (value, criterium) { return value == criterium; },
    $in: function (value, criterium) { return criterium.indexOf(value) >= 0; },
    $nin: function (value, criterium) { return criterium.indexOf(value) < 0; },
    $not: function (value, criterium) { return !match(criterium, value); },
    $or: function (value, criteria) { return whenthen(value, criteria, true, true); },
    $nor: function (value, criteria) { return whenthen(value, criteria, true, false); },
    $and: function (value, criteria) { return whenthen(value, criteria, false, false); },
    $exists: function (value, criteria) {
        var exists = (value !== undefined);
        return exists == criteria;
    },
}

function Store() {
    var values = {};
    var maxid = 0;

    this.clear = function() {
        values = {};
        maxid = 0;
    };

    this.get = function (id) {
        if (!values[id])
            return null;

        return clone(values[id]);
    };

    this.put = function (id, item) {
        values[id] = clone(item);
    };

    this.remove = function (id) {
        delete values[id];
    };

    this.add = function (item) {
        var newitem = clone(item);
        newitem.id = ++maxid;
        values[newitem.id] = newitem;
        return newitem.id;
    };

    this.update = function (id, newvalues) {
        var item = values[id];

        for (var n in newvalues)
            item[n] = newvalues[n];
    };

    this.find = function (criteria, fields) {
        return find(values, criteria, fields);
    };

    this.aggregate = function () {
        return aggregate(values, Array.prototype.slice.call(arguments));
    }
}

function find(values, criteria, fields) {
    var result = [];

    for (var n in values) {
        var value = values[n];

        if (match(criteria, value))
            result.push(clone(value, fields));
    }

    return result;
}

function aggregate(values, specs) {
    if (!specs || !specs.length) {
        if (Array.isArray(values))
            return values;

        var result = [];

        for (var n in values)
            result.push(clone(values[n]));

        return result;
    }

    var spec = specs.shift();

    var result = [];

    if (spec && spec.$match)
        return aggregate(find(values, spec.$match), specs);

    var nv = 0;

    for (var n in values) {
        var value = values[n];
        nv++;

        if (spec && spec.$skip)
            if (nv <= spec.$skip)
                continue;

        if (spec && spec.$project)
            result.push(clone(value, spec.$project));
        else
            result.push(clone(value));

        if (spec && spec.$limit)
            if (result.length >= spec.$limit)
                break;
    }

	if (spec && spec.$sort) {
		var name = Object.keys(spec.$sort)[0];
		var descending = spec.$sort[name] !== 1;

		result.sort(function (a, b) {
            var aval = a[name];
            var bval = b[name];

            if (aval < bval)
                if (descending)
                    return 1;
                else
                    return -1;

            if (aval > bval)
                if (descending)
                    return -1;
                else
                    return 1;

            return 0;
        });
	}

    return aggregate(result, specs);
}

function match(criteria, value)
{
    for (var n in criteria) {
        var criterium = criteria[n];

        if (n[0] == '$') {
            if (operators[n])
                if (operators[n](value, criterium))
                    continue;
                else
                    return false;

            throw new Error("Invalid operator '" + n + "'");
        }

        if (typeof criterium == 'object' && !Array.isArray(criterium))
            return match(criterium, value[n]);

        if (criterium !== value[n])
            return false;
    }

    return true;
}

function clone(item, fields) {
    var cloned = {};
    var keys = null;

    if (fields)
        keys = Object.keys(fields);

    if (!keys || keys.length == 0)
        for (var n in item)
            cloned[n] = item[n];
    else {
        var docopy = true;

        for (var n in fields)
            if (fields[n] === 1 || fields[n] === true)
                docopy = false;

        for (var n in item) {
            if (fields[n] === 0 || fields[n] === false)
                continue;
            else if ((fields[n] === 1 || fields[n] === true) || (docopy && fields[n] === undefined))
                cloned[n] = item[n];
        }
    }

    return cloned;
}

function createStore() {
    return new Store();
}

module.exports = {
    createStore: createStore
}

<|endoftext|>


<commit_before>var fs = require('fs')
var path = require('path')
var assign = require('deep-assign')
if (!process) {
  process = require('process') // > v0.4.0
}

var rc = require('rc-loader')
var repeatString = require('repeat-string')
var editorconfig = require('editorconfig')

var defaultIndentWidth = repeatString(' ', 2)

function loadStylelint(from) {
  var ret = {}
  var configNames = ['stylelint', 'stylelint.config']
  configNames.forEach(function (configName) {
    assign(ret, rc(configName, {}, { config: path.join(from, configName) }).rules)
  })
  return ret
}

function getIndentWidthFromEditorConfig(from, hasScss) {
  var config
  if (hasScss) {
    config = editorconfig.parseSync(path.resolve(process.cwd(), '*.scss'))
  } else {
    config = editorconfig.parseSync(path.resolve(process.cwd(), '*.css'))
  }
  if (config == undefined) {
    return defaultIndentWidth
  }
  switch (config.indent_style) {
    case 'tab':
      return '\t'
    case 'space':
      return repeatString(' ', config.indent_size)
    default:
      return defaultIndentWidth
  }
}

function isEmptyObject(obj) {
  for (var name in obj) {
    return false
  }
  return true
}

function hasScssInWorkingDir(wd) {
  var dirs = fs.readdirSync(wd)
  return dirs.some(function (dir) {
    return dir.match(/.*\.scss/)
  })
}

function newParmas(from) {
  var wd = path.dirname(from) || process.cwd()
  var params = {}

  var hasScss = hasScssInWorkingDir(wd)
  params.hasScss = hasScss

  var stylelintConfig = loadStylelint(wd)
  if (isEmptyObject(stylelintConfig)) {
    params.indentWidth = getIndentWidthFromEditorConfig(wd, hasScss)
    params.stylelint = {}
    return params
  }

  var indentaiton = stylelintConfig['indentation']
  switch (typeof indentaiton) {
    case 'string':
      params.indentWidth = '\t'
      break
    case 'number':
      params.indentWidth = repeatString(' ', indentaiton)
      break
    default:
      params.indentWidth = defaultIndentWidth
      break
  }
  params.stylelint = stylelintConfig
  return params
}

module.exports = newParmas
<commit_msg>Fix typo<commit_after>var fs = require('fs')
var path = require('path')
var assign = require('deep-assign')
if (!process) {
  process = require('process') // > v0.4.0
}

var rc = require('rc-loader')
var repeatString = require('repeat-string')
var editorconfig = require('editorconfig')

var defaultIndentWidth = repeatString(' ', 2)

function loadStylelint(from) {
  var ret = {}
  var configNames = ['stylelint', 'stylelint.config']
  configNames.forEach(function (configName) {
    assign(ret, rc(configName, {}, { config: path.join(from, configName) }).rules)
  })
  return ret
}

function getIndentWidthFromEditorConfig(from, hasScss) {
  var config
  if (hasScss) {
    config = editorconfig.parseSync(path.resolve(process.cwd(), '*.scss'))
  } else {
    config = editorconfig.parseSync(path.resolve(process.cwd(), '*.css'))
  }
  if (config == undefined) {
    return defaultIndentWidth
  }
  switch (config.indent_style) {
    case 'tab':
      return '\t'
    case 'space':
      return repeatString(' ', config.indent_size)
    default:
      return defaultIndentWidth
  }
}

function isEmptyObject(obj) {
  for (var name in obj) {
    return false
  }
  return true
}

function hasScssInWorkingDir(wd) {
  var dirs = fs.readdirSync(wd)
  return dirs.some(function (dir) {
    return dir.match(/.*\.scss/)
  })
}

function newParmas(from) {
  var wd = path.dirname(from) || process.cwd()
  var params = {}

  var hasScss = hasScssInWorkingDir(wd)
  params.hasScss = hasScss

  var stylelintConfig = loadStylelint(wd)
  if (isEmptyObject(stylelintConfig)) {
    params.indentWidth = getIndentWidthFromEditorConfig(wd, hasScss)
    params.stylelint = {}
    return params
  }

  var indentation = stylelintConfig['indentation']
  switch (typeof indentation) {
    case 'string':
      params.indentWidth = '\t'
      break
    case 'number':
      params.indentWidth = repeatString(' ', indentation)
      break
    default:
      params.indentWidth = defaultIndentWidth
      break
  }
  params.stylelint = stylelintConfig
  return params
}

module.exports = newParmas
<|endoftext|>


<commit_before>
var parsec = {};

!( function( parsec )
{
    'use strict';

    function Ok( item )
    {
        this.item = item;
    }

    function Err( errmsg )
    {
        this.errmsg = errmsg;
    }

    function ParseError( pos, msgs )
    {
        this.pos = pos;
        this.msgs = msgs;
    }

    ParseError.prototype.show = function()
    {
        return this.msgs.join( ', ' )
                + ' at'
                + ' line: ' + this.pos.line
                + ' column: ' + this.pos.column;
    };

    function mergeErrors( err, otherErr )
    {
        return new ParseError( err.pos, err.msgs.concat( otherErr.msgs ) );
    }

    var error = {

        unknown: function( state )
        {
            return new ParseError( state.pos, [ 'Error' ] );
        },

        expected: function( msg, pos )
        {
            return new ParseError( pos, [ 'Expected ' + msg ] );
        },

        unexpected: function( msg, pos )
        {
            return new ParseError( pos, [ 'Unxpected ' + msg ] );
        }
    };

    function incSourcePos( pos, tok )
    {
        if( tok === '\n' )
        {
            return { line: pos.line + 1, column: 1 };
        }

        return { line: pos.line, column: pos.column + 1 };
    }

    function repeat( n, item, accumulator )
    {
        if( typeof accumulator === 'undefined' )
        {
            accumulator = [];
        }

        accumulator.push( item );

        if( n > 1 )
        {
            return repeat( n - 1, item, accumulator );
        }
        else
        {
            return accumulator;
        }
    }

    function equal( to )
    {
        return function( that )
        {
            return that === to;
        }
    }

    function first( list )
    {
        return list[ 0 ];
    }

    function rest( list )
    {
        return list.slice( 1 );
    }

    var always = parsec.always = function always( x )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eok( x, state );
        };
    };

    var never = parsec.never = function never()
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eerr( error.unknown( state ) );
        };
    };

    var next = parsec.next = function next( p, q )
    {
        return bind( p, function() { return q; } );
    };

    var bind = parsec.bind = function bind( p, f )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            function pcok( item, state )
            {
                var q = f( item );

                return q( state, cok, cerr, cok, cerr );
            }

            function peok( item, state )
            {
                var q = f( item );

                return q( state, cok, cerr, eok, eerr );
            }

            return p( state, pcok, cerr, peok, eerr );
        };
    };

    var attempt = parsec.attempt = function attempt( p )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return p( state, cok, eerr, eok, eerr );
        };
    };

    var extract = parsec.extract = function extract( f )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eok( f( state ), state );
        };
    };

    var examine = parsec.examine = function examine()
    {
        return extract( function( s ) { return s; } );
    };

    var lineNo = parsec.lineNo = function lineNo()
    {
        return extract( function( s )
        {
            return s.pos.line;
        } );
    };

    var either = parsec.either = function either( p, q )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            function peerr( errFromP )
            {
                function qeerr( errFromQ )
                {
                    return eerr( mergeErrors( errFromP, errFromQ ) );
                }

                return q( state, cok, cerr, eok, qeerr );
            }

            return p( state, cok, cerr, eok, peerr );
        };
    };

    var token = parsec.token = function token( consume )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            var tok = first( state.input );

            if( tok  )
            {
                if( consume( tok ) )
                {
                    return cok( tok, { input: rest( state.input ), pos: incSourcePos( state.pos ) } );
                }
                else
                {
                    return eerr( error.unexpected( 'token "' + tok + '"', state.pos ) );
                }
            }
            else
            {
                return eerr( error.unexpected( 'end of input', state.pos ) );
            }
        };
    };

    var many = parsec.many = function many( p )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            function manyErr()
            {
                throw new Error(
                    'Combinator "*" is applied to a parser that accepts an empty string.'
                );
            }

            function safeP( state, cok, cerr, eok, eerr )
            {
                return p( state, cok, cerr, manyErr, eerr );
            }

            var many0 = bind( safeP, function many0Next( x )
            {
                return bind( many( safeP ), function many1Next( xs )
                {
                    return always( [ x ].concat( xs ).join( '' ) );
                } );
            } );

            var either0 = either( many0, always( '' ) );

            return either0( state, cok, cerr, eok, eerr );
        };
    };

    var times = parsec.times = function times( n, p )
    {
        if( n === 0 )
        {
            return always( [] );
        }

        return function( state, cok, cerr, eok, eerr )
        {
            function pcok( item, state )
            {
                var q = times( n - 1, p );

                function qcok( items, state )
                {
                    return cok( [ item ].concat( items ), state );
                }

                return q( state, qcok, cerr, qcok, eerr );
            }

            function peok( item, state )
            {
                return eok( repeat( n, item ), state );
            }

            return p( state, pcok, cerr, peok, eerr );
        };
    };

    var between = parsec.between = function between( open, close, p )
    {
        return bind( open, function()
        {
            return bind( p, function( x )
            {
                return bind( close, function()
                {
                    return always( x );
                } );
            } );
        } );
    };

    var lookahead = parsec.lookahead = function lookahead( p )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            var ok = function( item )
            {
                return eok( item, state );
            };

            return p( state, ok, cerr, eok, eerr );
        };
    };

    var choice = parsec.choice = function choice( /* parsers... */ )
    {
        var parsers = [].slice.call( arguments );

        if( !parsers.length )
        {
            return never();
        }

        var p = first( parsers );

        return either( p, choice.apply( null, rest( parsers ) ) );
    };

    var eof = parsec.eof = function eof()
    {
        return function( state, cok, cerr, eok, eerr )
        {
            if( !state.input.length )
            {
                return eok( null, state );
            }

            return eerr( error.expected( 'End of input', state.pos ) );
        };
    };

    var char = parsec.char = function char( c )
    {
        return token( equal( c ) );
    };

    var anyChar = parsec.anyChar = function anyChar()
    {
        return token( function( c )
        {
            return typeof c === 'string' && c.length === 1;
        } );
    };

    var anyString = parsec.anyString = function()
    {
        return parsec.many( parsec.anyChar() );
    };

    var digit = parsec.digit = function digit()
    {
        return token( function( d )
        {
            return !isNaN( +d );
        } );
    };

    var letter = parsec.letter = function letter()
    {
        return token( function( c )
        {
            return c.match( /[A-Za-z]/ );
        } );
    };

    var string = parsec.string = function string( s )
    {
        return s.split( '' ).map( char ).concat( always( s ) ).reduce( next );
    };

    var runParser = parsec.runParser = function runParser( p, state )
    {
        return p(
            state,
            function cok( item )
            {
                return new Ok( item );
            },
            function cerr( err )
            {
                return new Err( err.show() );
            },
            function eok( item )
            {
                return new Ok( item );
            },
            function eerr( err )
            {
                return new Err( err.show() );
            }
        );
    };

    var run = parsec.run = function run( p, input )
    {
        var result = runParser(
            p,
            {
                input: input,
                pos: {
                    line: 1,
                    column: 1
                }
            }
        );

        if( result instanceof Ok )
        {
            return result.item;
        }
        else if( result instanceof Err )
        {
            throw new Error( result.errmsg );
        }
    };

} )( parsec );

if( typeof exports !== 'undefined' )
{
    module.exports = parsec;
}
<commit_msg>Unique error messages.<commit_after>
var parsec = {};

!( function( parsec )
{
    'use strict';

    function Ok( item )
    {
        this.item = item;
    }

    function Err( errmsg )
    {
        this.errmsg = errmsg;
    }

    function ParseError( pos, msgs )
    {
        this.pos = pos;
        this.msgs = msgs;
    }

    ParseError.prototype.show = function()
    {
        return unique( this.msgs ).join( ', ' )
                + ' at'
                + ' line: ' + this.pos.line
                + ' column: ' + this.pos.column;
    };

    function mergeErrors( err, otherErr )
    {
        return new ParseError( err.pos, err.msgs.concat( otherErr.msgs ) );
    }

    var error = {

        unknown: function( state )
        {
            return new ParseError( state.pos, [ 'Error' ] );
        },

        expected: function( msg, pos )
        {
            return new ParseError( pos, [ 'Expected ' + msg ] );
        },

        unexpected: function( msg, pos )
        {
            return new ParseError( pos, [ 'Unxpected ' + msg ] );
        }
    };

    function incSourcePos( pos, tok )
    {
        if( tok === '\n' )
        {
            return { line: pos.line + 1, column: 1 };
        }

        return { line: pos.line, column: pos.column + 1 };
    }

    function repeat( n, item, accumulator )
    {
        if( typeof accumulator === 'undefined' )
        {
            accumulator = [];
        }

        accumulator.push( item );

        if( n > 1 )
        {
            return repeat( n - 1, item, accumulator );
        }
        else
        {
            return accumulator;
        }
    }

    function equal( to )
    {
        return function( that )
        {
            return that === to;
        }
    }

    function first( list )
    {
        return list[ 0 ];
    }

    function rest( list )
    {
        return list.slice( 1 );
    }

    // Just for the lulz
    function unique( list )
    {
        var sortedList = list.sort(),
            uniqueList = [],
            head;

        function unique0( list )
        {
            if( !list.length )
            {
                return uniqueList;
            }

            head = first( list );

            if( head !== first( rest( list ) ) )
            {
                uniqueList.push( head );
            }

            head = first( list );

            return unique0( rest( list ) );
        }

        return unique0( list );
    }

    var always = parsec.always = function always( x )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eok( x, state );
        };
    };

    var never = parsec.never = function never()
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eerr( error.unknown( state ) );
        };
    };

    var next = parsec.next = function next( p, q )
    {
        return bind( p, function() { return q; } );
    };

    var bind = parsec.bind = function bind( p, f )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            function pcok( item, state )
            {
                var q = f( item );

                return q( state, cok, cerr, cok, cerr );
            }

            function peok( item, state )
            {
                var q = f( item );

                return q( state, cok, cerr, eok, eerr );
            }

            return p( state, pcok, cerr, peok, eerr );
        };
    };

    // Will be a macro thanks to sweet.js
    var bind_ = parsec.bind_ = function bind_( bindings, f )
    {
        var result = [];

        function bind0()
        {
            var p = first( bindings );

            bindings = rest( bindings );

            if( bindings.length > 0 )
            {
                return bind( p, function( x )
                {
                    result.push( x );
                    return bind0();
                } );
            }
            else
            {
                return bind( p, function( x )
                {
                    result.push( x );
                    return f.apply( null, result );
                } );
            }
        }

        return bind0();
    };

    var attempt = parsec.attempt = function attempt( p )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return p( state, cok, eerr, eok, eerr );
        };
    };

    var extract = parsec.extract = function extract( f )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            return eok( f( state ), state );
        };
    };

    var examine = parsec.examine = function examine()
    {
        return extract( function( s ) { return s; } );
    };

    var lineNo = parsec.lineNo = function lineNo()
    {
        return extract( function( s )
        {
            return s.pos.line;
        } );
    };

    var either = parsec.either = function either( p, q )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            function peerr( errFromP )
            {
                function qeerr( errFromQ )
                {
                    return eerr( mergeErrors( errFromP, errFromQ ) );
                }

                return q( state, cok, cerr, eok, qeerr );
            }

            return p( state, cok, cerr, eok, peerr );
        };
    };

    var token = parsec.token = function token( consume )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            var tok = first( state.input );

            if( tok  )
            {
                if( consume( tok ) )
                {
                    return cok( tok, { input: rest( state.input ), pos: incSourcePos( state.pos ) } );
                }
                else
                {
                    return eerr( error.unexpected( 'token "' + tok + '"', state.pos ) );
                }
            }
            else
            {
                return eerr( error.unexpected( 'end of input', state.pos ) );
            }
        };
    };

    var many = parsec.many = function many( p )
    {
        function manyErr()
        {
            throw new Error(
                'Combinator "*" is applied to a parser that accepts an empty string.'
            );
        }

        function safeP( state, cok, cerr, eok, eerr )
        {
            return p( state, cok, cerr, manyErr, eerr );
        }

        var many0 = bind( safeP, function many0Next( x )
        {
            return bind( many( safeP ), function many1Next( xs )
            {
                return always( [ x ].concat( xs ).join( '' ) );
            } );
        } );

        return either( many0, always( '' ) );
    };

    var many1 = parsec.many1 = function many( p )
    {
        return bind( p, function( x )
        {
            return bind( many( p ), function( xs )
            {
                return always( [ x ].concat( xs ).join( '' ) );
            } );
        } );
    };

    var times = parsec.times = function times( n, p )
    {
        if( n === 0 )
        {
            return always( [] );
        }

        return function( state, cok, cerr, eok, eerr )
        {
            function pcok( item, state )
            {
                var q = times( n - 1, p );

                function qcok( items, state )
                {
                    return cok( [ item ].concat( items ), state );
                }

                return q( state, qcok, cerr, qcok, eerr );
            }

            function peok( item, state )
            {
                return eok( repeat( n, item ), state );
            }

            return p( state, pcok, cerr, peok, eerr );
        };
    };

    var between = parsec.between = function between( open, close, p )
    {
        return bind( open, function()
        {
            return bind( p, function( x )
            {
                return bind( close, function()
                {
                    return always( x );
                } );
            } );
        } );
    };

    var lookahead = parsec.lookahead = function lookahead( p )
    {
        return function( state, cok, cerr, eok, eerr )
        {
            var ok = function( item )
            {
                return eok( item, state );
            };

            return p( state, ok, cerr, eok, eerr );
        };
    };

    var choice = parsec.choice = function choice( /* parsers... */ )
    {
        var parsers = [].slice.call( arguments );

        if( !parsers.length )
        {
            return never();
        }

        var p = first( parsers );

        return either( p, choice.apply( null, rest( parsers ) ) );
    };

    var eof = parsec.eof = function eof()
    {
        return function( state, cok, cerr, eok, eerr )
        {
            if( !state.input.length )
            {
                return eok( null, state );
            }

            return eerr( error.expected( 'End of input', state.pos ) );
        };
    };

    var char = parsec.char = function char( c )
    {
        return token( equal( c ) );
    };

    var anyChar = parsec.anyChar = function anyChar()
    {
        return token( function( c )
        {
            return typeof c === 'string' && c.length === 1;
        } );
    };

    var anyString = parsec.anyString = function()
    {
        return parsec.many( parsec.anyChar() );
    };

    var digit = parsec.digit = function digit()
    {
        return token( function( d )
        {
            return !isNaN( +d );
        } );
    };

    var number = parsec.number = function number()
    {
        return many( digit() );
    };

    var letter = parsec.letter = function letter()
    {
        return token( function( c )
        {
            return c.match( /[A-Za-z]/ );
        } );
    };

    var string = parsec.string = function string( s )
    {
        return s.split( '' ).map( char ).concat( always( s ) ).reduce( next );
    };

    var runParser = parsec.runParser = function runParser( p, state )
    {
        return p(
            state,
            function cok( item )
            {
                return new Ok( item );
            },
            function cerr( err )
            {
                return new Err( err.show() );
            },
            function eok( item )
            {
                return new Ok( item );
            },
            function eerr( err )
            {
                return new Err( err.show() );
            }
        );
    };

    var run = parsec.run = function run( p, input )
    {
        var result = runParser(
            p,
            {
                input: input,
                pos: {
                    line: 1,
                    column: 1
                }
            }
        );

        if( result instanceof Ok )
        {
            return result.item;
        }
        else if( result instanceof Err )
        {
            throw new Error( result.errmsg );
        }
    };

} )( parsec );

if( typeof exports !== 'undefined' )
{
    module.exports = parsec;
}
<|endoftext|>


<commit_before>/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */

var jpeg = require('./jpeg'),
	exif = require('./exif'),
	simplify = require('./simplify');

function ExifResult(startMarker, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset) {
	this.startMarker = startMarker;
	this.tags = tags;
	this.imageSize = imageSize;
	this.thumbnailOffset = thumbnailOffset;
	this.thumbnailLength = thumbnailLength;
	this.thumbnailType = thumbnailType;
	this.app1Offset = app1Offset;
}

ExifResult.prototype = {
	hasThumbnail: function(mime) {
		if(!this.thumbnailOffset || !this.thumbnailLength) {
			return false;
		}
		if(typeof mime !== 'string') {
			return true;
		}
		if(mime.toLowerCase().trim() === 'image/jpeg') {
			return this.thumbnailType === 6;
		}
		if(mime.toLowerCase().trim() === 'image/tiff') {
			return this.thumbnailType === 1;
		}
		return false;
	},
	getThumbnailOffset: function() {
		return this.app1Offset + 6 + this.thumbnailOffset;
	},
	getThumbnailLength: function() {
		return this.thumbnailLength;
	},
	getThumbnailBuffer: function() {
		return this._getThumbnailStream().nextBuffer(this.thumbnailLength);
	},
	_getThumbnailStream: function() {
		return this.startMarker.openWithOffset(this.getThumbnailOffset());
	},
	getImageSize: function() {
		return this.imageSize;
	},
	getThumbnailSize: function() {
		var stream = this._getThumbnailStream(), size;
		jpeg.parseSections(stream, function(sectionType, sectionStream) {
			if(jpeg.getSectionName(sectionType).name === 'SOF') {
				size = jpeg.getSizeFromSOFSection(sectionStream);
			}
		});
		return size;
	}
};

function Parser(stream) {
	this.stream = stream;
	this.flags = {
		readBinaryTags: false,
		resolveTagNames: true,
		simplifyValues: true,
		imageSize: true,
		hidePointers: true,
		returnTags: true
	};
}

Parser.prototype = {
	enableBinaryFields: function(enable) {
		this.flags.readBinaryTags = !!enable;
		return this;
	},
	enablePointers: function(enable) {
		this.flags.hidePointers = !enable;
		return this;
	},
	enableTagNames: function(enable) {
		this.flags.resolveTagNames = !!enable;
		return this;
	},
	enableImageSize: function(enable) {
		this.flags.imageSize = !!enable;
		return this;
	},
	enableReturnTags: function(enable) {
		this.flags.returnTags = !!enable;
		return this;
	},
	enableSimpleValues: function(enable) {
		this.flags.simplifyValues = !!enable;
		return this;
	},
	parse: function() {
		var start = this.stream.mark(),
			stream = start.openWithOffset(0),
			flags = this.flags,
			tags,
			imageSize,
			thumbnailOffset,
			thumbnailLength,
			thumbnailType,
			app1Offset,
			tagNames,
			getTagValue, setTagValue;
		if(flags.resolveTagNames) {
			tagNames = require('./exif-tags');
		}
		if(flags.resolveTagNames) {
			tags = {};
			getTagValue = function(t) {
				return tags[t.name];
			};
			setTagValue = function(t, value) {
				tags[t.name] = value;
			};
		} else {
			tags = [];
			getTagValue = function(t) {
				var i;
				for(i = 0; i < tags.length; ++i) {
					if(tags[i].type === t.type && tags[i].section === t.section) {
						return tags.value;
					}
				}
			};
			setTagValue = function(t, value) {
				var i;
				for(i = 0; i < tags.length; ++i) {
					if(tags[i].type === t.type && tags[i].section === t.section) {
						tags.value = value;
						return;
					}
				}
			};
		}

		jpeg.parseSections(stream, function(sectionType, sectionStream) {
			var validExifHeaders, sectionOffset = sectionStream.offsetFrom(start);
			if(sectionType === 0xE1) {
				validExifHeaders = exif.parseTags(sectionStream, function(ifdSection, tagType, value, format) {
					//ignore binary fields if disabled
					if(!flags.readBinaryTags && format === 7) {
						return;
					}

					if(tagType === 0x0201) {
						thumbnailOffset = value[0];
						if(flags.hidePointers) {return;}
					} else if(tagType === 0x0202) {
						thumbnailLength = value[0];
						if(flags.hidePointers) {return;}
					} else if(tagType === 0x0103) {
						thumbnailType = value[0];
						if(flags.hidePointers) {return;}
					}
					//if flag is set to not store tags, return here after storing pointers
					if(!flags.returnTags) {
						return;
					}

					if(flags.simplifyValues) {
						value = simplify.simplifyValue(value, format);
					}
					if(flags.resolveTagNames) {
						var sectionTagNames = ifdSection === exif.GPSIFD ? tagNames.gps : tagNames.exif;
						var name = sectionTagNames[tagType];
						if(!name) {
							name = tagNames.exif[tagType];
						}
						if (!tags[name]) {
							tags[name] = value;
						}
					} else {
						tags.push({
							section: ifdSection,
							type: tagType,
							value: value
						});
					}
				});
				if(validExifHeaders) {
					app1Offset = sectionOffset;
				}
			}
			else if(flags.imageSize && jpeg.getSectionName(sectionType).name === 'SOF') {
				imageSize = jpeg.getSizeFromSOFSection(sectionStream);
			}
		});

		if(flags.simplifyValues) {
			simplify.castDegreeValues(getTagValue, setTagValue);
			simplify.castDateValues(getTagValue, setTagValue);
		}

		return new ExifResult(start, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset);
	}
};

module.exports = Parser;
<commit_msg>use hasOwnProperty to avoid coercing the value<commit_after>/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */

var jpeg = require('./jpeg'),
	exif = require('./exif'),
	simplify = require('./simplify');

function ExifResult(startMarker, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset) {
	this.startMarker = startMarker;
	this.tags = tags;
	this.imageSize = imageSize;
	this.thumbnailOffset = thumbnailOffset;
	this.thumbnailLength = thumbnailLength;
	this.thumbnailType = thumbnailType;
	this.app1Offset = app1Offset;
}

ExifResult.prototype = {
	hasThumbnail: function(mime) {
		if(!this.thumbnailOffset || !this.thumbnailLength) {
			return false;
		}
		if(typeof mime !== 'string') {
			return true;
		}
		if(mime.toLowerCase().trim() === 'image/jpeg') {
			return this.thumbnailType === 6;
		}
		if(mime.toLowerCase().trim() === 'image/tiff') {
			return this.thumbnailType === 1;
		}
		return false;
	},
	getThumbnailOffset: function() {
		return this.app1Offset + 6 + this.thumbnailOffset;
	},
	getThumbnailLength: function() {
		return this.thumbnailLength;
	},
	getThumbnailBuffer: function() {
		return this._getThumbnailStream().nextBuffer(this.thumbnailLength);
	},
	_getThumbnailStream: function() {
		return this.startMarker.openWithOffset(this.getThumbnailOffset());
	},
	getImageSize: function() {
		return this.imageSize;
	},
	getThumbnailSize: function() {
		var stream = this._getThumbnailStream(), size;
		jpeg.parseSections(stream, function(sectionType, sectionStream) {
			if(jpeg.getSectionName(sectionType).name === 'SOF') {
				size = jpeg.getSizeFromSOFSection(sectionStream);
			}
		});
		return size;
	}
};

function Parser(stream) {
	this.stream = stream;
	this.flags = {
		readBinaryTags: false,
		resolveTagNames: true,
		simplifyValues: true,
		imageSize: true,
		hidePointers: true,
		returnTags: true
	};
}

Parser.prototype = {
	enableBinaryFields: function(enable) {
		this.flags.readBinaryTags = !!enable;
		return this;
	},
	enablePointers: function(enable) {
		this.flags.hidePointers = !enable;
		return this;
	},
	enableTagNames: function(enable) {
		this.flags.resolveTagNames = !!enable;
		return this;
	},
	enableImageSize: function(enable) {
		this.flags.imageSize = !!enable;
		return this;
	},
	enableReturnTags: function(enable) {
		this.flags.returnTags = !!enable;
		return this;
	},
	enableSimpleValues: function(enable) {
		this.flags.simplifyValues = !!enable;
		return this;
	},
	parse: function() {
		var start = this.stream.mark(),
			stream = start.openWithOffset(0),
			flags = this.flags,
			tags,
			imageSize,
			thumbnailOffset,
			thumbnailLength,
			thumbnailType,
			app1Offset,
			tagNames,
			getTagValue, setTagValue;
		if(flags.resolveTagNames) {
			tagNames = require('./exif-tags');
		}
		if(flags.resolveTagNames) {
			tags = {};
			getTagValue = function(t) {
				return tags[t.name];
			};
			setTagValue = function(t, value) {
				tags[t.name] = value;
			};
		} else {
			tags = [];
			getTagValue = function(t) {
				var i;
				for(i = 0; i < tags.length; ++i) {
					if(tags[i].type === t.type && tags[i].section === t.section) {
						return tags.value;
					}
				}
			};
			setTagValue = function(t, value) {
				var i;
				for(i = 0; i < tags.length; ++i) {
					if(tags[i].type === t.type && tags[i].section === t.section) {
						tags.value = value;
						return;
					}
				}
			};
		}

		jpeg.parseSections(stream, function(sectionType, sectionStream) {
			var validExifHeaders, sectionOffset = sectionStream.offsetFrom(start);
			if(sectionType === 0xE1) {
				validExifHeaders = exif.parseTags(sectionStream, function(ifdSection, tagType, value, format) {
					//ignore binary fields if disabled
					if(!flags.readBinaryTags && format === 7) {
						return;
					}

					if(tagType === 0x0201) {
						thumbnailOffset = value[0];
						if(flags.hidePointers) {return;}
					} else if(tagType === 0x0202) {
						thumbnailLength = value[0];
						if(flags.hidePointers) {return;}
					} else if(tagType === 0x0103) {
						thumbnailType = value[0];
						if(flags.hidePointers) {return;}
					}
					//if flag is set to not store tags, return here after storing pointers
					if(!flags.returnTags) {
						return;
					}

					if(flags.simplifyValues) {
						value = simplify.simplifyValue(value, format);
					}
					if(flags.resolveTagNames) {
						var sectionTagNames = ifdSection === exif.GPSIFD ? tagNames.gps : tagNames.exif;
						var name = sectionTagNames[tagType];
						if(!name) {
							name = tagNames.exif[tagType];
						}
						if (tags.hasOwnProperty(name)) {
							tags[name] = value;
						}
					} else {
						tags.push({
							section: ifdSection,
							type: tagType,
							value: value
						});
					}
				});
				if(validExifHeaders) {
					app1Offset = sectionOffset;
				}
			}
			else if(flags.imageSize && jpeg.getSectionName(sectionType).name === 'SOF') {
				imageSize = jpeg.getSizeFromSOFSection(sectionStream);
			}
		});

		if(flags.simplifyValues) {
			simplify.castDegreeValues(getTagValue, setTagValue);
			simplify.castDateValues(getTagValue, setTagValue);
		}

		return new ExifResult(start, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset);
	}
};

module.exports = Parser;
<|endoftext|>


<commit_before>var _ = require('lodash'),
  utils = require('./utils');

var parseVariable;

function escapeRegExp(str) {
  return str.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
}

exports.parse = function (source, opts) {
  source = source.replace(/\r\n/g, '\n');
  // Split the template source based on variable, tag, and comment blocks
  // /(\{\{.*?\}\}|\{\%.*?\%\}|\{\#[^.*?\#\})/
  var tagOpen = opts.tagControls[0],
    tagClose = opts.tagControls[1],
    tagStrip = new RegExp('^' + escapeRegExp(tagOpen) + '\\s*|\\s*' + escapeRegExp(tagClose) + '$', 'g'),
    varOpen = opts.varControls[0],
    varClose = opts.varControls[1],
    varStrip = new RegExp('^' + escapeRegExp(varOpen) + '\\s*|\\s*' + escapeRegExp(varClose) + '$', 'g'),
    cmtOpen = opts.cmtControls[0],
    cmtClose = opts.cmtControls[1],
    splitter = new RegExp(
      '(' +
        escapeRegExp(tagOpen) + '.*?' + escapeRegExp(tagClose) + '|' +
        escapeRegExp(varOpen) + '.*?' + escapeRegExp(varClose) + '|' +
        escapeRegExp(cmtOpen) + '.*?' + escapeRegExp(cmtClose) +
        ')'
    ),
    tokens = [],
    line = 1 + source.match(/^\n*/)[0].length,
    stack = [];

  function parseVariable(str) {
    if (!str) {
      return;
    }

    return {
      compile: function () {
        return '_output += ' + str + ';\n';
      }
    };
  }
  exports.parseVariable = parseVariable;

  function parseTag(str, tags) {
    if (!str) {
      return;
    }

    var chunks, tagName, tag, args, last;

    if (utils.startsWith(str, 'end')) {
      last = _.last(stack);
      if (last.name === str.replace(/^end/, '') && last.ends) {
        stack.pop();
        return;
      }

      throw new Error('Unexpected tag ' + str + '.');
    }

    chunks = str.split(/\s+(.+)?/);
    tagName = chunks[0];
    tag = tags[tagName];
    args = tag.parse(chunks[1]);

    return {
      compile: tag.compile,
      setup: '',
      teardown: '',
      args: args,
      content: [],
      ends: tag.ends,
      name: tagName
    };
  }

  source = _.without(utils.strip(source).split(splitter), '');

  _.each(source, function (chunk) {
    var token;

    if (utils.startsWith(chunk, varOpen) && utils.endsWith(chunk, varClose)) {
      token = parseVariable(chunk.replace(varStrip, ''));
    } else if (utils.startsWith(chunk, tagOpen) && utils.endsWith(chunk, tagClose)) {
      token = parseTag(chunk.replace(tagStrip, ''), opts.tags, stack);
    } else if (!utils.startsWith(chunk, cmtOpen) && !utils.endsWith(chunk, cmtClose)) {
      token = chunk;
    }

    if (!token) {
      return;
    }

    if (stack.length) {
      stack[stack.length - 1].content = token;
    } else {
      tokens.push(token);
    }

    if (token.name) {
      stack.push(token);
    }
  });

  return tokens;
};

exports.compile = function (tokens) {
  var out = '';
  _.each(tokens, function (token, index) {
    if (typeof token === 'string') {
      out += '_output += "' + token + '";\n';
      return;
    }

    out += token.compile(exports, token.setup, token.args, token.content, token.teardown);
  });

  return out;
};
<commit_msg>reduce escape redundancy<commit_after>var _ = require('lodash'),
  utils = require('./utils');

var parseVariable;

function escapeRegExp(str) {
  return str.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
}

exports.parse = function (source, opts) {
  source = source.replace(/\r\n/g, '\n');
  // Split the template source based on variable, tag, and comment blocks
  // /(\{\{.*?\}\}|\{\%.*?\%\}|\{\#[^.*?\#\})/
  var tagOpen = opts.tagControls[0],
    tagClose = opts.tagControls[1],
    varOpen = opts.varControls[0],
    varClose = opts.varControls[1],
    escapedTagOpen = escapeRegExp(tagOpen),
    escapedTagClose = escapeRegExp(tagClose),
    escapedVarOpen = escapeRegExp(varOpen),
    escapedVarClosed = escapeRegExp(varClose),
    tagStrip = new RegExp('^' + escapedTagOpen + '\\s*|\\s*' + escapedTagClose + '$', 'g'),
    varStrip = new RegExp('^' + escapedVarOpen + '\\s*|\\s*' + escapedVarClosed + '$', 'g'),
    cmtOpen = opts.cmtControls[0],
    cmtClose = opts.cmtControls[1],
    splitter = new RegExp(
      '(' +
        escapedTagOpen + '.*?' + escapedTagClose + '|' +
        escapedVarOpen + '.*?' + escapedVarClosed + '|' +
        escapeRegExp(cmtOpen) + '.*?' + escapeRegExp(cmtClose) +
        ')'
    ),
    tokens = [],
    line = 1 + source.match(/^\n*/)[0].length,
    stack = [];

  function parseVariable(str) {
    if (!str) {
      return;
    }

    return {
      compile: function () {
        return '_output += ' + str + ';\n';
      }
    };
  }
  exports.parseVariable = parseVariable;

  function parseTag(str, tags) {
    if (!str) {
      return;
    }

    var chunks, tagName, tag, args, last;

    if (utils.startsWith(str, 'end')) {
      last = _.last(stack);
      if (last.name === str.replace(/^end/, '') && last.ends) {
        stack.pop();
        return;
      }

      throw new Error('Unexpected tag ' + str + '.');
    }

    chunks = str.split(/\s+(.+)?/);
    tagName = chunks[0];
    tag = tags[tagName];
    args = tag.parse(chunks[1]);

    return {
      compile: tag.compile,
      setup: '',
      teardown: '',
      args: args,
      content: [],
      ends: tag.ends,
      name: tagName
    };
  }

  source = _.without(utils.strip(source).split(splitter), '');

  _.each(source, function (chunk) {
    var token;

    if (utils.startsWith(chunk, varOpen) && utils.endsWith(chunk, varClose)) {
      token = parseVariable(chunk.replace(varStrip, ''));
    } else if (utils.startsWith(chunk, tagOpen) && utils.endsWith(chunk, tagClose)) {
      token = parseTag(chunk.replace(tagStrip, ''), opts.tags, stack);
    } else if (!utils.startsWith(chunk, cmtOpen) && !utils.endsWith(chunk, cmtClose)) {
      token = chunk;
    }

    if (!token) {
      return;
    }

    if (stack.length) {
      stack[stack.length - 1].content = token;
    } else {
      tokens.push(token);
    }

    if (token.name) {
      stack.push(token);
    }
  });

  return tokens;
};

exports.compile = function (tokens) {
  var out = '';
  _.each(tokens, function (token, index) {
    if (typeof token === 'string') {
      out += '_output += "' + token + '";\n';
      return;
    }

    out += token.compile(exports, token.setup, token.args, token.content, token.teardown);
  });

  return out;
};
<|endoftext|>