<commit_before>'use strict';

var isPlainObj = require('is-plain-object');
var omit = require('object-omit');
var isObj = require('isobject');
var forIn = require('for-in');
var typeOf = require('kind-of');
var get = require('get-value');

/**
 * Specify a fallback value to use when the desired
 * value is undefined. Note that undefined variables
 * that are _not object properties_ with throw an error.
 *
 * ```js
 * // when `title` is undefined, use the generic `site.title`
 * <%= fallback(page.title, site.title) %>
 * ```
 *
 * @param  {*} `a` The desired value.
 * @param  {*} `b` The fallback ("default") value
 * @return {*} Either `a` or `b`
 */

exports.fallback = function fallback(a, b) {
  return a != null ? a : b;
};

/**
 * Stringify an object using `JSON.stringify()`.
 *
 * ```js
 * <%= stringify({a: "a"}) %>
 * //=> '{"a":"a"}'
 * ``
 *
 * @param  {Object} `object`
 * @return {String}
 * @api public
 */

exports.stringify = function stringify(o) {
  return JSON.stringify(o);
};

/**
 * Parse a string into an object using `JSON.parse()`.
 *
 * ```js
 * <%= parse(\'{"foo":"bar"}\')["foo"] %>
 * //=> 'bar'
 * ```
 *
 * @param  {String} `str` The string to parse.
 * @return {Object} The parsed object.
 * @api public
 */

exports.parse = function parse(str) {
  return JSON.parse(str);
};

/**
 * Use property paths (`a.b.c`) get a nested value from an object.
 *
 * ```js
 * <%= get({a: {b: 'c'}}, 'a.b') %>
 * //=> 'c'
 * ```
 *
 * @param  {Object} `object`
 * @param  {String} `path` Dot notation for the property to get.
 * @return {String}
 * @api public
 */

exports.get = function get_(o, prop) {
  return get.apply(get, arguments);
};

/**
 * Returns an array of keys from the given `object`.
 *
 * ```js
 * <%= keys({a: 'b', c: 'd'}) %>
 * //=> '["a", "c"]'
 * ```
 *
 * @param  {Object} `object`
 * @return {Array} Keys from `object`
 * @api public
 */

exports.keys = function keys(obj) {
  return Object.keys(obj);
};

/**
 * Return true if the given `value` is an object, and
 * not `null` or an array.
 *
 * ```js
 * <%= isObject(['a', 'b', 'c']) %>
 * //=> 'false'
 *
 * <%= isObject({a: 'b'}) %>
 * //=> 'true'
 * ```
 *
 * @param  {Object} `value` The value to check.
 * @return {Boolean}
 * @api public
 */

exports.isObject = function isObject(val) {
  return isObj(val);
};

/**
 * Return true if the given `value` is a plain object.
 *
 * ```js
 * <%= isPlainObject(['a', 'b', 'c']) %>
 * //=> 'false'
 *
 * <%= isPlainObject({a: 'b'}) %>
 * //=> 'true'
 *
 * <%= isPlainObject(/foo/g) %>
 * //=> 'false'
 * ```
 *
 * @param  {Object} `value` The value to check.
 * @return {Boolean}
 * @api public
 */

exports.isPlainObject = function isPlainObject(val) {
  return isPlainObj(val);
};

/**
 * Return true if `key` is an own, enumerable property
 * of the given `obj`.
 *
 * @param  {Object} `object`
 * @param  {String} `key`
 * @return {Boolean}
 * @api public
 */

exports.hasOwn = function hasOwn(o, key) {
  return {}.hasOwnProperty.call(o, key);
};

/**
 * Return a copy of `object` exclusing the given `keys`.
 *
 * ```js
 * <%= omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c']) %>
 * //=> '{b: "b"}'
 * ```
 *
 * @param  {Object} `object` Object with keys to omit.
 * @param  {String} `keys` Keys to omit.
 * @return {Boolean}
 * @api public
 */

exports.omit = function(o, keys) {
  return omit.apply(omit, arguments);
};

/**
 * Iterate over the own and inherited enumerable properties
 * of an object, and return an object with properties that
 * evaluate to true from the callback. Exit early by returning
 * `false`.
 *
 * @param  {Object} `object` The object to iterate over.
 * @param  {Function} `fn` Callback function.
 * @param  {Object} `thisArg` Context in which to execute the callback.
 * @return {Object}
 * @api public
 */

exports.forIn = function forIn(o, fn, thisArg) {
  for (var key in o) {
    if (fn.call(thisArg, o[key], key, o) === false) {
      break;
    }
  }
};

/**
 * Iterate over the own enumerable properties of an object, and
 * return an object with properties that evaluate to true from
 * the callback. Exit early by returning `false`
 *
 * @param  {Object} `object` The object to iterate over.
 * @param  {Function} `fn` Callback function.
 * @param  {Object} `thisArg` Context in which to execute the callback.
 * @return {Object}
 * @api public
 */

exports.forOwn = function forOwn(o, fn, thisArg) {
  forIn(o, function (val, key) {
    if (exports.hasOwn(o, key)) {
      return fn.call(thisArg, o[key], key, o);
    }
  });
};

/**
 * Extend `o` with properties of other `objects`.
 *
 * @param  {Object} `o` The target object. Pass an empty object to shallow clone.
 * @param  {Object} `objects`
 * @return {Object}
 * @api public
 */

exports.extend = function extend(o) {
  if (typeOf(o) !== 'object') { return ''; }
  var args = arguments;
  var len = args.length - 1;

  for (var i = 0; i < len; i++) {
    var obj = args[i + 1];

    if (typeOf(obj) === 'object') {
      for (var key in obj) {
        if (exports.hasOwn(obj, key)) {
          o[key] = obj[key];
        }
      }
    }
  }
  return o;
};

/**
 * Recursively combine the properties of `o` with the
 * properties of other `objects`.
 *
 * @param  {Object} `o` The target object. Pass an empty object to shallow clone.
 * @param  {Object} `objects`
 * @return {Object}
 * @api public
 */

exports.merge = function merge(o) {
  if (typeOf(o) !== 'object') { return ''; }
  var args = arguments;
  var len = args.length - 1;

  for (var i = 0; i < len; i++) {
    var obj = args[i + 1];

    if (typeOf(obj) === 'object') {
      for (var key in obj) {
        if (exports.hasOwn(obj, key)) {
          if (typeOf(obj[key]) === 'object') {
            o[key] = exports.merge(o[key], obj[key]);
          } else {
            o[key] = obj[key];
          }
        }
      }
    }
  }
  return o;
};
<commit_msg>fix example<commit_after>'use strict';

var isPlainObj = require('is-plain-object');
var omit = require('object-omit');
var isObj = require('isobject');
var forIn = require('for-in');
var typeOf = require('kind-of');
var get = require('get-value');

/**
 * Specify a fallback value to use when the desired
 * value is undefined. Note that undefined variables
 * that are _not object properties_ with throw an error.
 *
 * ```js
 * // when `title` is undefined, use the generic `site.title`
 * <%= fallback(page.title, site.title) %>
 * ```
 *
 * @param  {*} `a` The desired value.
 * @param  {*} `b` The fallback ("default") value
 * @return {*} Either `a` or `b`
 */

exports.fallback = function fallback(a, b) {
  return a != null ? a : b;
};

/**
 * Stringify an object using `JSON.stringify()`.
 *
 * ```js
 * <%= stringify({a: "a"}) %>
 * //=> '{"a":"a"}'
 * ``
 *
 * @param  {Object} `object`
 * @return {String}
 * @api public
 */

exports.stringify = function stringify(o) {
  return JSON.stringify(o);
};

/**
 * Parse a string into an object using `JSON.parse()`.
 *
 * ```js
 * <%= parse('{"foo":"bar"}')["foo"] %>
 * //=> 'bar'
 * ```
 *
 * @param  {String} `str` The string to parse.
 * @return {Object} The parsed object.
 * @api public
 */

exports.parse = function parse(str) {
  return JSON.parse(str);
};

/**
 * Use property paths (`a.b.c`) get a nested value from an object.
 *
 * ```js
 * <%= get({a: {b: 'c'}}, 'a.b') %>
 * //=> 'c'
 * ```
 *
 * @param  {Object} `object`
 * @param  {String} `path` Dot notation for the property to get.
 * @return {String}
 * @api public
 */

exports.get = function get_(o, prop) {
  return get.apply(get, arguments);
};

/**
 * Returns an array of keys from the given `object`.
 *
 * ```js
 * <%= keys({a: 'b', c: 'd'}) %>
 * //=> '["a", "c"]'
 * ```
 *
 * @param  {Object} `object`
 * @return {Array} Keys from `object`
 * @api public
 */

exports.keys = function keys(obj) {
  return Object.keys(obj);
};

/**
 * Return true if the given `value` is an object, and
 * not `null` or an array.
 *
 * ```js
 * <%= isObject(['a', 'b', 'c']) %>
 * //=> 'false'
 *
 * <%= isObject({a: 'b'}) %>
 * //=> 'true'
 * ```
 *
 * @param  {Object} `value` The value to check.
 * @return {Boolean}
 * @api public
 */

exports.isObject = function isObject(val) {
  return isObj(val);
};

/**
 * Return true if the given `value` is a plain object.
 *
 * ```js
 * <%= isPlainObject(['a', 'b', 'c']) %>
 * //=> 'false'
 *
 * <%= isPlainObject({a: 'b'}) %>
 * //=> 'true'
 *
 * <%= isPlainObject(/foo/g) %>
 * //=> 'false'
 * ```
 *
 * @param  {Object} `value` The value to check.
 * @return {Boolean}
 * @api public
 */

exports.isPlainObject = function isPlainObject(val) {
  return isPlainObj(val);
};

/**
 * Return true if `key` is an own, enumerable property
 * of the given `obj`.
 *
 * @param  {Object} `object`
 * @param  {String} `key`
 * @return {Boolean}
 * @api public
 */

exports.hasOwn = function hasOwn(o, key) {
  return {}.hasOwnProperty.call(o, key);
};

/**
 * Return a copy of `object` exclusing the given `keys`.
 *
 * ```js
 * <%= omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c']) %>
 * //=> '{b: "b"}'
 * ```
 *
 * @param  {Object} `object` Object with keys to omit.
 * @param  {String} `keys` Keys to omit.
 * @return {Boolean}
 * @api public
 */

exports.omit = function(o, keys) {
  return omit.apply(omit, arguments);
};

/**
 * Iterate over the own and inherited enumerable properties
 * of an object, and return an object with properties that
 * evaluate to true from the callback. Exit early by returning
 * `false`.
 *
 * @param  {Object} `object` The object to iterate over.
 * @param  {Function} `fn` Callback function.
 * @param  {Object} `thisArg` Context in which to execute the callback.
 * @return {Object}
 * @api public
 */

exports.forIn = function forIn(o, fn, thisArg) {
  for (var key in o) {
    if (fn.call(thisArg, o[key], key, o) === false) {
      break;
    }
  }
};

/**
 * Iterate over the own enumerable properties of an object, and
 * return an object with properties that evaluate to true from
 * the callback. Exit early by returning `false`
 *
 * @param  {Object} `object` The object to iterate over.
 * @param  {Function} `fn` Callback function.
 * @param  {Object} `thisArg` Context in which to execute the callback.
 * @return {Object}
 * @api public
 */

exports.forOwn = function forOwn(o, fn, thisArg) {
  forIn(o, function (val, key) {
    if (exports.hasOwn(o, key)) {
      return fn.call(thisArg, o[key], key, o);
    }
  });
};

/**
 * Extend `o` with properties of other `objects`.
 *
 * @param  {Object} `o` The target object. Pass an empty object to shallow clone.
 * @param  {Object} `objects`
 * @return {Object}
 * @api public
 */

exports.extend = function extend(o) {
  if (typeOf(o) !== 'object') { return ''; }
  var args = arguments;
  var len = args.length - 1;

  for (var i = 0; i < len; i++) {
    var obj = args[i + 1];

    if (typeOf(obj) === 'object') {
      for (var key in obj) {
        if (exports.hasOwn(obj, key)) {
          o[key] = obj[key];
        }
      }
    }
  }
  return o;
};

/**
 * Recursively combine the properties of `o` with the
 * properties of other `objects`.
 *
 * @param  {Object} `o` The target object. Pass an empty object to shallow clone.
 * @param  {Object} `objects`
 * @return {Object}
 * @api public
 */

exports.merge = function merge(o) {
  if (typeOf(o) !== 'object') { return ''; }
  var args = arguments;
  var len = args.length - 1;

  for (var i = 0; i < len; i++) {
    var obj = args[i + 1];

    if (typeOf(obj) === 'object') {
      for (var key in obj) {
        if (exports.hasOwn(obj, key)) {
          if (typeOf(obj[key]) === 'object') {
            o[key] = exports.merge(o[key], obj[key]);
          } else {
            o[key] = obj[key];
          }
        }
      }
    }
  }
  return o;
};
<|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;
    },
}

var exproperators = {
    $eq: function (values) { return values[0] == values[1]; },
    $ne: function (values) { return values[0] != values[1]; },
    $lt: function (values) { return values[0] < values[1]; },
    $lte: function (values) { return values[0] <= values[1]; },
    $gt: function (values) { return values[0] > values[1]; },
    $gte: function (values) { return values[0] >= values[1]; },
    $substr: function (values) { return values[0].substring(values[1], values[2]); },
    $concat: function (values) { var result = ''; for (var n in values) result += values[n]; return result; },
}

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)
        values = find(values, spec.$match);

    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 && spec.$sort[name] !== true;

		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];
        }

        for (var n in fields) {
            var fldvalue = fields[n];

            if (fldvalue === 1 || fldvalue === -1 || fldvalue === true || fldvalue === false)
                continue;

            if (typeof fldvalue === 'object')
                cloned[n] = evaluate(item, fldvalue);
            else
                cloned[n] = fldvalue;
        }
    }

    return cloned;
}

function evaluate(data, expr) {
    for (var n in expr)
        if (exproperators[n]) {
            var vals = expr[n];
            var values = [];

            vals.forEach(function (val) {
                values.push(getValue(data, val));
            });

            return exproperators[n](values);
        }

    if (expr.$cmp) {
        var value1 = getValue(data, expr.$cmp[0]);
        var value2 = getValue(data, expr.$cmp[1]);

        if (value1 < value2)
            return -1;

        if (value1 > value2)
            return 1;

        return 0;
    }
    if (expr.$strcasecmp) {
        var value1 = getValue(data, expr.$strcasecmp[0]).toLowerCase();
        var value2 = getValue(data, expr.$strcasecmp[1]).toLowerCase();

        if (value1 < value2)
            return -1;

        if (value1 > value2)
            return 1;

        return 0;
    }
    if (expr.$toLower) {
        var result = getValue(data, expr.$toLower);

        return result.toLowerCase();
    }
    if (expr.$toUpper) {
        var result = getValue(data, expr.$toUpper);

        return result.toUpperCase();
    }
    if (expr.$add) {
        var result = getValue(data, expr.$add[0]) + getValue(data, expr.$add[1]);

        for (var k = 2; k < expr.$add.length; k++)
            result += getValue(data, expr.$add[k]);

        return result;
    }
    if (expr.$subtract)
        return getValue(data, expr.$subtract[0]) -getValue(data, expr.$subtract[1]);
    if (expr.$multiply) {
        var result = getValue(data, expr.$multiply[0]) * getValue(data, expr.$multiply[1]);

        for (var k = 2; k < expr.$multiply.length; k++)
            result *= getValue(data, expr.$multiply[k]);

        return result;
    }
    if (expr.$divide)
        return getValue(data, expr.$divide[0]) / getValue(data, expr.$divide[1]);
    if (expr.$mod)
        return getValue(data, expr.$mod[0]) % getValue(data, expr.$mod[1]);
}

function getValue(data, value) {
    if (typeof value == 'string')
        if (value[0] == '$')
            return data[value.substring(1)];

    return value;
}

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

module.exports = {
    createStore: createStore
}

<commit_msg>Refactor $toLower, $toUpper implementation and invocation<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;
    },
}

var exproperators = {
    $eq: function (values) { return values[0] == values[1]; },
    $ne: function (values) { return values[0] != values[1]; },
    $lt: function (values) { return values[0] < values[1]; },
    $lte: function (values) { return values[0] <= values[1]; },
    $gt: function (values) { return values[0] > values[1]; },
    $gte: function (values) { return values[0] >= values[1]; },
    $substr: function (values) { return values[0].substring(values[1], values[2]); },
    $concat: function (values) { var result = ''; for (var n in values) result += values[n]; return result; },
    $toLower: function (value) { return value.toLowerCase(); },
    $toUpper: function (value) { return value.toUpperCase(); },
}

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)
        values = find(values, spec.$match);

    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 && spec.$sort[name] !== true;

		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];
        }

        for (var n in fields) {
            var fldvalue = fields[n];

            if (fldvalue === 1 || fldvalue === -1 || fldvalue === true || fldvalue === false)
                continue;

            if (typeof fldvalue === 'object')
                cloned[n] = evaluate(item, fldvalue);
            else
                cloned[n] = fldvalue;
        }
    }

    return cloned;
}

function evaluate(data, expr) {
    for (var n in expr)
        if (exproperators[n]) {
            var vals = expr[n];
            var values = [];

            if (Array.isArray(vals))
                vals.forEach(function (val) {
                    values.push(getValue(data, val));
                });
            else
                values = getValue(data, vals);

            return exproperators[n](values);
        }

    if (expr.$cmp) {
        var value1 = getValue(data, expr.$cmp[0]);
        var value2 = getValue(data, expr.$cmp[1]);

        if (value1 < value2)
            return -1;

        if (value1 > value2)
            return 1;

        return 0;
    }
    if (expr.$strcasecmp) {
        var value1 = getValue(data, expr.$strcasecmp[0]).toLowerCase();
        var value2 = getValue(data, expr.$strcasecmp[1]).toLowerCase();

        if (value1 < value2)
            return -1;

        if (value1 > value2)
            return 1;

        return 0;
    }
    if (expr.$add) {
        var result = getValue(data, expr.$add[0]) + getValue(data, expr.$add[1]);

        for (var k = 2; k < expr.$add.length; k++)
            result += getValue(data, expr.$add[k]);

        return result;
    }
    if (expr.$subtract)
        return getValue(data, expr.$subtract[0]) -getValue(data, expr.$subtract[1]);
    if (expr.$multiply) {
        var result = getValue(data, expr.$multiply[0]) * getValue(data, expr.$multiply[1]);

        for (var k = 2; k < expr.$multiply.length; k++)
            result *= getValue(data, expr.$multiply[k]);

        return result;
    }
    if (expr.$divide)
        return getValue(data, expr.$divide[0]) / getValue(data, expr.$divide[1]);
    if (expr.$mod)
        return getValue(data, expr.$mod[0]) % getValue(data, expr.$mod[1]);
}

function getValue(data, value) {
    if (typeof value == 'string')
        if (value[0] == '$')
            return data[value.substring(1)];

    return value;
}

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

module.exports = {
    createStore: createStore
}

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 Mansi Shah, Santosh Rajan
*/

var ast = {
        type: "Program",
        body: []
    }

module.exports = function(src) {

    var spaceRegEx = /^[ \t\r\n\b\f]+/,
        newLineRegEx = /\r?\n/g,
        nullRegEx = /^null\b/i,
        booleanRegEx = /^(true|false)\b/i,
        numberRegEx = /^[-+]?(\d+(\.\d*)?|\.\d+)([e][+-]?\d+)?/i,
        stringRegEx = /^('|").*?[^\\]\1/,
        identifierRegEx = /^[a-z\~]+[a-z0-9_\[\]\.\*]*/i,
        openParanthesesRegEx = /^\(/,
        closingParanthesesRegEx = /^\)/,
        operatorRegEx = /^(\*|\+|\-|\/|\%)/,
        functionKeywordRegEx = /^(\()(\s)*function/,
        macroKeywordRegEx = /^(\()(\s)*macro\b/,
        varKeywordRegEx = /^(\()?(\s)*var/,
        fatArrowRegEx = /^\=\>/,
        line = 1
        macros = {}

    function consumer(regex, process) {
        return function() {
            var matches = regex.exec(src)
            if(!matches) return null
            src = src.slice(matches[0].length)
            spaceParser()
            if(process)    return process(matches[0])
            return matches[0]
        }
    }

    function parserFactory() {
        var parsers = arguments
        return function() {
            for(index in parsers){
                var parser = parsers[index]
                spaceParser()
                var result = parser()
                if(result)  return result
            }
            return null
        }
    }

    var spaceParser = consumer(spaceRegEx, function(val){
            var match = val.match(newLineRegEx)
            var l = (match)? match.length : 0
            line+=l
            return true
        }),
        nullParser = consumer(nullRegEx, function(){
            return {type: "Literal", value: null}
        }),
        booleanParser = consumer(booleanRegEx, function(val){
            return {type: "Literal", value: val}
        }),
        numberParser = consumer(numberRegEx, function(val){
            return {type: "Literal", value: Number(val)}
        }),
        stringParser = consumer(stringRegEx, function(val){
            return {type: "Literal", value: val.slice(1,val.length-1)}
        }),
        literalParser = parserFactory(nullParser, booleanParser, numberParser, stringParser),
        identifierParser = consumer(identifierRegEx, function(val) {
            return {type: "Identifier", name: val}
        }),
        elementParser = parserFactory(literalParser, identifierParser),
        openParanthesesParser = consumer(openParanthesesRegEx, function(val){
            return true
        }),
        closingParanthesesParser = consumer(closingParanthesesRegEx, function(val){
            return true
        }),
        operatorParser = consumer(operatorRegEx),
        varKeywordParser = consumer(varKeywordRegEx),
        functionKeywordParser = consumer(functionKeywordRegEx),
        macroKeywordParser = consumer(macroKeywordRegEx),
        fatArrowParser = consumer(fatArrowRegEx)

    function argumentsParser() {
        var args = []
        spaceParser()
        while(!closingParanthesesParser()){
            arg = argStatementParser()
            if (!arg)
                throw new SyntaxError("argumentsParser : Unexpected character at line: "+line)
            args.push(arg)
            spaceParser()
        }
        spaceParser()
        if (args.length > 0)
            return args
        return []
    }

    function expressionParser() {
        if (!openParanthesesParser()) return null
        return callExpressionParser() || binaryExpressionParser() || statementParser()
    }

    function expressionStatementParser() {
        spaceParser()
        if (closingParanthesesParser())  return null
        return combinator({type: "ExpressionStatement"}, ["expression", expressionParser])
    }

    function dereferencer(macro, tree) {
        for(objs in tree){
            if (Array.isArray(tree[objs])) {
                var restArgs = []
                tree[objs].forEach(function(t, idx){
                    var persistObjs = objs
                    tree[objs][idx] = dereferencer(macro, t)
                    objs = persistObjs
                    if(Array.isArray(tree[objs][tree[objs].length-1]['deletenode'])){
                        restArgs = tree[objs][idx]['deletenode']
                    }
                })
                if(restArgs.length > 0){
                    tree[objs].pop()
                    restArgs.forEach(function(ele){
                        tree[objs].push(ele)
                    })
                }
            }
            else if (typeof tree[objs] === "object") {
                dereferencer(macro, tree[objs])
            }
            if (objs === "type" && tree[objs] === "Identifier") {
                if (tilde = /^\~(.*|\.\.\.rest)\b/.exec(tree.name)) {
                    var ref = tilde[0].slice(1)
                    // Fix this: Should be ...rest
                    if(ref === "...rest")    tree = {deletenode: macro.params[ref]}
                    else    tree = macro.params[ref]
                }
            }
        }

        return tree
    }

    function macroCallParser() {
        var temp = src
        if(!openParanthesesParser()) return null
        spaceParser()
        var expr = {}
        if (expr.callee = identifierParser()) {
            spaceParser()
            expr.arguments = argumentsParser() || []
            if (macroCopy = macros[expr.callee.name.toString()]) {
                var macro = JSON.parse(JSON.stringify(macroCopy))
                var macroName = expr.callee.name
                var args = expr.arguments.map(function(arg){
                    return arg
                })
                for(param in macro.params){
                    if (param != "...rest")
                        macro.params[param] = args.shift()
                }
                macro.params['...rest'] = args
                var macroExpr = dereferencer(macro, macro.body)
                return macroExpr
            }
        }

        src = temp
        return null
    }
    function callExpressionParser() {
        return combinator({type: "CallExpression"}, ["callee", identifierParser], ["arguments", argumentsParser])
    }

    function binaryExpressionParser() {
        return combinator({type: "BinaryExpression"}, ["operator", operatorParser], ["left", elementParser], ["right", elementParser], ["", closingParanthesesParser])
    }

    function extractExpression() {
        var found = false, parantheses = 1, i = 0
        while(!found){
            if (src[i] == '(')
                parantheses++
            if (src[i] == ')')
                parantheses--
            if (parantheses == 0)
                found = true
            i++
        }

        return src.slice(0,i)
    }

    function macroDeclarationParser() {
        if (!macroKeywordParser())    return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        spaceParser()

        //id
        if (id = identifierParser())
            macroId = id.name
        else
            throw new SyntaxError("Expecting a macro identifier")
        spaceParser()

        //params
        if (!openParanthesesParser())
            throw new SyntaxError("Expecting '(' after " + macroId)

        var macro = {}
        macro.params = {}
        while(!closingParanthesesParser()){
            if (param = identifierParser()) {
                var paramName = param.name
                // param[paramName.toString()] = null
                macro.params[paramName.toString()] = null
            }
            spaceParser()
        }
        macro.params["...rest"] = []
        spaceParser()

        //body
        macro.body = []

        //Fix this: Not very desirable, might go in infinite loop
        while(src.length != 0){
            macro.body.push(statementParser(src.slice(0,src.length-1)))
            spaceParser()
            closingParanthesesParser()
        }

        src = rest
        spaceParser()

        macros[macroId.toString()] = macro
        return null

    }

    function combinator(expr){
        for(var i=1; i<arguments.length; i++){
            var attr = arguments[i][0]
            var parser = arguments[i][1]
            var args = arguments[i][2]
            if(typeof parser == "function"){
                if(Array.isArray(args)){
                    var result = parser(args[0],args[1])
                }
                else
                    var result = parser()
                if(result == null) return null
            }
            else
                result = parser

            if(attr){
                expr[attr] = result
            }
        }
        return expr
    }

    function functionDeclarationParser() {
        if (!functionKeywordParser())    return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        var expr = combinator({type: "FunctionDeclaration"}, ["id", identifierParser], ["",openParanthesesParser], ["params",argumentsParser], ["defaults", []], ["body", combinator, [{type: "BlockStatement", body: []}, ["body", functionBodyParser]]], ["generator", false], ["expression", false])
        src = rest
        return expr
    }

    function initializerParser() {
        return elementParser() || expressionParser()
    }

    function variableDeclarationParser() {
        if (!varKeywordParser()) return null
        var expr = {type: "VariableDeclaration", declarations: [], kind: "var"}
        var innerExpr = combinator({type: "VariableDeclarator"}, ["id", identifierParser], ["init", initializerParser], ["", closingParanthesesParser])
        expr.declarations.push(innerExpr)
        closingParanthesesParser()
        return expr
    }

    function arrowFunctionParser() {
        if (!fatArrowParser())   return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        var expr = combinator({type: "ArrowFunctionExpression"}, ["id", null], ["", openParanthesesParser], ["params", argumentsParser], ["defaults", []], ["body", combinator, [{type: "BlockStatement"}, ["body", functionBodyParser]]], ["generator", false], ["expression", false])
        src = rest
        return expr
    }

    function extractStatements() {
        var stmts = [], openP = 0, idx = 0
        for(var i = 0; i < src.length; i++){
            if (i != 0 && openP == 0) {
                var stmt = src.slice(idx, i)
                if (stmt.length > 0)
                    stmts.push(stmt)
                idx = i
            }
            if (src[i] == '(')
                openP++
            if (src[i] == ')')
                openP--
        }
        return stmts
    }

    function functionBodyParser() {
        var stmts = []
        while(!closingParanthesesParser()){
            var stmt = argStatementParser()
            if(stmt != null)
                stmts.push(stmt)
        }
        stmts[stmts.length-1] = {type: "ReturnStatement", argument: stmts[stmts.length-1]}
        //Fix this: literal splitting up in characters
        //Fix this: what about ...rest in functions?

        return stmts
    }

    function statementParser() {
        return arrowFunctionParser() || macroDeclarationParser()|| functionDeclarationParser() || variableDeclarationParser() || elementParser() || macroCallParser() || expressionStatementParser()
    }

    //Fix this: Have to figure out a way to remove this function
    function argStatementParser() {
        return arrowFunctionParser() || macroDeclarationParser()|| functionDeclarationParser() || variableDeclarationParser() || elementParser() || expressionParser()
    }

    function bodyParser() {
        var body = []
        while(src.length != 0){
            spaceParser()
            var stmt = statementParser()
            if (!stmt)
                throw new SyntaxError("Cannot parse statement at line: " + line)
            if(Array.isArray(stmt))
                stmt.forEach(function(s){body.push(s)})
            else
                body.push(stmt)

        }
        return body
    }

    ast.body = bodyParser()
    return ast
}<commit_msg>Code cleanup<commit_after>/*
 * Copyright (c) 2015 Mansi Shah, Santosh Rajan
*/

var ast = {
        type: "Program",
        body: []
    }

module.exports = function(src) {

    var spaceRegEx = /^[ \t\r\n\b\f]+/,
        newLineRegEx = /\r?\n/g,
        nullRegEx = /^null\b/i,
        booleanRegEx = /^(true|false)\b/i,
        numberRegEx = /^[-+]?(\d+(\.\d*)?|\.\d+)([e][+-]?\d+)?/i,
        stringRegEx = /^('|").*?[^\\]\1/,
        identifierRegEx = /^[a-z\~]+[a-z0-9_\[\]\.\*]*/i,
        openParanthesesRegEx = /^\(/,
        closingParanthesesRegEx = /^\)/,
        operatorRegEx = /^(\*|\+|\-|\/|\%)/,
        functionKeywordRegEx = /^(\()(\s)*function/,
        macroKeywordRegEx = /^(\()(\s)*macro\b/,
        varKeywordRegEx = /^(\()?(\s)*var/,
        fatArrowRegEx = /^\=\>/,
        line = 1
        macros = {}

    function consumer(regex, process) {
        return function() {
            var matches = regex.exec(src)
            if(!matches) return null
            src = src.slice(matches[0].length)
            spaceParser()
            if(process)    return process(matches[0])
            return matches[0]
        }
    }

    function parserFactory() {
        var parsers = arguments
        return function() {
            for(index in parsers){
                var parser = parsers[index]
                spaceParser()
                var result = parser()
                if(result)  return result
            }
            return null
        }
    }

    var spaceParser = consumer(spaceRegEx, function(val){
            var match = val.match(newLineRegEx)
            var l = (match)? match.length : 0
            line+=l
            return true
        }),
        nullParser = consumer(nullRegEx, function(){
            return {type: "Literal", value: null}
        }),
        booleanParser = consumer(booleanRegEx, function(val){
            return {type: "Literal", value: val}
        }),
        numberParser = consumer(numberRegEx, function(val){
            return {type: "Literal", value: Number(val)}
        }),
        stringParser = consumer(stringRegEx, function(val){
            return {type: "Literal", value: val.slice(1,val.length-1)}
        }),
        literalParser = parserFactory(nullParser, booleanParser, numberParser, stringParser),
        identifierParser = consumer(identifierRegEx, function(val) {
            return {type: "Identifier", name: val}
        }),
        elementParser = parserFactory(literalParser, identifierParser),
        openParanthesesParser = consumer(openParanthesesRegEx, function(val){
            return true
        }),
        closingParanthesesParser = consumer(closingParanthesesRegEx, function(val){
            return true
        }),
        operatorParser = consumer(operatorRegEx),
        varKeywordParser = consumer(varKeywordRegEx),
        functionKeywordParser = consumer(functionKeywordRegEx),
        macroKeywordParser = consumer(macroKeywordRegEx),
        fatArrowParser = consumer(fatArrowRegEx)

    function argumentsParser() {
        var args = []
        spaceParser()
        while(!closingParanthesesParser()){
            arg = argStatementParser()
            if (!arg)
                throw new SyntaxError("argumentsParser : Unexpected character at line: "+line)
            args.push(arg)
            spaceParser()
        }
        spaceParser()
        if (args.length > 0)
            return args
        return []
    }

    function expressionParser() {
        if (!openParanthesesParser()) return null
        return callExpressionParser() || binaryExpressionParser() || statementParser()
    }

    function expressionStatementParser() {
        spaceParser()
        if (closingParanthesesParser())  return null
        return combinator({type: "ExpressionStatement"}, ["expression", expressionParser])
    }

    function dereferencer(macro, tree) {
        for(objs in tree){
            if (Array.isArray(tree[objs])) {
                var restArgs = []
                tree[objs].forEach(function(t, idx){
                    var persistObjs = objs
                    tree[objs][idx] = dereferencer(macro, t)
                    objs = persistObjs
                    if(Array.isArray(tree[objs][tree[objs].length-1]['deletenode'])){
                        restArgs = tree[objs][idx]['deletenode']
                    }
                })
                if(restArgs.length > 0){
                    tree[objs].pop()
                    restArgs.forEach(function(ele){
                        tree[objs].push(ele)
                    })
                }
            }
            else if (typeof tree[objs] === "object") {
                dereferencer(macro, tree[objs])
            }
            if (objs === "type" && tree[objs] === "Identifier") {
                if (tilde = /^\~(.*|\.\.\.rest)\b/.exec(tree.name)) {
                    var ref = tilde[0].slice(1)
                    // Fix this: Should be ...rest
                    if(ref === "...rest")    tree = {deletenode: macro.params[ref]}
                    else    tree = macro.params[ref]
                }
            }
        }

        return tree
    }

    function macroCallParser() {
        var temp = src
        if(!openParanthesesParser()) return null
        spaceParser()
        var expr = {}
        if (expr.callee = identifierParser()) {
            spaceParser()
            expr.arguments = argumentsParser() || []
            if (macroCopy = macros[expr.callee.name.toString()]) {
                var macro = JSON.parse(JSON.stringify(macroCopy))
                var macroName = expr.callee.name
                var args = expr.arguments.map(function(arg){
                    return arg
                })
                for(param in macro.params){
                    if (param != "...rest")
                        macro.params[param] = args.shift()
                }
                macro.params['...rest'] = args
                var macroExpr = dereferencer(macro, macro.body)
                return macroExpr
            }
        }

        src = temp
        return null
    }
    function callExpressionParser() {
        return combinator({type: "CallExpression"}, ["callee", identifierParser], ["arguments", argumentsParser])
    }

    function binaryExpressionParser() {
        return combinator({type: "BinaryExpression"}, ["operator", operatorParser], ["left", elementParser], ["right", elementParser], ["", closingParanthesesParser])
    }

    function extractExpression() {
        var found = false, parantheses = 1, i = 0
        while(!found){
            if (src[i] == '(')
                parantheses++
            if (src[i] == ')')
                parantheses--
            if (parantheses == 0)
                found = true
            i++
        }

        return src.slice(0,i)
    }

    function macroDeclarationParser() {
        if (!macroKeywordParser())    return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        spaceParser()

        //id
        if (id = identifierParser())
            macroId = id.name
        else
            throw new SyntaxError("Expecting a macro identifier")
        spaceParser()

        //params
        if (!openParanthesesParser())
            throw new SyntaxError("Expecting '(' after " + macroId)

        var macro = {}
        macro.params = {}
        while(!closingParanthesesParser()){
            if (param = identifierParser()) {
                var paramName = param.name
                // param[paramName.toString()] = null
                macro.params[paramName.toString()] = null
            }
            spaceParser()
        }
        macro.params["...rest"] = []
        spaceParser()

        //body
        macro.body = []

        //Fix this: Not very desirable, might go in infinite loop
        while(src.length != 0){
            macro.body.push(statementParser(src.slice(0,src.length-1)))
            spaceParser()
            closingParanthesesParser()
        }

        src = rest
        spaceParser()

        macros[macroId.toString()] = macro
        return null

    }

    function combinator(expr){
        for(var i=1; i<arguments.length; i++){
            var attr = arguments[i][0]
            var parser = arguments[i][1]
            var args = arguments[i][2]
            if(typeof parser == "function"){
                if(Array.isArray(args)){
                    var result = parser(args[0],args[1])
                }
                else
                    var result = parser()
                if(result == null) return null
            }
            else
                result = parser

            if(attr){
                expr[attr] = result
            }
        }
        return expr
    }

    function functionDeclarationParser() {
        if (!functionKeywordParser())    return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        var expr = combinator({type: "FunctionDeclaration"}, ["id", identifierParser], ["",openParanthesesParser], ["params",argumentsParser], ["defaults", []], ["body", combinator, [{type: "BlockStatement", body: []}, ["body", functionBodyParser]]], ["generator", false], ["expression", false])
        src = rest
        return expr
    }

    function initializerParser() {
        return elementParser() || expressionParser()
    }

    function variableDeclarationParser() {
        if (!varKeywordParser()) return null
        var expr = {type: "VariableDeclaration", declarations: [], kind: "var"}
        var innerExpr = combinator({type: "VariableDeclarator"}, ["id", identifierParser], ["init", initializerParser], ["", closingParanthesesParser])
        expr.declarations.push(innerExpr)
        closingParanthesesParser()
        return expr
    }

    function arrowFunctionParser() {
        if (!fatArrowParser())   return null
        var snippet = extractExpression()
        var rest = src.slice(snippet.length)
        src = snippet
        var expr = combinator({type: "ArrowFunctionExpression"}, ["id", null], ["", openParanthesesParser], ["params", argumentsParser], ["defaults", []], ["body", combinator, [{type: "BlockStatement"}, ["body", functionBodyParser]]], ["generator", false], ["expression", false])
        src = rest
        return expr
    }

    function functionBodyParser() {
        var stmts = []
        while(!closingParanthesesParser()){
            var stmt = argStatementParser()
            if(stmt != null)
                stmts.push(stmt)
        }
        stmts[stmts.length-1] = {type: "ReturnStatement", argument: stmts[stmts.length-1]}
        //Fix this: literal splitting up in characters
        //Fix this: what about ...rest in functions?

        return stmts
    }

    function statementParser() {
        return arrowFunctionParser() || macroDeclarationParser()|| functionDeclarationParser() || variableDeclarationParser() || elementParser() || macroCallParser() || expressionStatementParser()
    }

    //Fix this: Have to figure out a way to remove this function
    function argStatementParser() {
        return arrowFunctionParser() || macroDeclarationParser()|| functionDeclarationParser() || variableDeclarationParser() || elementParser() || expressionParser()
    }

    function bodyParser() {
        var body = []
        while(src.length != 0){
            spaceParser()
            var stmt = statementParser()
            if (!stmt)
                throw new SyntaxError("Cannot parse statement at line: " + line)
            if(Array.isArray(stmt))
                stmt.forEach(function(s){body.push(s)})
            else
                body.push(stmt)

        }
        return body
    }

    ast.body = bodyParser()
    return ast
}<|endoftext|>


<commit_before>'use strict';

var create       = Object.create
  , keys         = Object.keys
  , stringify    = JSON.stringify
  , path         = require('path')
  , fs           = require('fs')
  , copy         = require('es5-ext/lib/Array/prototype/copy')
  , last         = require('es5-ext/lib/Array/prototype/last')
  , isString     = require('es5-ext/lib/String/is-string')
  , trimCommon   = require('es5-ext/lib/String/prototype/trim-common-left')
  , d            = require('es5-ext/lib/Object/descriptor')
  , indent       = require('es5-ext/lib/String/prototype/indent')
  , deferred     = require('deferred')
  , getRequire   = require('next/lib/get-require')
  , separator    = require('next/lib/path/separator')
  , findRoot     = require('next/lib/find-package-root')
  , fileExists   = deferred.promisify(require('next/lib/fs/file-exists'))
  , dirExists    = deferred.promisify(require('next/lib/fs/dir-exists'))
  , findRequires = require('find-requires')

  , dirname = path.dirname, join = path.join, resolve = path.resolve
  , readFile = deferred.promisify(fs.readFile)

  , parseDependencies, parser, modulesToString;

parseDependencies = function (text) {
	return findRequires(text, { raw: true }).map(function (node) {
		var path = node.value;
		if (!path) {
			throw new TypeError("Not supported require call: '" + node.raw + "'");
		}
		return (path.slice(-3) === '.js') ? path.slice(0, -3) : path;
	});
};

var max = 200;
modulesToString = function self(nest, options) {
	return keys(this).sort().map(function (name) {
		var current = this[name]
		  , text = indent.call(stringify(name), '\t', nest + 1) + ': ';
		if (name === ':mainpath:') {
			text += stringify(current);
		} else if (!isString(current)) {
			text += '{\n' + self.call(current, nest + 1, options) + '\n' +
				indent.call('}', '\t', nest + 1);
		} else {
			text += 'function (exports, module, require) {\n' +
				indent.call(options.sourceMap ?
						'eval(' + stringify(current + '\n//@ sourceURL=' +
							current.filename) +
						')' : current, '\t', nest + 2) +
				indent.call('}', '\t', nest + 1);
		}
		return text;
	}, this).join(',\n');
};

parser = {
	readInput: function (input) {
		var scope, path, tree = [];
		input = resolve(String(input));
		return findRoot(input)(function (root) {
			var name, dirs;
			name = root ? last.call(root.split(separator)) : '/';
			if (!this.modules[name]) {
				this.packages[name] = root || separator;
				this.modules[name] = {};
			}
			scope = this.modules[name];
			path = name + separator +
				(dirs = trimCommon.call(input, root || '').slice(1));
			dirs = dirs.split(separator);
			name = dirs.pop();
			dirs.forEach(function (dir) {
				tree.push(scope);
				scope = scope[dir] || (scope[dir] = {});
			});
			return this.readFile(input, name, scope, tree);
		}.bind(this))(function () {
			return path.slice(0, -3).replace(/\\/g, '/');
		});
	},
	readFile: function (filename, name, scope, tree) {
		// console.log("PC", filename);
		return readFile(filename, 'utf8')(function (content) {
			this.modulesFiles.push(filename);
			if (content.charCodeAt(0) === 0xFEFF) {
				// Remove BOM, see:
				// https://github.com/joyent/node/blob/master/lib/module.js#L448
				// (...) This catches EF BB BF (the UTF-8 BOM)
				// because the buffer-to-string conversion in `fs.readFile()`
				// translates it to FEFF, the UTF-16 BOM. (...)
				content = content.slice(1);
			}
			if (last.call(content) !== '\n') {
				content += '\n';
			}
			scope[name] = content = new String(content);
			content.filename = filename.split(separator)
				.slice(-2 - tree.length).join('/');
			return deferred.map(parseDependencies(content),
				this.resolve.bind(this, filename, dirname(filename), scope, tree));
		}.bind(this));
	},
	resolve: function (fromfile, dirname, scope, tree, filename) {
		// console.log("R", filename);
		tree = copy.call(tree);
		if (filename[0] === '.') {
			return this.resolveLocal(fromfile, dirname, scope, tree, filename);
		} else {
			return this.resolveExternal(fromfile, dirname, filename);
		}
	},
	resolveLocal: function (fromfile, dirname, scope, tree, filename) {
		// console.log("RL", filename, dirname, fromfile, tree.length);
		var path, dir, name, pname, isDir, org;
		filename = join(filename);
		isDir = (last.call(filename) === separator);
		path = filename.split(separator);
		filename = resolve(dirname + separator + filename);
		return (isDir ? dirExists(filename)(function (exists) {
			if (exists) {
				path.pop();
				name = 'index.js';
				filename += separator + 'index.js';
			} else {
				throw new Error("Module '" + filename +
					"' not found, as required in '" + fromfile + "'");
			}
		}) : fileExists(filename + '.js')(function (exists) {
			if (exists) {
				filename += '.js';
				name = path.pop() + '.js';
			} else {
				return dirExists(filename)(function (exists) {
					if (exists) {
						name = 'index.js';
						filename += separator + 'index.js';
					} else {
						throw new Error("Module '" + filename +
							"' not found, as required in '" + fromfile + "'");
					}
				});
			}
		}))(function () {
			while ((dir = path.shift())) {
				if (dir === '..') {
					if (!tree.length) {
						throw new Error("Require out of package root scope");
					}
					scope = tree.pop();
				} else if (dir !== '.') {
					tree.push(scope);
					scope = scope[dir] || (scope[dir] = {});
				}
			}
			if (scope[name]) {
				return null;
			} else {
				return (scope[name] = this.readFile(filename, name, scope, tree));
			}
		}.bind(this));
	},
	resolveExternal: function (fromfile, dirname, filename) {
		// console.log("RE", filename);
		var org = filename, name, tree, require, main, path;
		filename = join(filename);
		name = filename.split(separator, 1)[0];
		return deferred.promisifySync(function () {
			if (this.modules[name]) {
				return this.modules[name];
			} else {
				require = getRequire(fromfile);
				try {
					path = main = require.resolve(name);
				} catch (e) {
					try {
						path = require.resolve(org);
					} catch (e2) {
						throw new Error("Module '" + filename +
							"' not found, as required in '" + fromfile + "'");
					}
				}
				if (main === name) {
					throw new Error("Cannot require " + stringify(name) +
						". Native node.js modules are not ported to client-side.");
				}
				return (this.modules[name] = findRoot(path)(function (root) {
					var module = {};
					this.packages[name] = root;
					if (main) {
						module[':mainpath:']
							= trimCommon.call(main, root).slice(1, -3);
					}
					return module;
				}.bind(this)).cb(function (module) {
					if (!this.modules[name]) {
						process.nextTick(function () {
							this.modules[name] = module;
						}.bind(this))
					} else {
						this.modules[name] = module;
					}
				}.bind(this), null));
			}
		}.bind(this))()(function (scope) {
			// console.log('RFT', name, this.modules[name] === scope);
			tree = [];
			if (name === filename) {
				filename = scope[':mainpath:'];
			} else {
				filename = filename.slice(name.length + 1);
			}
			// console.log("RE", name, scope, filename);
			return this.resolveLocal(fromfile, this.packages[name], scope, tree,
				filename);
		}.bind(this));
	},
	toString: function (options) {
		return '{\n' + modulesToString.call(this.modules, 0, options) + '\n}';
	}
};

module.exports = function () {
	return create(parser, {
		modules: d({}),
		packages: d({}),
		modulesFiles: d([])
	});
};
<commit_msg>Fix external package resolution<commit_after>'use strict';

var create       = Object.create
  , keys         = Object.keys
  , stringify    = JSON.stringify
  , path         = require('path')
  , fs           = require('fs')
  , copy         = require('es5-ext/lib/Array/prototype/copy')
  , last         = require('es5-ext/lib/Array/prototype/last')
  , isString     = require('es5-ext/lib/String/is-string')
  , trimCommon   = require('es5-ext/lib/String/prototype/trim-common-left')
  , d            = require('es5-ext/lib/Object/descriptor')
  , indent       = require('es5-ext/lib/String/prototype/indent')
  , deferred     = require('deferred')
  , getRequire   = require('next/lib/get-require')
  , separator    = require('next/lib/path/separator')
  , findRoot     = require('next/lib/find-package-root')
  , fileExists   = deferred.promisify(require('next/lib/fs/file-exists'))
  , dirExists    = deferred.promisify(require('next/lib/fs/dir-exists'))
  , findRequires = require('find-requires')

  , dirname = path.dirname, join = path.join, resolve = path.resolve
  , readFile = deferred.promisify(fs.readFile)

  , parseDependencies, parser, modulesToString;

parseDependencies = function (text) {
	return findRequires(text, { raw: true }).map(function (node) {
		var path = node.value;
		if (!path) {
			throw new TypeError("Not supported require call: '" + node.raw + "'");
		}
		return (path.slice(-3) === '.js') ? path.slice(0, -3) : path;
	});
};

var max = 200;
modulesToString = function self(nest, options) {
	return keys(this).sort().map(function (name) {
		var current = this[name]
		  , text = indent.call(stringify(name), '\t', nest + 1) + ': ';
		if (name === ':mainpath:') {
			text += stringify(current);
		} else if (!isString(current)) {
			text += '{\n' + self.call(current, nest + 1, options) + '\n' +
				indent.call('}', '\t', nest + 1);
		} else {
			text += 'function (exports, module, require) {\n' +
				indent.call(options.sourceMap ?
						'eval(' + stringify(current + '\n//@ sourceURL=' +
							current.filename) +
						')' : current, '\t', nest + 2) +
				indent.call('}', '\t', nest + 1);
		}
		return text;
	}, this).join(',\n');
};

parser = {
	readInput: function (input) {
		var scope, path, tree = [];
		input = resolve(String(input));
		return findRoot(input)(function (root) {
			var name, dirs;
			name = root ? last.call(root.split(separator)) : '/';
			if (!this.modules[name]) {
				this.packages[name] = root || separator;
				this.modules[name] = {};
			}
			scope = this.modules[name];
			path = name + separator +
				(dirs = trimCommon.call(input, root || '').slice(1));
			dirs = dirs.split(separator);
			name = dirs.pop();
			dirs.forEach(function (dir) {
				tree.push(scope);
				scope = scope[dir] || (scope[dir] = {});
			});
			return this.readFile(input, name, scope, tree);
		}.bind(this))(function () {
			return path.slice(0, -3).replace(/\\/g, '/');
		});
	},
	readFile: function (filename, name, scope, tree) {
		// console.log("PC", filename);
		return readFile(filename, 'utf8')(function (content) {
			this.modulesFiles.push(filename);
			if (content.charCodeAt(0) === 0xFEFF) {
				// Remove BOM, see:
				// https://github.com/joyent/node/blob/master/lib/module.js#L448
				// (...) This catches EF BB BF (the UTF-8 BOM)
				// because the buffer-to-string conversion in `fs.readFile()`
				// translates it to FEFF, the UTF-16 BOM. (...)
				content = content.slice(1);
			}
			if (last.call(content) !== '\n') {
				content += '\n';
			}
			scope[name] = content = new String(content);
			content.filename = filename.split(separator)
				.slice(-2 - tree.length).join('/');
			return deferred.map(parseDependencies(content),
				this.resolve.bind(this, filename, dirname(filename), scope, tree));
		}.bind(this));
	},
	resolve: function (fromfile, dirname, scope, tree, filename) {
		// console.log("R", filename);
		tree = copy.call(tree);
		if (filename[0] === '.') {
			return this.resolveLocal(fromfile, dirname, scope, tree, filename);
		} else {
			return this.resolveExternal(fromfile, dirname, filename);
		}
	},
	resolveLocal: function (fromfile, dirname, scope, tree, filename) {
		// console.log("RL", filename, dirname, fromfile, tree.length);
		var path, dir, name, pname, isDir, org;
		filename = join(filename);
		isDir = (last.call(filename) === separator);
		path = filename.split(separator);
		filename = resolve(dirname + separator + filename);
		return (isDir ? dirExists(filename)(function (exists) {
			if (exists) {
				path.pop();
				name = 'index.js';
				filename += separator + 'index.js';
			} else {
				throw new Error("Module '" + filename +
					"' not found, as required in '" + fromfile + "'");
			}
		}) : fileExists(filename + '.js')(function (exists) {
			if (exists) {
				filename += '.js';
				name = path.pop() + '.js';
			} else {
				return dirExists(filename)(function (exists) {
					if (exists) {
						name = 'index.js';
						filename += separator + 'index.js';
					} else {
						throw new Error("Module '" + filename +
							"' not found, as required in '" + fromfile + "'");
					}
				});
			}
		}))(function () {
			while ((dir = path.shift())) {
				if (dir === '..') {
					if (!tree.length) {
						throw new Error("Require out of package root scope");
					}
					scope = tree.pop();
				} else if (dir !== '.') {
					tree.push(scope);
					scope = scope[dir] || (scope[dir] = {});
				}
			}
			if (scope[name]) {
				return null;
			} else {
				return (scope[name] = this.readFile(filename, name, scope, tree));
			}
		}.bind(this));
	},
	resolveExternal: function (fromfile, dirname, filename) {
		// console.log("RE", filename);
		var org = filename, name, tree, require, main, path;
		filename = join(filename);
		name = filename.split(separator, 1)[0];
		return deferred.promisifySync(function () {
			if (this.modules[name]) {
				return this.modules[name];
			} else {
				require = getRequire(fromfile);
				try {
					path = main = require.resolve(name);
				} catch (e) {
					try {
						path = require.resolve(org);
					} catch (e2) {
						throw new Error("Module '" + filename +
							"' not found, as required in '" + fromfile + "'");
					}
				}
				if (main === name) {
					throw new Error("Cannot require " + stringify(name) +
						". Native node.js modules are not ported to client-side.");
				}
				var promise = this.modules[name] = findRoot(path)(function (root) {
					var module = {};
					this.packages[name] = root;
					if (main) {
						module[':mainpath:']
							= trimCommon.call(main, root).slice(1, -3);
					}
					return module;
				}.bind(this));
				promise.cb(function (module) {
					this.modules[name] = module;
				}.bind(this), null);
				return promise;
			}
		}.bind(this))()(function (scope) {
			// console.log('RFT', name, this.modules[name] === scope);
			tree = [];
			if (name === filename) {
				filename = scope[':mainpath:'];
			} else {
				filename = filename.slice(name.length + 1);
			}
			// console.log("RE", name, scope, filename);
			return this.resolveLocal(fromfile, this.packages[name], scope, tree,
				filename);
		}.bind(this));
	},
	toString: function (options) {
		return '{\n' + modulesToString.call(this.modules, 0, options) + '\n}';
	}
};

module.exports = function () {
	return create(parser, {
		modules: d({}),
		packages: d({}),
		modulesFiles: d([])
	});
};
<|endoftext|>


<commit_before>
var lexer = require('./lexer');
var TokenType = lexer.TokenType;

function doVisit(node, name, visitor) {
    var fnname = 'visit' + name;

    if (visitor[fnname])
        visitor[fnname](node);
    else
        visitor.visitGeneric(node);
}

function ConstantExpression(value) {
    this.compile = function () {
        return value;
    };

    this.getValue = function () { return value; };

    this.visit = function (visitor) {
        doVisit(this, 'ConstantExpression', visitor);
    };
}

function StringExpression(value) {
    this.compile = function () {
        return '"' + value + '"';
    };

    this.getValue = function () { return value; };

    this.visit = function (visitor) {
        doVisit(this, 'StringExpression', visitor);
    };
}

function NameExpression(name) {
    this.compile = function () {
        return name;
    };

    this.getName = function () { return name; };

    this.visit = function (visitor) {
        doVisit(this, 'NameExpression', visitor);
    };
}

function CallExpression(expr, args) {
    this.compile = function () {
        var code = expr.compile() + "(";

        for (var k = 0; k < args.length; k++) {
            if (k)
                code += ", ";
            code += args[k].compile();
        }

        code += ')';

        return code;
    };
}

function ParensExpression(expr) {
    this.compile = function () {
        return '(' + expr.compile() + ')';
    };
}

function BinaryExpression (leftexpr, rightexpr, operator) {
    this.compile = function () {
        return leftexpr.compile() + ' ' + operator + ' ' + rightexpr.compile();
    }

    this.getLeftExpression = function () { return leftexpr; };
    this.getRightExpression = function () { return rightexpr; };
    this.getOperator = function () { return operator; };

    this.visit = function (visitor) {
        return doVisit(this, 'BinaryExpression', visitor);
    }
}

function CompositeCommand(commands) {
    this.compile = function () {
        var cmds = [];

        if (this.variables)
            this.variables.forEach(function (variable) {
                cmds.push('int ' + variable + ';');
            });

        commands.forEach(function (command) {
            cmds.push(command.compile());
        });

        return cmds;
    };

    this.getCommands = function () { return commands; };

    this.collectVariables = function (names) {
        commands.forEach(function (command) {
            if (command.collectVariables)
                command.collectVariables(names);
        });
    };

    this.visit = function (node) {
        doVisit(this, 'CompositeCommand', node);
    };
}

function ReturnCommand(expr) {
    this.compile = function () {
        return 'return ' + expr.compile() + ';';
    };
}

function BreakCommand(expr) {
    this.compile = function () {
        return 'break;';
    };
}

function ContinueCommand(expr) {
    this.compile = function () {
        return 'continue;';
    };
}

function IfCommand(expr, command) {
    this.compile = function () {
        var result = ['if (' + expr.compile() + ')'];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        return result;
    };

    this.getExpression = function () { return expr; };
    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'IfCommand', visitor);
    };

    this.collectVariables = function (names) {
        if (command.collectVariables)
            command.collectVariables(names);
    };
}

function WhileCommand(expr, command) {
    this.compile = function () {
        var result = ['while (' + expr.compile() + ')'];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        return result;
    };

    this.getExpression = function () { return expr; };
    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'WhileCommand', visitor);
    };

    this.collectVariables = function (names) {
        if (command.collectVariables)
            command.collectVariables(names);
    };
}

function ExpressionCommand(expr) {
    this.compile = function () {
        return expr.compile() + ';';
    };
}

function AssignmentCommand(expr, expr2) {
    this.compile = function () {
        return expr.compile() + ' = ' + expr2.compile() + ';';
    };

    this.visit = function (visitor) {
        doVisit(this, 'AssignmentCommand', visitor);
    };

    this.getLeftValueExpression = function () { return expr; };

    this.collectVariables = function (names) {
        if (expr && expr.getName) {
            var name = expr.getName();
            if (names.indexOf(name) < 0)
                names.push(name);
        }
    };
}

function DefCommand(name, args, command) {
    this.compile = function () {
        var code = 'int ' + name + '(';

        for (var k = 0; k < args.length; k++) {
            if (k)
                code += '. ';
            code += 'int ' + args[k];
        }

        code += ')';

        var result = [code];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        var names = [];

        if (command.collectVariables)
            command.collectVariables(names);

        var vars = [];

        names.forEach(function (name) {
            vars.push('int ' + name + ';');
        });

        result[1] = vars.concat(result[1]);

        if (result[1].length == 1)
            result[1].push(null);

        return result;
    };

    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'DefCommand', visitor);
    };
}

function Parser(text) {
    var lexr = lexer.createLexer(text);
    var tokens = [];
    var self = this;

    this.parseExpression = function () {
        var expr = parseTerm();

        if (expr === null)
            return null;

        for (var token = nextToken(); isBinaryOperator(token); token = nextToken())
            expr = new BinaryExpression(expr, parseTerm(), token.value);

        pushToken(token);

        return expr;
    }

    this.parseIndent = function () {
        var token = nextToken();

        if (token === null)
            return null;

        return token.value;
    }

    this.parseCommand = function () {
        var indent = this.parseIndent();

        if (indent === null)
            return null;

        var token = nextToken();

        if (isWord(token, 'return'))
            return new ReturnCommand(this.parseExpression());

        if (isWord(token, 'break'))
            return new BreakCommand();

        if (isWord(token, 'continue'))
            return new ContinueCommand();

        if (isWord(token, 'if')) {
            var expr = this.parseExpression();
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new IfCommand(expr, commands);
        }

        if (isWord(token, 'while')) {
            var expr = this.parseExpression();
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new WhileCommand(expr, commands);
        }

        if (isWord(token, 'def')) {
            var name = parseName();
            var args = [];

            parseToken('(', TokenType.Punctuation);

            var token = nextToken();

            if (token && token.type == TokenType.Name) {
                args.push(token.value);
            }
            else
                pushToken(token);

            parseToken(')', TokenType.Punctuation);
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new DefCommand(name, args, commands);
        }

        pushToken(token);

        var expr = this.parseExpression();

        token = nextToken();

        if (!token || token.type !== TokenType.Operator || token.value !== '=') {
            pushToken(token);
            return new ExpressionCommand(expr);
        }

        var expr2 = this.parseExpression();

        return new AssignmentCommand(expr, expr2);
    };

    this.parseCommands = function () {
        var commands = [];

        for (var command = this.parseCommand(); command; command = this.parseCommand())
            commands.push(command);

        return new CompositeCommand(commands);
    };

    function parseSuite(indent) {
        var commands = [];

        for (var ind = self.parseIndent(); ind && ind > indent; ind = self.parseIndent()) {
            pushToken({ value: ind, type: TokenType.Indent });
            commands.push(self.parseCommand());
        }

        if (ind !== null)
            pushToken({ value: ind, type: TokenType.Indent });

        return new CompositeCommand(commands);
    }

    function parseTerm() {
        var expr = parseSimpleTerm();

        if (expr === null)
            return null;

        var token = nextToken();

        if (isPunctuation(token, '(')) {
            var args = parseArguments();
            expr = new CallExpression(expr, args);
        }
        else
            pushToken(token);

        return expr;
    }

    function parseSimpleTerm() {
        var token = nextToken();

        if (token === null)
            return null;

        if (token.type === TokenType.Name)
            return new NameExpression(token.value);

        if (token.type === TokenType.String)
            return new StringExpression(token.value);

        if (token.type === TokenType.Punctuation && token.value === '(') {
            var expr = self.parseExpression();
            parseToken(')', TokenType.Punctuation);

            return new ParensExpression(expr);
        }

        if (token.type === TokenType.Integer)
            return new ConstantExpression(token.value);

        if (token.type === TokenType.Indent)
            throw "Unexpected indent";

        throw "Unexpected '" + token.value + "'";
    }

    function parseArguments() {
        var args = [];
        var token = nextToken();

        if (!isPunctuation(token, ')')) {
            pushToken(token);

            while (true) {
                args.push(self.parseExpression());
                token = nextToken();
                if (!isPunctuation(token, ','))
                    break;
            }
        }

        if (!isPunctuation(token, ')'))
            throw "Expected ')'";

        return args;
    }

    function nextToken() {
        if (tokens.length)
            return tokens.pop();

        return lexr.nextToken();
    }

    function pushToken(token) {
        tokens.push(token);
    }

    function parseToken(value, type) {
        var token = nextToken();

        if (!token || token.value !== value || token.type !== type)
            throw "Expected '" + value + "'";
    }

    function parseName() {
        var token = nextToken();

        if (!token || token.type !== TokenType.Name)
            throw "Name expected";

        return token.value;
    }
}

function isWord(token, name)
{
    return token && token.type === TokenType.Name && token.value === name;
}

function isPunctuation(token, value)
{
    return token && token.type === TokenType.Punctuation && token.value === value;
}

var binaryoperators = ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='];

function isBinaryOperator(token)
{
    return token && token.type === TokenType.Operator && binaryoperators.indexOf(token.value) >= 0;
}

function createParser(text) {
    return new Parser(text);
}

module.exports = {
    createParser: createParser
}

<commit_msg>Refactor: remove collectVariables method; not it's using a visitor<commit_after>
var lexer = require('./lexer');
var TokenType = lexer.TokenType;

function doVisit(node, name, visitor) {
    var fnname = 'visit' + name;

    if (visitor[fnname])
        visitor[fnname](node);
    else
        visitor.visitGeneric(node);
}

function ConstantExpression(value) {
    this.compile = function () {
        return value;
    };

    this.getValue = function () { return value; };

    this.visit = function (visitor) {
        doVisit(this, 'ConstantExpression', visitor);
    };
}

function StringExpression(value) {
    this.compile = function () {
        return '"' + value + '"';
    };

    this.getValue = function () { return value; };

    this.visit = function (visitor) {
        doVisit(this, 'StringExpression', visitor);
    };
}

function NameExpression(name) {
    this.compile = function () {
        return name;
    };

    this.getName = function () { return name; };

    this.visit = function (visitor) {
        doVisit(this, 'NameExpression', visitor);
    };
}

function CallExpression(expr, args) {
    this.compile = function () {
        var code = expr.compile() + "(";

        for (var k = 0; k < args.length; k++) {
            if (k)
                code += ", ";
            code += args[k].compile();
        }

        code += ')';

        return code;
    };
}

function ParensExpression(expr) {
    this.compile = function () {
        return '(' + expr.compile() + ')';
    };
}

function BinaryExpression (leftexpr, rightexpr, operator) {
    this.compile = function () {
        return leftexpr.compile() + ' ' + operator + ' ' + rightexpr.compile();
    }

    this.getLeftExpression = function () { return leftexpr; };
    this.getRightExpression = function () { return rightexpr; };
    this.getOperator = function () { return operator; };

    this.visit = function (visitor) {
        return doVisit(this, 'BinaryExpression', visitor);
    }
}

function CompositeCommand(commands) {
    this.compile = function () {
        var cmds = [];

        if (this.variables)
            this.variables.forEach(function (variable) {
                cmds.push('int ' + variable + ';');
            });

        commands.forEach(function (command) {
            cmds.push(command.compile());
        });

        return cmds;
    };

    this.getCommands = function () { return commands; };

    this.visit = function (node) {
        doVisit(this, 'CompositeCommand', node);
    };
}

function ReturnCommand(expr) {
    this.compile = function () {
        return 'return ' + expr.compile() + ';';
    };
}

function BreakCommand(expr) {
    this.compile = function () {
        return 'break;';
    };
}

function ContinueCommand(expr) {
    this.compile = function () {
        return 'continue;';
    };
}

function IfCommand(expr, command) {
    this.compile = function () {
        var result = ['if (' + expr.compile() + ')'];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        return result;
    };

    this.getExpression = function () { return expr; };
    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'IfCommand', visitor);
    };
}

function WhileCommand(expr, command) {
    this.compile = function () {
        var result = ['while (' + expr.compile() + ')'];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        return result;
    };

    this.getExpression = function () { return expr; };
    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'WhileCommand', visitor);
    };
}

function ExpressionCommand(expr) {
    this.compile = function () {
        return expr.compile() + ';';
    };
}

function AssignmentCommand(expr, expr2) {
    this.compile = function () {
        return expr.compile() + ' = ' + expr2.compile() + ';';
    };

    this.visit = function (visitor) {
        doVisit(this, 'AssignmentCommand', visitor);
    };

    this.getLeftValueExpression = function () { return expr; };
}

function DefCommand(name, args, command) {
    this.compile = function () {
        var code = 'int ' + name + '(';

        for (var k = 0; k < args.length; k++) {
            if (k)
                code += '. ';
            code += 'int ' + args[k];
        }

        code += ')';

        var result = [code];
        var cmd = command.compile();

        if (Array.isArray(cmd))
            result[1] = cmd;
        else
            result[1] = [cmd];

        var vars = [];

        if (this.variables)
            this.variables.forEach(function (variable) {
                vars.push('int ' + variable + ';');
            });

        result[1] = vars.concat(result[1]);

        if (result[1].length == 1)
            result[1].push(null);

        return result;
    };

    this.getCommand = function () { return command; };

    this.visit = function (visitor) {
        doVisit(this, 'DefCommand', visitor);
    };
}

function Parser(text) {
    var lexr = lexer.createLexer(text);
    var tokens = [];
    var self = this;

    this.parseExpression = function () {
        var expr = parseTerm();

        if (expr === null)
            return null;

        for (var token = nextToken(); isBinaryOperator(token); token = nextToken())
            expr = new BinaryExpression(expr, parseTerm(), token.value);

        pushToken(token);

        return expr;
    }

    this.parseIndent = function () {
        var token = nextToken();

        if (token === null)
            return null;

        return token.value;
    }

    this.parseCommand = function () {
        var indent = this.parseIndent();

        if (indent === null)
            return null;

        var token = nextToken();

        if (isWord(token, 'return'))
            return new ReturnCommand(this.parseExpression());

        if (isWord(token, 'break'))
            return new BreakCommand();

        if (isWord(token, 'continue'))
            return new ContinueCommand();

        if (isWord(token, 'if')) {
            var expr = this.parseExpression();
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new IfCommand(expr, commands);
        }

        if (isWord(token, 'while')) {
            var expr = this.parseExpression();
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new WhileCommand(expr, commands);
        }

        if (isWord(token, 'def')) {
            var name = parseName();
            var args = [];

            parseToken('(', TokenType.Punctuation);

            var token = nextToken();

            if (token && token.type == TokenType.Name) {
                args.push(token.value);
            }
            else
                pushToken(token);

            parseToken(')', TokenType.Punctuation);
            parseToken(':', TokenType.Punctuation);
            var commands = parseSuite(indent);
            return new DefCommand(name, args, commands);
        }

        pushToken(token);

        var expr = this.parseExpression();

        token = nextToken();

        if (!token || token.type !== TokenType.Operator || token.value !== '=') {
            pushToken(token);
            return new ExpressionCommand(expr);
        }

        var expr2 = this.parseExpression();

        return new AssignmentCommand(expr, expr2);
    };

    this.parseCommands = function () {
        var commands = [];

        for (var command = this.parseCommand(); command; command = this.parseCommand())
            commands.push(command);

        return new CompositeCommand(commands);
    };

    function parseSuite(indent) {
        var commands = [];

        for (var ind = self.parseIndent(); ind && ind > indent; ind = self.parseIndent()) {
            pushToken({ value: ind, type: TokenType.Indent });
            commands.push(self.parseCommand());
        }

        if (ind !== null)
            pushToken({ value: ind, type: TokenType.Indent });

        return new CompositeCommand(commands);
    }

    function parseTerm() {
        var expr = parseSimpleTerm();

        if (expr === null)
            return null;

        var token = nextToken();

        if (isPunctuation(token, '(')) {
            var args = parseArguments();
            expr = new CallExpression(expr, args);
        }
        else
            pushToken(token);

        return expr;
    }

    function parseSimpleTerm() {
        var token = nextToken();

        if (token === null)
            return null;

        if (token.type === TokenType.Name)
            return new NameExpression(token.value);

        if (token.type === TokenType.String)
            return new StringExpression(token.value);

        if (token.type === TokenType.Punctuation && token.value === '(') {
            var expr = self.parseExpression();
            parseToken(')', TokenType.Punctuation);

            return new ParensExpression(expr);
        }

        if (token.type === TokenType.Integer)
            return new ConstantExpression(token.value);

        if (token.type === TokenType.Indent)
            throw "Unexpected indent";

        throw "Unexpected '" + token.value + "'";
    }

    function parseArguments() {
        var args = [];
        var token = nextToken();

        if (!isPunctuation(token, ')')) {
            pushToken(token);

            while (true) {
                args.push(self.parseExpression());
                token = nextToken();
                if (!isPunctuation(token, ','))
                    break;
            }
        }

        if (!isPunctuation(token, ')'))
            throw "Expected ')'";

        return args;
    }

    function nextToken() {
        if (tokens.length)
            return tokens.pop();

        return lexr.nextToken();
    }

    function pushToken(token) {
        tokens.push(token);
    }

    function parseToken(value, type) {
        var token = nextToken();

        if (!token || token.value !== value || token.type !== type)
            throw "Expected '" + value + "'";
    }

    function parseName() {
        var token = nextToken();

        if (!token || token.type !== TokenType.Name)
            throw "Name expected";

        return token.value;
    }
}

function isWord(token, name)
{
    return token && token.type === TokenType.Name && token.value === name;
}

function isPunctuation(token, value)
{
    return token && token.type === TokenType.Punctuation && token.value === value;
}

var binaryoperators = ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='];

function isBinaryOperator(token)
{
    return token && token.type === TokenType.Operator && binaryoperators.indexOf(token.value) >= 0;
}

function createParser(text) {
    return new Parser(text);
}

module.exports = {
    createParser: createParser
}

<|endoftext|>