<commit_before>module.exports = function BlastObject(Blast, Collection) {

	/**
	 * Return a string representing the source code of the object.
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param    {Boolean|Number}   tab   If indent should be used
	 *
	 * @return   {String}
	 */
	Blast.definePrototype('Object', 'toSource', function toSource(tab) {

		var result = '',
		    passtab,
		    type,
		    key;

		if (tab === true) {
			tab = 1;
		}

		if (tab > 0) {
			passtab = tab + 1;
		} else {
			passtab = tab;
			tab = 0;
		}

		for (key in this) {
			if (this.hasOwnProperty(key)) {

				type = typeof this[key];

				if (!result) {
					result = '({';
				} else {
					result += ',';
				}

				if (tab) {
					result += '\n';
				}

				result += Blast.Bound.String.multiply('\t', tab) + JSON.stringify(key) + ': ';
				result += Blast.uneval(this[key], passtab);
			}
		}

		if (!result) {
			result = '({';
		} else {
			if (tab) {
				result += '\n' + Blast.Bound.String.multiply('\t', tab-1);
			}
		}

		result += '})';

		return result;
	}, true);

	/**
	 * Check if the argument is actually an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isObject', function isObject(obj) {

		if (!obj) {
			return false;
		}

		return typeof obj === 'object';
	});

	/**
	 * Check if the argument is a plain object
	 * (created with an object literal or new Object)
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isPlainObject', function isPlainObject(obj) {

		if (!obj) {
			return false;
		}

		return Object.prototype.toString.call(obj) === '[object Object]';
	});

	/**
	 * Check if the argument is the object form of a primitive
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isPrimitiveObject', function isPrimitiveObject(obj) {

		var id;

		// If the argument isn't even an object, return false
		if (!obj || typeof obj !== 'object') {
			return false;
		}

		// Get the constructor name
		id = obj.constructor.name;

		// If it's constructed by one of the 3 primitives, return true
		if (id == 'String' || id == 'Number' || id == 'Boolean') {
			return true;
		}

		return false;
	});

	/**
	 * Create a new object for every key-value and wrap them in an array
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}    obj            The object to arrayify
	 *
	 * @return   {Array}
	 */
	Blast.defineStatic('Object', 'divide', function divide(obj) {

		var list = [],
		    temp,
		    key;

		for (key in obj) {
			temp = {};
			temp[key] = obj[key];

			list[list.length] = temp;
		}

		return list;
	});

	/**
	 * Get the value of the given property path
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.1.2
	 *
	 * @param    {Object}   obj
	 * @param    {String}   path   The dot notation path
	 *
	 * @return   {Mixed}
	 */
	Blast.defineStatic('Object', 'path', function path(obj, path) {

		var pieces,
		    result,
		    here,
		    key,
		    end,
		    i;

		if (typeof path !== 'string') {
			if (Array.isArray(path)) {
				pieces = path;
			} else {
				return;
			}
		} else {
			pieces = [];

			for (i = 1; i < arguments.length; i++) {
				pieces = pieces.concat(arguments[i].split('.'));
			}
		}

		here = obj;

		// Go over every piece in the path
		for (i = 0; i < pieces.length; i++) {

			// Get the current key
			key = pieces[i];

			if (here !== null && here !== undefined) {
				here = here[key];

				// Is this the final piece?
				end = ((i+1) == pieces.length);

				if (end) {
					result = here;
				}
			}
		}

		return result;
	});

	/**
	 * See if the given path exists inside an object,
	 * even if that value is undefined
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}   obj
	 * @param    {String}   path   The dot notation path
	 *
	 * @return   {Mixed}
	 */
	Blast.defineStatic('Object', 'exists', function exists(obj, path) {

		var pieces = path.split('.'),
		    result = false,
		    hereType,
		    here,
		    key,
		    end,
		    i;

		// Set the object as the current position
		here = obj;

		// Go over every piece in the path
		for (i = 0; i < pieces.length; i++) {

			// Get the current key
			key = pieces[i];
			hereType = typeof here;

			if (here === null || here === undefined) {
				return false;
			}

			if (here !== null && here !== undefined) {

				// Is this the final piece?
				end = ((i+1) == pieces.length);

				if (end) {
					if (here[key] || ((hereType == 'object' || hereType == 'function') && key in here)) {
						result = true;
					}
					break;
				}

				here = here[key];
			}
		}

		return result;
	});

	/**
	 * Determine if the object is empty
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}   obj
	 * @param    {Boolean}  includePrototype   If true, prototypal properties also count
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isEmpty', function isEmpty(obj, includePrototype) {

		var key;

		if (!obj) {
			return true;
		}

		for(key in obj) {
			if (includePrototype || obj.hasOwnProperty(key)) {
				return false;
			}
		}

		return true;
	});

	/**
	 * Get an array of the object values
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param    {Object}   obj
	 * @param    {Boolean}  includePrototype   If true, prototypal properties also count
	 *
	 * @return   {Array}
	 */
	Blast.defineStatic('Object', 'values', function isEmpty(obj, includePrototype) {

		var result = [],
		    key;

		if (!obj) {
			return result;
		}

		for(key in obj) {
			if (includePrototype || obj.hasOwnProperty(key)) {
				result[result.length] = obj[key];
			}
		}

		return result;
	}, true);

	/**
	 * Inject the properties of one object into another target object
	 *
	 * @author   Jelle De Loecker   <jelle@kipdola.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param   {Object}   target     The object to inject the extension into
	 * @param   {Object}   extension  The object to inject
	 *
	 * @returns {Object}   Returns the injected target (which it also modifies byref)
	 */
	Blast.defineStatic('Object', 'assign', function assign(target, first, second) {

		var length = arguments.length, extension, key, i;

		// Go over every argument, other than the first
		for (i = 1; i <= length; i++) {
			extension = arguments[i];

			// If the given extension isn't valid, continue
			if (!extension) continue;

			// Go over every property of the current object
			for (key in extension) {
				target[key] = extension[key];
			}
		}

		return target;
	}, true);

	/**
	 * Convert an array to an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param    {Mixed}   source
	 * @param    {Boolean} recursive
	 * @param    {Mixed}   value
	 *
	 * @return   {Object}
	 */
	Blast.defineStatic('Object', 'objectify', function objectify(source, recursive, value) {

		var result = {},
		    temp,
		    type,
		    key,
		    i;

		if (typeof value == 'undefined') {
			value = true;
		}

		if (Array.isArray(source)) {
			for (i = 0; i < source.length; i++) {

				if (typeof source[i] !== 'object') {
					result[source[i]] = value;
				} else if (Array.isArray(source[i])) {
					Collection.Object.assign(result, Object.objectify(source[i], recursive, value));
				} else {
					Collection.Object.assign(result, source[i]);
				}
			}
		} else {
			Collection.Object.assign(result, source);
		}

		for (key in result) {
			type = typeof result[key];

			if (type == 'object') {
				if (recursive) {
					result[key] = Collection.Object.objectify(result[key], true, value);
				}
			}
		}

		return result;
	});

	/**
	 * Iterate over an object's properties
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}     obj
	 * @param    {Function}   fnc
	 */
	Blast.defineStatic('Object', 'each', function each(obj, fnc) {

		var key;

		for (key in obj) {
			fnc(obj[key], key, obj);
		}
	});

	/**
	 * Map an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}     obj
	 * @param    {Function}   fnc
	 *
	 * @return   {Object}
	 */
	Blast.defineStatic('Object', 'map', function map(obj, fnc) {

		var mapped = {};

		Collection.Object.each(obj, function mapEach(value, key) {
			mapped[key] = fnc(value, key, obj);
		});

		return mapped;
	});

	/**
	 * Get the key of a value in an array or object.
	 * If the value is not found a false is returned (not -1 for arrays)
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target   The object to search in
	 * @param     {Object}         value    The value to look for
	 *
	 * @return    {String|Number|Boolean}
	 */
	Blast.defineStatic('Object', 'getValueKey', function getValueKey(target, value) {

		var result, key;

		if (target) {

			if (Array.isArray(target)) {
				result = target.indexOf(value);

				if (result > -1) {
					return result;
				}
			} else {
				for (key in target) {
					if (target[key] == value) {
						return key;
					}
				}
			}
		}

		return false;
	});

	/**
	 * See if a key exists in an object or array
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target      The object to search in
	 * @param     {String}          property   The key to look for
	 *
	 * @return    {Boolean}
	 */
	Blast.defineStatic('Object', 'hasProperty', function hasProperty(target, property) {

		if (target && (typeof target[property] !== 'undefined' || property in target)) {
			return true;
		}

		return false;
	});

	/**
	 * Look for a value in an object or array
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target   The object to search in
	 * @param     {Object}         value    The value to look for
	 *
	 * @return    {Boolean}
	 */
	Blast.defineStatic('Object', 'hasValue', function hasValue(target, value) {
		return !(Collection.Object.getValueKey(target, value) === false);
	});

	/**
	 * Uneval
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object}   obj
	 *
	 * @return    {String}
	 */
	Blast.defineStatic('Object', 'uneval', Blast.uneval);

};<commit_msg>Improve Object.objectify<commit_after>module.exports = function BlastObject(Blast, Collection) {

	/**
	 * Return a string representing the source code of the object.
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param    {Boolean|Number}   tab   If indent should be used
	 *
	 * @return   {String}
	 */
	Blast.definePrototype('Object', 'toSource', function toSource(tab) {

		var result = '',
		    passtab,
		    type,
		    key;

		if (tab === true) {
			tab = 1;
		}

		if (tab > 0) {
			passtab = tab + 1;
		} else {
			passtab = tab;
			tab = 0;
		}

		for (key in this) {
			if (this.hasOwnProperty(key)) {

				type = typeof this[key];

				if (!result) {
					result = '({';
				} else {
					result += ',';
				}

				if (tab) {
					result += '\n';
				}

				result += Blast.Bound.String.multiply('\t', tab) + JSON.stringify(key) + ': ';
				result += Blast.uneval(this[key], passtab);
			}
		}

		if (!result) {
			result = '({';
		} else {
			if (tab) {
				result += '\n' + Blast.Bound.String.multiply('\t', tab-1);
			}
		}

		result += '})';

		return result;
	}, true);

	/**
	 * Check if the argument is actually an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isObject', function isObject(obj) {

		if (!obj) {
			return false;
		}

		return typeof obj === 'object';
	});

	/**
	 * Check if the argument is a plain object
	 * (created with an object literal or new Object)
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isPlainObject', function isPlainObject(obj) {

		if (!obj) {
			return false;
		}

		return Object.prototype.toString.call(obj) === '[object Object]';
	});

	/**
	 * Check if the argument is the object form of a primitive
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.2
	 * @version  0.1.2
	 *
	 * @param    {Object}    obj
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isPrimitiveObject', function isPrimitiveObject(obj) {

		var id;

		// If the argument isn't even an object, return false
		if (!obj || typeof obj !== 'object') {
			return false;
		}

		// Get the constructor name
		id = obj.constructor.name;

		// If it's constructed by one of the 3 primitives, return true
		if (id == 'String' || id == 'Number' || id == 'Boolean') {
			return true;
		}

		return false;
	});

	/**
	 * Create a new object for every key-value and wrap them in an array
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}    obj            The object to arrayify
	 *
	 * @return   {Array}
	 */
	Blast.defineStatic('Object', 'divide', function divide(obj) {

		var list = [],
		    temp,
		    key;

		for (key in obj) {
			temp = {};
			temp[key] = obj[key];

			list[list.length] = temp;
		}

		return list;
	});

	/**
	 * Get the value of the given property path
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.1.2
	 *
	 * @param    {Object}   obj
	 * @param    {String}   path   The dot notation path
	 *
	 * @return   {Mixed}
	 */
	Blast.defineStatic('Object', 'path', function path(obj, path) {

		var pieces,
		    result,
		    here,
		    key,
		    end,
		    i;

		if (typeof path !== 'string') {
			if (Array.isArray(path)) {
				pieces = path;
			} else {
				return;
			}
		} else {
			pieces = [];

			for (i = 1; i < arguments.length; i++) {
				pieces = pieces.concat(arguments[i].split('.'));
			}
		}

		here = obj;

		// Go over every piece in the path
		for (i = 0; i < pieces.length; i++) {

			// Get the current key
			key = pieces[i];

			if (here !== null && here !== undefined) {
				here = here[key];

				// Is this the final piece?
				end = ((i+1) == pieces.length);

				if (end) {
					result = here;
				}
			}
		}

		return result;
	});

	/**
	 * See if the given path exists inside an object,
	 * even if that value is undefined
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}   obj
	 * @param    {String}   path   The dot notation path
	 *
	 * @return   {Mixed}
	 */
	Blast.defineStatic('Object', 'exists', function exists(obj, path) {

		var pieces = path.split('.'),
		    result = false,
		    hereType,
		    here,
		    key,
		    end,
		    i;

		// Set the object as the current position
		here = obj;

		// Go over every piece in the path
		for (i = 0; i < pieces.length; i++) {

			// Get the current key
			key = pieces[i];
			hereType = typeof here;

			if (here === null || here === undefined) {
				return false;
			}

			if (here !== null && here !== undefined) {

				// Is this the final piece?
				end = ((i+1) == pieces.length);

				if (end) {
					if (here[key] || ((hereType == 'object' || hereType == 'function') && key in here)) {
						result = true;
					}
					break;
				}

				here = here[key];
			}
		}

		return result;
	});

	/**
	 * Determine if the object is empty
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}   obj
	 * @param    {Boolean}  includePrototype   If true, prototypal properties also count
	 *
	 * @return   {Boolean}
	 */
	Blast.defineStatic('Object', 'isEmpty', function isEmpty(obj, includePrototype) {

		var key;

		if (!obj) {
			return true;
		}

		for(key in obj) {
			if (includePrototype || obj.hasOwnProperty(key)) {
				return false;
			}
		}

		return true;
	});

	/**
	 * Get an array of the object values
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param    {Object}   obj
	 * @param    {Boolean}  includePrototype   If true, prototypal properties also count
	 *
	 * @return   {Array}
	 */
	Blast.defineStatic('Object', 'values', function isEmpty(obj, includePrototype) {

		var result = [],
		    key;

		if (!obj) {
			return result;
		}

		for(key in obj) {
			if (includePrototype || obj.hasOwnProperty(key)) {
				result[result.length] = obj[key];
			}
		}

		return result;
	}, true);

	/**
	 * Inject the properties of one object into another target object
	 *
	 * @author   Jelle De Loecker   <jelle@kipdola.be>
	 * @since    0.1.0
	 * @version  0.1.0
	 *
	 * @param   {Object}   target     The object to inject the extension into
	 * @param   {Object}   extension  The object to inject
	 *
	 * @returns {Object}   Returns the injected target (which it also modifies byref)
	 */
	Blast.defineStatic('Object', 'assign', function assign(target, first, second) {

		var length = arguments.length, extension, key, i;

		// Go over every argument, other than the first
		for (i = 1; i <= length; i++) {
			extension = arguments[i];

			// If the given extension isn't valid, continue
			if (!extension) continue;

			// Go over every property of the current object
			for (key in extension) {
				target[key] = extension[key];
			}
		}

		return target;
	}, true);

	/**
	 * Convert an array to an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.1.0
	 * @version  0.1.2
	 *
	 * @param    {Mixed}   source
	 * @param    {Boolean} recursive
	 * @param    {Mixed}   value
	 *
	 * @return   {Object}
	 */
	Blast.defineStatic('Object', 'objectify', function objectify(source, recursive, value) {

		var result = {},
		    temp,
		    type,
		    key,
		    i;

		if (typeof value == 'undefined') {
			value = true;
		}

		// Convert object form of primitives to their primitive states
		if (Collection.Object.isPrimitiveObject(source)) {
			source = source.valueOf();
		}

		if (Array.isArray(source)) {
			for (i = 0; i < source.length; i++) {

				if (typeof source[i] !== 'object') {
					result[source[i]] = value;
				} else if (Array.isArray(source[i])) {
					Collection.Object.assign(result, Object.objectify(source[i], recursive, value));
				} else {
					Collection.Object.assign(result, source[i]);
				}
			}
		} else if (Collection.Object.isObject(source)) {
			Collection.Object.assign(result, source);
		} else {
			if (typeof source !== 'boolean' && (source || typeof source !== 'undefined')) {
				result[source] = value;
			}
		}

		if (!recursive) {
			return result;
		}

		for (key in result) {
			type = typeof result[key];

			if (type == 'object') {
				result[key] = Collection.Object.objectify(result[key], true, value);
			}
		}

		return result;
	});

	/**
	 * Iterate over an object's properties
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}     obj
	 * @param    {Function}   fnc
	 */
	Blast.defineStatic('Object', 'each', function each(obj, fnc) {

		var key;

		for (key in obj) {
			fnc(obj[key], key, obj);
		}
	});

	/**
	 * Map an object
	 *
	 * @author   Jelle De Loecker   <jelle@codedor.be>
	 * @since    0.0.1
	 * @version  0.0.1
	 *
	 * @param    {Object}     obj
	 * @param    {Function}   fnc
	 *
	 * @return   {Object}
	 */
	Blast.defineStatic('Object', 'map', function map(obj, fnc) {

		var mapped = {};

		Collection.Object.each(obj, function mapEach(value, key) {
			mapped[key] = fnc(value, key, obj);
		});

		return mapped;
	});

	/**
	 * Get the key of a value in an array or object.
	 * If the value is not found a false is returned (not -1 for arrays)
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target   The object to search in
	 * @param     {Object}         value    The value to look for
	 *
	 * @return    {String|Number|Boolean}
	 */
	Blast.defineStatic('Object', 'getValueKey', function getValueKey(target, value) {

		var result, key;

		if (target) {

			if (Array.isArray(target)) {
				result = target.indexOf(value);

				if (result > -1) {
					return result;
				}
			} else {
				for (key in target) {
					if (target[key] == value) {
						return key;
					}
				}
			}
		}

		return false;
	});

	/**
	 * See if a key exists in an object or array
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target      The object to search in
	 * @param     {String}          property   The key to look for
	 *
	 * @return    {Boolean}
	 */
	Blast.defineStatic('Object', 'hasProperty', function hasProperty(target, property) {

		if (target && (typeof target[property] !== 'undefined' || property in target)) {
			return true;
		}

		return false;
	});

	/**
	 * Look for a value in an object or array
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object|Array}   target   The object to search in
	 * @param     {Object}         value    The value to look for
	 *
	 * @return    {Boolean}
	 */
	Blast.defineStatic('Object', 'hasValue', function hasValue(target, value) {
		return !(Collection.Object.getValueKey(target, value) === false);
	});

	/**
	 * Uneval
	 *
	 * @author    Jelle De Loecker   <jelle@codedor.be>
	 * @since     0.1.0
	 * @version   0.1.0
	 *
	 * @param     {Object}   obj
	 *
	 * @return    {String}
	 */
	Blast.defineStatic('Object', 'uneval', Blast.uneval);

};<|endoftext|>


<commit_before>/*
   All emon_widgets code is released under the GNU General Public License v3.
   See COPYRIGHT.txt and LICENSE.txt.

    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

// Convenience function for shoving things into the widget object
// I'm not sure about calling optionKey "optionKey", but I don't want to just use "options" (because that's what this whole function returns), and it's confusing enough as it is.
function addOption(widget, optionKey, optionType, optionName, optionHint, optionData){
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function dial_widgetlist(){
  var widgets =
  {
    "dial":
    {
      "offsetx":-80,"offsety":-80,"width":160,"height":160,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]

    }
  };

  var typeDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
          [0,    "Light <-> dark green, Zero at left"],
          [1,    "Red <-> Green, Zero at center"],
          [2,    "Green <-> Red, Zero at left"],
          [3,    "Green <-> Red, Zero at center"],
          [4,    "Red <-> Green, Zero at left"],
          [5,    "Red <-> Green, Zero at center"],
          [6,    "Green center <-> orange edges, Zero at center "],
          [7,    "Light <-> Dark blue, Zero at left"],
          [8,    "Light blue <-> Red, Zero at mid-left"],
          [9,    "Red <-> Dark Red, Zero at left"],
          [10,   "Black <-> White, Zero at left"],
          [11,   "Blue <-> Red, Zero at upper-left"]
        ];

  var graduationDropBoxOptions = [
          [1, "Yes"],
          [0, "No"]
        ]

  addOption(widgets["dial"], "feedid",      "feedid",  _Tr("Feed"),        _Tr("Feed value"),                                                            []);
  addOption(widgets["dial"], "max",         "value",   _Tr("Max value"),   _Tr("Max value to show"),                                                     []);
  addOption(widgets["dial"], "scale",       "value",   _Tr("Scale"),       _Tr("Value is multiplied by scale before display"),                           []);
  addOption(widgets["dial"], "units",       "value",   _Tr("Units"),       _Tr("Units to show"),                                                         []);
  addOption(widgets["dial"], "offset",      "value",   _Tr("Offset"),      _Tr("Static offset. Subtracted from value before computing needle position"), []);
  addOption(widgets["dial"], "type",        "dropbox", _Tr("Type"),        _Tr("Type to show"),                                                          typeDropBoxOptions);
  addOption(widgets["dial"], "graduations", "dropbox", _Tr("Graduations"), _Tr("Should the graduation limits be shown"),                                 graduationDropBoxOptions);

  return widgets;
}

function dial_init(){
  setup_widget_canvas('dial');
}

function dial_draw(){
  $('.dial').each(function(index) {
    var feedid = $(this).attr("feedid");
    if (associd[feedid] === undefined) { console.log("Review config for feed id of " + $(this).attr("class")); return; }
    var val = curve_value(feedid,dialrate);
    // ONLY UPDATE ON CHANGE
    if ((val * 1).toFixed(2) != (associd[feedid]['value'] * 1).toFixed(2) || redraw == 1)
    {
      var id = "can-"+$(this).attr("id");
      var scale = 1*$(this).attr("scale") || 1;
      draw_gauge(widgetcanvas[id],
                 0,
                 0,
                 $(this).width(),
                 $(this).height(),
                 val*scale,
                 $(this).attr("max"),
                 $(this).attr("units"),
                 $(this).attr("type"),
                 $(this).attr("offset"),
                 $(this).attr("graduations"));
    }
  });
}

function dial_slowupdate(){}

function dial_fastupdate(){
  dial_draw();
}

function deg_to_radians(deg){
  return deg * (Math.PI / 180)
}
function polar_to_cart(mag, ang, xOff, yOff){
  ang = deg_to_radians(ang);
  var x = mag * Math.cos(ang) + xOff;
  var y = mag * Math.sin(ang) + yOff;
  return [x, y];
}

// X, Y are the center coordinates of the canvas
function draw_gauge(ctx,x,y,width,height,position,maxvalue,units,type, offset, graduationBool){
  if (!ctx) return;

  // if (1 * maxvalue) == false: 3000. Else 1 * maxvalue
  maxvalue = 1 * maxvalue || 3000;
  // if units == false: "". Else units
  units = units || "";
  offset = 1*offset || 0;
  var val = position
  position = position-offset

  var size = 0;
  if (width<height) size = width/2;
  else size = height/2;
  size = size - (size*0.058/2);

  x = width/2;
  y = height/2;

  ctx.clearRect(0,0,200,200);

  if (!position) position = 0;

  var angleOffset = 0;
  var segment = ["#c0e392","#9dc965","#87c03f","#70ac21","#378d42","#046b34"];

  type = type || 0;

  // Depending on type the start value is calculated.
  // The maximum value is defined via settings and is defined as the value to reach needle's end limit.
  // Depending on dial's type start limit is either 0 or a negative value.
  // Note: Should consider a type which enables defiable min value in futur extension.

  if (type == 0){ // Standard dial from 0 to maxvalue if offset is not set.
    // TODO: seperate between needle position at maximum/minimum and the value displayed.
    // TODO: Do we need to limit the value being displayed? Only needle position should be limited.
    if (position<0)
      position = 0;
  }
  else if (type == 1){
    angleOffset = -0.75;
    segment = ["#e61703","#ff6254","#ffa29a","#70ac21","#378d42","#046b34"];
  }
  else if (type == 2){
    if (position<0)
      position = 0;
    segment = ["#046b34","#378d42","#87c03f","#f8a01b","#f46722","#bf2025"];
  }
  else if (type == 3){
    angleOffset = -0.75;
    segment = ["#046b34","#378d42","#87c03f","#f8a01b","#f46722","#bf2025"];
  }
  else if (type == 4){
    if (position<0)
      position = 0;
    segment = ["#bf2025","#f46722","#f8a01b","#87c03f","#378d42","#046b34"];
  }
  else if (type == 5){
    angleOffset = -0.75;
    segment = ["#bf2025","#f46722","#f8a01b","#87c03f","#378d42","#046b34"];
  }
  else if (type == 6){
    angleOffset = -0.75;
    segment = ["#f46722","#f8a01b","#87c03f","#87c03f","#f8a01b","#f46722"];
  }
  else if (type == 7){
    if (position<0)
      position = 0;
    segment = ["#a7cbe2","#68b7eb","#0d97f3","#0f81d0","#0c6dae","#08578e"];
  }
  else if (type == 8){  //temperature dial blue-red, first segment blue should mean below freezing C
    angleOffset = -0.25;
    segment = ["#b7beff","#ffd9d9","#ffbebe","#ff9c9c","#ff6e6e","#ff3d3d"];
  }
  else if (type == 9){  //temperature dial blue-red, first segment blue should mean below freezing C
    angleOffset = 0;
    segment = ["#e94937","#da4130","#c43626","#ad2b1c","#992113","#86170a"];
  }
  else if (type == 10){ //light: from dark grey to white
    if (position<0)
      position = 0;
    segment = ["#202020","#4D4D4D","#7D7D7D","#EEF0F3","#F7F7F7", "#FFFFFF"];
  }
  else if (type == 11){  //temperature dial blue-red, first 2 segments blue should mean below freezing C
    angleOffset = -0.5;
    segment = ["#0d97f3","#a7cbe2","#ffbebe","#ff8383","#ff6464","#ff3d3d"];
  }

  if (position>maxvalue) position = maxvalue;

  // needle values and their corresponding direction
  // South West (limit start) a = 1.75
  // West: .. ............... a = 1.5
  // North West: ............ a = 1.25
  // North: ................. a = 1
  // North East: ............ a = 0.75
  // East: .................. a = 0.5
  // South East (limit stop)  a = 0.25
  var needle = 1.75 - ((position/maxvalue) * (1.5+angleOffset)) + angleOffset;

  width = 0.785;
  var c=3*0.785;
  var pos = 0;
  var inner = size * 0.48;

  // Segments
  for (var z in segment){
    ctx.fillStyle = segment[z];
    ctx.beginPath();
    ctx.arc(x,y,size,c+pos,c+pos+width+0.01,false);
    ctx.lineTo(x,y);
    ctx.closePath();
    ctx.fill();
    pos += width;
  }
  pos -= width;
  ctx.lineWidth = (size*0.058).toFixed(0);
  pos += width;
  ctx.strokeStyle = "#fff";
  ctx.beginPath();
  ctx.arc(x,y,size,c,c+pos,false);
  ctx.lineTo(x,y);
  ctx.closePath();
  ctx.stroke();

  ctx.fillStyle = "#666867";
  ctx.beginPath();
  ctx.arc(x,y,inner,0,Math.PI*2,true);
  ctx.closePath();
  ctx.fill();

  ctx.lineWidth = (size*0.052).toFixed(0);
  //---------------------------------------------------------------
  ctx.beginPath();
  ctx.moveTo(x+Math.sin(Math.PI*needle-0.2)*inner,y+Math.cos(Math.PI*needle-0.2)*inner);
  ctx.lineTo(x+Math.sin(Math.PI*needle)*size,y+Math.cos(Math.PI*needle)*size);
  ctx.lineTo(x+Math.sin(Math.PI*needle+0.2)*inner,y+Math.cos(Math.PI*needle+0.2)*inner);
  ctx.arc(x,y,inner,1-(Math.PI*needle-0.2),1-(Math.PI*needle+5.4),true);
  ctx.closePath();
  ctx.fill();
  ctx.stroke();

  if (isNaN(val))
     val = 0;
  else if (val>=100)
     val = val.toFixed(0);
  else if (val>=10)
     val = val.toFixed(1);
  else if (val<=-100)
     val = val.toFixed(0);
  else if (val<=-10)
     val = val.toFixed(1);
  else
     val = val.toFixed(2);

  var dialtext = val+units;
  var textsize = (size / (dialtext.length+2)) * 6;

  ctx.fillStyle = "#fff";
  ctx.textAlign    = "center";
  ctx.font = "bold "+(textsize*0.26)+"px arial";

  ctx.fillText(val+units,x,y+(textsize*0.125));

  ctx.fillStyle = "#000";
  var spreadAngle = 32;

  if (graduationBool == 1){
    ctx.font = "bold "+(size*0.22)+"px arial";

    var posStrt = polar_to_cart(size/1.15, 90+spreadAngle, x, y);
    var posStop = polar_to_cart(size/1.15, 90-spreadAngle, x, y);

    ctx.save()
    ctx.translate(posStrt[0], posStrt[1]);
    ctx.rotate(deg_to_radians(-45));

    // graduation text - start limit
    ctx.fillText(""+round1decimal( (2*angleOffset/3*1.5*maxvalue)+offset )+units, 0, 0);        // Since we've translated the entire context, the coords we want to draw at are now at [0,0]
    ctx.restore();

    ctx.save(); // each ctx.save is only good for one restore, apparently.
    ctx.translate(posStop[0], posStop[1]);
    ctx.rotate(deg_to_radians(45));

    // graduation text - end limit
    ctx.fillText(""+round1decimal(offset+maxvalue)+units, 0, 0);
    ctx.restore();
  }

  function round1decimal(x) {
    Ergebnis = Math.round(x * 10) / 10 ;
    return Ergebnis;
  }

  function round2decimal(x) {
    Ergebnis = Math.round(x * 100) / 100 ;
    return Ergebnis;
  }

}
<commit_msg>Fix dial widget start limit issues<commit_after>/*
   All emon_widgets code is released under the GNU General Public License v3.
   See COPYRIGHT.txt and LICENSE.txt.

    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

// Convenience function for shoving things into the widget object
// I'm not sure about calling optionKey "optionKey", but I don't want to just use "options" (because that's what this whole function returns), and it's confusing enough as it is.
function addOption(widget, optionKey, optionType, optionName, optionHint, optionData){
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function dial_widgetlist(){
  var widgets =
  {
    "dial":
    {
      "offsetx":-80,"offsety":-80,"width":160,"height":160,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]

    }
  };

  var typeDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
          [0,    "Light <-> dark green, Zero at left"],
          [1,    "Red <-> Green, Zero at center"],
          [2,    "Green <-> Red, Zero at left"],
          [3,    "Green <-> Red, Zero at center"],
          [4,    "Red <-> Green, Zero at left"],
          [5,    "Red <-> Green, Zero at center"],
          [6,    "Green center <-> orange edges, Zero at center "],
          [7,    "Light <-> Dark blue, Zero at left"],
          [8,    "Light blue <-> Red, Zero at mid-left"],
          [9,    "Red <-> Dark Red, Zero at left"],
          [10,   "Black <-> White, Zero at left"],
          [11,   "Blue <-> Red, Zero at upper-left"]
        ];

  var graduationDropBoxOptions = [
          [1, "Yes"],
          [0, "No"]
        ]

  addOption(widgets["dial"], "feedid",      "feedid",  _Tr("Feed"),        _Tr("Feed value"),                                                            []);
  addOption(widgets["dial"], "max",         "value",   _Tr("Max value"),   _Tr("Max value to show"),                                                     []);
  addOption(widgets["dial"], "scale",       "value",   _Tr("Scale"),       _Tr("Value is multiplied by scale before display"),                           []);
  addOption(widgets["dial"], "units",       "value",   _Tr("Units"),       _Tr("Units to show"),                                                         []);
  addOption(widgets["dial"], "offset",      "value",   _Tr("Offset"),      _Tr("Static offset. Subtracted from value before computing needle position"), []);
  addOption(widgets["dial"], "type",        "dropbox", _Tr("Type"),        _Tr("Type to show"),                                                          typeDropBoxOptions);
  addOption(widgets["dial"], "graduations", "dropbox", _Tr("Graduations"), _Tr("Should the graduation limits be shown"),                                 graduationDropBoxOptions);

  return widgets;
}

function dial_init(){
  setup_widget_canvas('dial');
}

function dial_draw(){
  $('.dial').each(function(index) {
    var feedid = $(this).attr("feedid");
    if (associd[feedid] === undefined) { console.log("Review config for feed id of " + $(this).attr("class")); return; }
    var val = curve_value(feedid,dialrate);
    // ONLY UPDATE ON CHANGE
    if ((val * 1).toFixed(2) != (associd[feedid]['value'] * 1).toFixed(2) || redraw == 1)
    {
      var id = "can-"+$(this).attr("id");
      var scale = 1*$(this).attr("scale") || 1;
      draw_gauge(widgetcanvas[id],
                 0,
                 0,
                 $(this).width(),
                 $(this).height(),
                 val*scale,
                 $(this).attr("max"),
                 $(this).attr("units"),
                 $(this).attr("type"),
                 $(this).attr("offset"),
                 $(this).attr("graduations"));
    }
  });
}

function dial_slowupdate(){}

function dial_fastupdate(){
  dial_draw();
}

function deg_to_radians(deg){
  return deg * (Math.PI / 180)
}
function polar_to_cart(mag, ang, xOff, yOff){
  ang = deg_to_radians(ang);
  var x = mag * Math.cos(ang) + xOff;
  var y = mag * Math.sin(ang) + yOff;
  return [x, y];
}

// X, Y are the center coordinates of the canvas
function draw_gauge(ctx,x,y,width,height,position,maxvalue,units,type, offset, graduationBool){
  if (!ctx) return;

  // if (1 * maxvalue) == false: 3000. Else 1 * maxvalue
  maxvalue = 1 * maxvalue || 3000;
  // if units == false: "". Else units
  units = units || "";
  offset = 1*offset || 0;
  var val = position
  position = position-offset

  var size = 0;
  if (width<height) size = width/2;
  else size = height/2;
  size = size - (size*0.058/2);

  x = width/2;
  y = height/2;

  ctx.clearRect(0,0,200,200);

  if (!position) position = 0;

  var angleOffset = 0;
  var segment = ["#c0e392","#9dc965","#87c03f","#70ac21","#378d42","#046b34"];

  type = type || 0;

  // Depending on type the start value is calculated.
  // The maximum value is defined via settings and is defined as the value to reach needle's end limit.
  // Depending on dial's type start limit is either 0 or a negative value.
  // Note: Should consider a type which enables defiable min value in futur extension.

  if (type == 0){ // Standard dial from 0 to maxvalue if offset is not set.
    // TODO: seperate between needle position at maximum/minimum and the value displayed.
    // TODO: Do we need to limit the value being displayed? Only needle position should be limited.
  }
  else if (type == 1){
    angleOffset = -0.75;
    segment = ["#e61703","#ff6254","#ffa29a","#70ac21","#378d42","#046b34"];
  }
  else if (type == 2){
    segment = ["#046b34","#378d42","#87c03f","#f8a01b","#f46722","#bf2025"];
  }
  else if (type == 3){
    angleOffset = -0.75;
    segment = ["#046b34","#378d42","#87c03f","#f8a01b","#f46722","#bf2025"];
  }
  else if (type == 4){
    segment = ["#bf2025","#f46722","#f8a01b","#87c03f","#378d42","#046b34"];
  }
  else if (type == 5){
    angleOffset = -0.75;
    segment = ["#bf2025","#f46722","#f8a01b","#87c03f","#378d42","#046b34"];
  }
  else if (type == 6){
    angleOffset = -0.75;
    segment = ["#f46722","#f8a01b","#87c03f","#87c03f","#f8a01b","#f46722"];
  }
  else if (type == 7){
    segment = ["#a7cbe2","#68b7eb","#0d97f3","#0f81d0","#0c6dae","#08578e"];
  }
  else if (type == 8){  //temperature dial blue-red, first segment blue should mean below freezing C
    angleOffset = -0.25;
    segment = ["#b7beff","#ffd9d9","#ffbebe","#ff9c9c","#ff6e6e","#ff3d3d"];
  }
  else if (type == 9){  //temperature dial blue-red, first segment blue should mean below freezing C
    angleOffset = 0;
    segment = ["#e94937","#da4130","#c43626","#ad2b1c","#992113","#86170a"];
  }
  else if (type == 10){ //light: from dark grey to white
    segment = ["#202020","#4D4D4D","#7D7D7D","#EEF0F3","#F7F7F7", "#FFFFFF"];
  }
  else if (type == 11){  //temperature dial blue-red, first 2 segments blue should mean below freezing C
    angleOffset = -0.5;
    segment = ["#0d97f3","#a7cbe2","#ffbebe","#ff8383","#ff6464","#ff3d3d"];
  }

  // needle values and their corresponding direction
  // South West (limit start) a = 1.75
  // West: .. ............... a = 1.5
  // North West: ............ a = 1.25
  // North: ................. a = 1
  // North East: ............ a = 0.75
  // East: .................. a = 0.5
  // South East (limit stop)  a = 0.25
  //
  // Calculation explanation:
  // Total needle space = 1.75 - 0.25 = 1.5 (from above).
  // (1.5 + angleOffset) = needle space from 0 to maxvalue (because angleOffset is negative).
  // (position / maxvalue) = fraction of how far round the needle space from 0 to maxvalue we need to be.
  // The rest follows from these.
  var needle = 1.75 - ((position/maxvalue) * (1.5+angleOffset)) + angleOffset;
  needle = Math.min(needle, 1.75);
  needle = Math.max(needle, 0.25);

  width = 0.785;
  var c=3*0.785;
  var pos = 0;
  var inner = size * 0.48;

  // Segments
  for (var z in segment){
    ctx.fillStyle = segment[z];
    ctx.beginPath();
    ctx.arc(x,y,size,c+pos,c+pos+width+0.01,false);
    ctx.lineTo(x,y);
    ctx.closePath();
    ctx.fill();
    pos += width;
  }
  pos -= width;
  ctx.lineWidth = (size*0.058).toFixed(0);
  pos += width;
  ctx.strokeStyle = "#fff";
  ctx.beginPath();
  ctx.arc(x,y,size,c,c+pos,false);
  ctx.lineTo(x,y);
  ctx.closePath();
  ctx.stroke();

  ctx.fillStyle = "#666867";
  ctx.beginPath();
  ctx.arc(x,y,inner,0,Math.PI*2,true);
  ctx.closePath();
  ctx.fill();

  ctx.lineWidth = (size*0.052).toFixed(0);
  //---------------------------------------------------------------
  ctx.beginPath();
  ctx.moveTo(x+Math.sin(Math.PI*needle-0.2)*inner,y+Math.cos(Math.PI*needle-0.2)*inner);
  ctx.lineTo(x+Math.sin(Math.PI*needle)*size,y+Math.cos(Math.PI*needle)*size);
  ctx.lineTo(x+Math.sin(Math.PI*needle+0.2)*inner,y+Math.cos(Math.PI*needle+0.2)*inner);
  ctx.arc(x,y,inner,1-(Math.PI*needle-0.2),1-(Math.PI*needle+5.4),true);
  ctx.closePath();
  ctx.fill();
  ctx.stroke();

  if (isNaN(val))
     val = 0;
  else if (val>=100)
     val = val.toFixed(0);
  else if (val>=10)
     val = val.toFixed(1);
  else if (val<=-100)
     val = val.toFixed(0);
  else if (val<=-10)
     val = val.toFixed(1);
  else
     val = val.toFixed(2);

  var dialtext = val+units;
  var textsize = (size / (dialtext.length+2)) * 6;

  ctx.fillStyle = "#fff";
  ctx.textAlign    = "center";
  ctx.font = "bold "+(textsize*0.26)+"px arial";

  ctx.fillText(val+units,x,y+(textsize*0.125));

  ctx.fillStyle = "#000";
  var spreadAngle = 32;

  if (graduationBool == 1){
    ctx.font = "bold "+(size*0.22)+"px arial";

    var posStrt = polar_to_cart(size/1.15, 90+spreadAngle, x, y);
    var posStop = polar_to_cart(size/1.15, 90-spreadAngle, x, y);

    ctx.save()
    ctx.translate(posStrt[0], posStrt[1]);
    ctx.rotate(deg_to_radians(-45));

    // graduation text - start limit
    // Calculation similar to the `needle` calculation above
    var start_limit = round1decimal((angleOffset * (maxvalue / (1.5 + angleOffset))) + offset);
    ctx.fillText(""+start_limit+units, 0, 0); // Since we've translated the entire context, the coords we want to draw at are now at [0,0]
    ctx.restore();

    ctx.save(); // each ctx.save is only good for one restore, apparently.
    ctx.translate(posStop[0], posStop[1]);
    ctx.rotate(deg_to_radians(45));

    // graduation text - end limit
    var end_limit = round1decimal(offset+maxvalue);
    ctx.fillText(""+end_limit+units, 0, 0);
    ctx.restore();
  }

  function round1decimal(x) {
    Ergebnis = Math.round(x * 10) / 10 ;
    return Ergebnis;
  }

  function round2decimal(x) {
    Ergebnis = Math.round(x * 100) / 100 ;
    return Ergebnis;
  }

}
<|endoftext|>


<commit_before>/*
  Parser
  Parses clean source. Generates clean AST.
*/
const opPrec = require('./operatorPrecedence')
const estemplate = require('./estemplate')
const updateAst = require('./astupdate')

/*  Utility functions  */
const mayBe = (value, func) => value === null ? null : func(...value)
const parenCheck = src => mayBe(parser.regex(/^(\))((.|\n)*)$/)(src),
                              (m, val, rest) => returnRest(val, src, src.str))

const returnRest = (val, input, rest, field) => {
  const output = JSON.parse(JSON.stringify(input))
  if (field) {
    let value = field.value
    switch (field.name) {
      case 'indent':
        output.indent = value
        output.column = value
        break
      case 'return':
        output.column = 1
        output.line += value
        break
      case 'column':
        output.column += value
        break
    }
  }
  output.str = rest
  return [val, output]
}

/*  Predefined regexes */
const spaceRegex = /^( +)((.|\n)*)$/
const returnRegex = /^(\n)((.|\n)*)$/
const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^((?:\d+(?:\.\d*)?|\.\d+)(?:[e][+-]?\d+)?)((.|\n)*)$/
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^('[^\\']*(?:\\.[^\\']*)*')((.|\n)*)$/
const binaryOperatorRegex = /^(\+\+|\+|-|\/|\*|<<|<=|<|>>>|>>|>=|>|&&|&|\|\||\||\^|==)((.|\n)*)$/
const unaryOperatorRegex = /^(:type|-|!)((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src.str),
  (m, val, rest) => returnRest(val, src, rest, {'name': 'column', 'value': val.length})
)

parser.bind = (mv, mf) => input => mayBe(
  mv(input),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/
var spaceParser = parser.regex(spaceRegex)

var equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

var thinArrowParser = parser.regex(/^(\s*->\s+)((.|\n)*)$/)

var slashParser = parser.regex(/^(\s*\\)((.|\n)*)$/)

var letParser = parser.regex(/^(\s*let\s+)((.|\n)*)$/)

var inParser = parser.regex(/^(\s*in\s+)((.|\n)*)$/)

var dotParser = parser.regex(/^(\.)((.|\n)*)$/)

var ifParser = parser.regex(/^(if\s+)((.|\n)*)$/)
var thenParser = parser.regex(/^(then\s+)((.|\n)*)$/)
var elseParser = parser.regex(/^(else\s+)((.|\n)*)$/)

var indentParser = input => mayBe(
  spaceRegex.exec(input),
  (a, space, rest) => [space, rest]
)

var returnParser = input => mayBe(
  returnRegex.exec(input.str),
  (a, newLine, rest) => returnRest(newLine, input, rest, {'name': 'return', 'value': newLine.length})
)

var numberParser = input => mayBe(
  numRegex.exec(input.str),
  (a, num, rest) => returnRest(estemplate.literal(num, num), input, rest, {'name': 'column', 'value': num.length})
)

var identifierParser = input => mayBe(
  idRegex.exec(input.str),
  (a, name, rest) => returnRest(estemplate.identifier(name), input, rest, {'name': 'column', 'value': name.length})
)

var stringParser = input => mayBe(
  stringRegex.exec(input.str),
  (a, string, rest) => returnRest(estemplate.stringLiteral(string.replace(/(^')/, '').replace(/('$)/, '')),
                                    input, rest, {'name': 'column', 'value': string.length})
)

var booleanParser = input => mayBe(
    boolRegex.exec(input.str),
    (a, bool, rest) => returnRest(estemplate.boolLiteral(bool), input, rest, {'name': 'column', 'value': bool.length})
  )

var parenthesisParser = parser.bind(
  parser.bind(
  parser.bind(
  parser.bind(
    input => input.str.startsWith('(')
      ? returnRest('(', input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null,
    val => input => {
      const space = spaceParser(input)
      return space ? [space[0], space[1]] : [null, input]
    }),
    val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => returnRest(v, input, rest.str)
    )),
    val => input => {
      var space = spaceParser(input)
      return space ? [val, space[1]] : [val, input]
    }),
    val => input => input.str.startsWith(')')
      ? returnRest(val, input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null
)

var expressionParser = parser.any(parenthesisParser, booleanParser, identifierParser, numberParser, stringParser)

var unaryOperatorParser = input => mayBe(
    unaryOperatorRegex.exec(input.str),
    (m, operator, rest) => returnRest(operator, input, rest, {'name': 'column', 'value': operator.length})
  )

var unaryExprParser = parser.bind(
  parser.bind(
    unaryOperatorParser,
    operator => rest => operator === ':type'
      ? spaceParser(rest) !== null ? ['typeof', spaceParser(rest)[1]] : null
      : [operator, rest]),
  operator => rest => mayBe(expressionParser(rest),
    (argument, rest) => [estemplate.unaryExpression(operator, argument), rest])
)

var binaryOperatorParser = parser.regex(binaryOperatorRegex)

var declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
    parser.any(unaryExprParser, ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
        (v, rest) => {
          return returnRest(estemplate.declaration(val, v), input, rest.str)
        }
      )
)

var paramParser = parser.all(spaceParser, parser.any(identifierParser, numberParser))

var paramsParser = (str, paramArray = []) => {
  var param = paramParser(str)
  return (param !== null) ? paramsParser(param[1], paramArray.concat(param[0][1]))
                          : [paramArray, str]
}

var fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => input => mayBe(
        paramsParser(input),
        (params, rest) => returnRest(estemplate.funcDeclaration(val, params), input, rest.str)
      )
    ),
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
    parser.any(unaryExprParser, ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.declarations[0].init.body = v
        return returnRest(val, input, rest.str)
      }
    )
)

var lambdaParamParser = parser.all(identifierParser, spaceParser)

var lambdaParamsParser = (str, lambdaparamArray = []) => {
  var arg = lambdaParamParser(str)
  if (arg !== null) return lambdaParamsParser(arg[1], lambdaparamArray.concat(arg[0][0]))
  return [lambdaparamArray, str]
}

var lambdaParser = parser.bind(
  parser.bind(
    parser.bind(
      slashParser,
      val => input => mayBe(
        lambdaParamsParser(input),
        (params, rest) => returnRest(estemplate.lambda(params), input, rest.str)
       )
      ),
    val => input => mayBe(
      thinArrowParser(input),
      (v, rest) => returnRest(val, input, rest.str)
    )
   ),
   val => input => mayBe(
     expressionParser(input),
     (v, rest) => {
       val.expression.body = v
       return returnRest(val, input, rest.str)
     }
   )
 )

var letParamParser = parser.all(expressionParser, equalSignParser, expressionParser, spaceParser)

var letParamsParser = (str, letIdArray = [], letLiteralArray = []) => {
  var param = letParamParser(str)
  if (param !== null) return letParamsParser(param[1], letIdArray.concat(param[0][0]), letLiteralArray.concat(param[0][2]))
  return [letIdArray, letLiteralArray, str]
}

var letExpressionParser = parser.bind(
  parser.bind(
    parser.bind(
      letParser,
      val => input => mayBe(
          letParamsParser(input),
          (val1, val2, rest) => returnRest(estemplate.letExpression(val1, val2), input, rest.str)
       )
    ),
    val => input => mayBe(
        inParser(input),
        (v, rest) => returnRest(val, input, rest.str)
      )
  ),
  val => input => mayBe(
      parser.any(binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.expression.callee.body = v
        return returnRest(val, input, rest.str)
      }
    )
  )

var memberPropParser = parser.all(identifierParser, dotParser)

var memberPropsParser = (str, memberArray = []) => {
  var result = memberPropParser(str)
  if (result !== null) return memberPropsParser(result[1], memberArray.concat(result[0][0]))
  if (result === null && memberArray.length === 0) return null
  var property = identifierParser(str)
  if (property === null) return null
  let tree = memberExprParser(memberArray.concat(property[0]))
  return [tree, property[1]]
}

var memberExprParser = (str, tree = {}) => {
  let memExpr = estemplate.memberExpression
  tree = memExpr(str[0], str[1])
  for (let i = 2; i < str.length; i++) {
    tree = memExpr(tree.expression, str[i])
  }
  return tree
}

var argsParser = (input, argArray = []) => {
  var arg = parser.all(
              parser.any(unaryExprParser, expressionParser),
              parser.any(returnParser, parenCheck, spaceParser))(input)
  if (arg === null) return argArray.length === 0 ? null : [argArray, input]
  let [[argument, nextChar], rest] = arg
  if (nextChar === '\n' || nextChar === ')') {
    rest.str = nextChar === '\n' ? '\n' + rest.str : rest.str
    return [argArray.concat(argument), rest]
  }
  return argsParser(rest, argArray.concat(argument))
}

var fnCallParser = parser.bind(
  parser.all(parser.any(memberPropsParser, identifierParser), spaceParser),
  val => input => mayBe(
      argsParser(input),
      (args, rest) => val[1] === 'n' ? null : returnRest(estemplate.fnCall(val[0], args), input, rest.str)
    )
)

var ifExprParser = input => mayBe(
  parser.all(
    ifParser, expressionParser,
    spaceParser, thenParser, expressionParser,
    spaceParser, elseParser, expressionParser)(input),
    (val, rest) => {
      let [, condition, , , consequent, , , alternate] = val
      return returnRest(estemplate.ifthenelse(condition, consequent, alternate), input, rest.str)
    }
)
var multiLineParser = src => {
  if (src.length < 2) return src
  if (!indentParser(src[1])) return src
  const lead = src.splice(0, 1)
  const follow = src.splice(0, 1)
  src.unshift(lead + follow)
  return multiLineParser(src)
}

var statementParser = parser.any(returnParser, letExpressionParser, declParser, ifExprParser, fnDeclParser, fnCallParser, lambdaParser)

var programParser = (input, ast = estemplate.ast()) => {
  let result = statementParser(returnRest('', input, multiLineParser(input.str.split('\n')).join('\n'))[1])
  if (result === null) return updateAst(ast)
  if (typeof result[0] !== 'string') ast.body.push(result[0])
  return programParser(result[1], ast)
}

/* helper functions for binaryExprParser */
const precedence = operator => opPrec[operator].prec
const associativity = operator => opPrec[operator].assoc
const current = parser.any(unaryExprParser, binaryOperatorParser, spaceParser, fnCallParser, expressionParser)
const isBinaryOperator = token => opPrec[token] !== undefined
const isExpression = token => token.type !== undefined

var binaryExprParser = src => {
  let opStack = ['$']
  let operandStack = []
  let biExpr = estemplate.binaryExpression
  var descender = s => {
    let currentToken = current(s)
    let [token, rest] = currentToken === null ? [null, s] : currentToken
    let topOfOpStack = opStack[opStack.length - 1]

    if (token === null) {
      if (topOfOpStack !== '$') {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        return descender(rest)
      }
      return operandStack.length === 0 && opStack.length === 1 ? null : [operandStack.pop(), rest]
    }

    if (isExpression(token)) {
      operandStack.push(token)
      return descender(rest)
    }
    if (isBinaryOperator(token)) { // check operator stack
      if (precedence(token) > precedence(topOfOpStack)) {
        opStack.push(token)
        return descender(rest)
      }

      if (precedence(token) < precedence(topOfOpStack)) {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
      if (associativity(token) === associativity(topOfOpStack)) {
        if (associativity(token) === 'R') {
          opStack.push(token)
          return descender(rest)
        }
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
    }
    return descender(rest)
  }
  let [val, rest] = descender(src)
  return val === null ? null : [val, rest]
}
/*  Module Exports programParser  */
module.exports = programParser
<commit_msg>Call `letExpressionParser` in places required<commit_after>/*
  Parser
  Parses clean source. Generates clean AST.
*/
const opPrec = require('./operatorPrecedence')
const estemplate = require('./estemplate')
const updateAst = require('./astupdate')

/*  Utility functions  */
const mayBe = (value, func) => value === null ? null : func(...value)
const parenCheck = src => mayBe(parser.regex(/^(\))((.|\n)*)$/)(src),
                              (m, val, rest) => returnRest(val, src, src.str))

const returnRest = (val, input, rest, field) => {
  const output = JSON.parse(JSON.stringify(input))
  if (field) {
    let value = field.value
    switch (field.name) {
      case 'indent':
        output.indent = value
        output.column = value
        break
      case 'return':
        output.column = 1
        output.line += value
        break
      case 'column':
        output.column += value
        break
    }
  }
  output.str = rest
  return [val, output]
}

/*  Predefined regexes */
const spaceRegex = /^( +)((.|\n)*)$/
const returnRegex = /^(\n)((.|\n)*)$/
const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^((?:\d+(?:\.\d*)?|\.\d+)(?:[e][+-]?\d+)?)((.|\n)*)$/
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^('[^\\']*(?:\\.[^\\']*)*')((.|\n)*)$/
const binaryOperatorRegex = /^(\+\+|\+|-|\/|\*|<<|<=|<|>>>|>>|>=|>|&&|&|\|\||\||\^|==)((.|\n)*)$/
const unaryOperatorRegex = /^(:type|-|!)((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src.str),
  (m, val, rest) => returnRest(val, src, rest, {'name': 'column', 'value': val.length})
)

parser.bind = (mv, mf) => input => mayBe(
  mv(input),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/
var spaceParser = parser.regex(spaceRegex)

var equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

var thinArrowParser = parser.regex(/^(\s*->\s+)((.|\n)*)$/)

var slashParser = parser.regex(/^(\s*\\)((.|\n)*)$/)

var letParser = parser.regex(/^(\s*let\s+)((.|\n)*)$/)

var inParser = parser.regex(/^(\s*in\s+)((.|\n)*)$/)

var dotParser = parser.regex(/^(\.)((.|\n)*)$/)

var ifParser = parser.regex(/^(if\s+)((.|\n)*)$/)
var thenParser = parser.regex(/^(then\s+)((.|\n)*)$/)
var elseParser = parser.regex(/^(else\s+)((.|\n)*)$/)

var indentParser = input => mayBe(
  spaceRegex.exec(input),
  (a, space, rest) => [space, rest]
)

var returnParser = input => mayBe(
  returnRegex.exec(input.str),
  (a, newLine, rest) => returnRest(newLine, input, rest, {'name': 'return', 'value': newLine.length})
)

var numberParser = input => mayBe(
  numRegex.exec(input.str),
  (a, num, rest) => returnRest(estemplate.literal(num, num), input, rest, {'name': 'column', 'value': num.length})
)

var identifierParser = input => mayBe(
  idRegex.exec(input.str),
  (a, name, rest) => returnRest(estemplate.identifier(name), input, rest, {'name': 'column', 'value': name.length})
)

var stringParser = input => mayBe(
  stringRegex.exec(input.str),
  (a, string, rest) => returnRest(estemplate.stringLiteral(string.replace(/(^')/, '').replace(/('$)/, '')),
                                    input, rest, {'name': 'column', 'value': string.length})
)

var booleanParser = input => mayBe(
    boolRegex.exec(input.str),
    (a, bool, rest) => returnRest(estemplate.boolLiteral(bool), input, rest, {'name': 'column', 'value': bool.length})
  )

var parenthesisParser = parser.bind(
  parser.bind(
  parser.bind(
  parser.bind(
    input => input.str.startsWith('(')
      ? returnRest('(', input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null,
    val => input => {
      const space = spaceParser(input)
      return space ? [space[0], space[1]] : [null, input]
    }),
    val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => returnRest(v, input, rest.str)
    )),
    val => input => {
      var space = spaceParser(input)
      return space ? [val, space[1]] : [val, input]
    }),
    val => input => input.str.startsWith(')')
      ? returnRest(val, input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null
)

var expressionParser = parser.any(parenthesisParser, booleanParser, identifierParser, numberParser, stringParser)

var unaryOperatorParser = input => mayBe(
    unaryOperatorRegex.exec(input.str),
    (m, operator, rest) => returnRest(operator, input, rest, {'name': 'column', 'value': operator.length})
  )

var unaryExprParser = parser.bind(
  parser.bind(
    unaryOperatorParser,
    operator => rest => operator === ':type'
      ? spaceParser(rest) !== null ? ['typeof', spaceParser(rest)[1]] : null
      : [operator, rest]),
  operator => rest => mayBe(expressionParser(rest),
    (argument, rest) => [estemplate.unaryExpression(operator, argument), rest])
)

var binaryOperatorParser = parser.regex(binaryOperatorRegex)

var declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
    parser.any(unaryExprParser, letExpressionParser, ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
        (v, rest) => {
          return returnRest(estemplate.declaration(val, v), input, rest.str)
        }
      )
)

var paramParser = parser.all(spaceParser, parser.any(identifierParser, numberParser))

var paramsParser = (str, paramArray = []) => {
  var param = paramParser(str)
  return (param !== null) ? paramsParser(param[1], paramArray.concat(param[0][1]))
                          : [paramArray, str]
}

var fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => input => mayBe(
        paramsParser(input),
        (params, rest) => returnRest(estemplate.funcDeclaration(val, params), input, rest.str)
      )
    ),
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
    parser.any(unaryExprParser, letExpressionParser, ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.declarations[0].init.body = v
        return returnRest(val, input, rest.str)
      }
    )
)

var lambdaParamParser = parser.all(identifierParser, spaceParser)

var lambdaParamsParser = (str, lambdaparamArray = []) => {
  var arg = lambdaParamParser(str)
  if (arg !== null) return lambdaParamsParser(arg[1], lambdaparamArray.concat(arg[0][0]))
  return [lambdaparamArray, str]
}

var lambdaParser = parser.bind(
  parser.bind(
    parser.bind(
      slashParser,
      val => input => mayBe(
        lambdaParamsParser(input),
        (params, rest) => returnRest(estemplate.lambda(params), input, rest.str)
       )
      ),
    val => input => mayBe(
      thinArrowParser(input),
      (v, rest) => returnRest(val, input, rest.str)
    )
   ),
   val => input => mayBe(
     expressionParser(input),
     (v, rest) => {
       val.expression.body = v
       return returnRest(val, input, rest.str)
     }
   )
 )

var letParamParser = parser.all(expressionParser, equalSignParser, expressionParser, spaceParser)

var letParamsParser = (str, letIdArray = [], letLiteralArray = []) => {
  var param = letParamParser(str)
  if (param !== null) return letParamsParser(param[1], letIdArray.concat(param[0][0]), letLiteralArray.concat(param[0][2]))
  return [letIdArray, letLiteralArray, str]
}

var letExpressionParser = parser.bind(
  parser.bind(
    parser.bind(
      letParser,
      val => input => mayBe(
          letParamsParser(input),
          (val1, val2, rest) => returnRest(estemplate.letExpression(val1, val2), input, rest.str)
       )
    ),
    val => input => mayBe(
        inParser(input),
        (v, rest) => returnRest(val, input, rest.str)
      )
  ),
  val => input => mayBe(
      parser.any(binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.expression.callee.body = v
        return returnRest(val, input, rest.str)
      }
    )
  )

var memberPropParser = parser.all(identifierParser, dotParser)

var memberPropsParser = (str, memberArray = []) => {
  var result = memberPropParser(str)
  if (result !== null) return memberPropsParser(result[1], memberArray.concat(result[0][0]))
  if (result === null && memberArray.length === 0) return null
  var property = identifierParser(str)
  if (property === null) return null
  let tree = memberExprParser(memberArray.concat(property[0]))
  return [tree, property[1]]
}

var memberExprParser = (str, tree = {}) => {
  let memExpr = estemplate.memberExpression
  tree = memExpr(str[0], str[1])
  for (let i = 2; i < str.length; i++) {
    tree = memExpr(tree.expression, str[i])
  }
  return tree
}

var argsParser = (input, argArray = []) => {
  var arg = parser.all(
              parser.any(unaryExprParser, expressionParser),
              parser.any(returnParser, parenCheck, spaceParser))(input)
  if (arg === null) return argArray.length === 0 ? null : [argArray, input]
  let [[argument, nextChar], rest] = arg
  if (nextChar === '\n' || nextChar === ')') {
    rest.str = nextChar === '\n' ? '\n' + rest.str : rest.str
    return [argArray.concat(argument), rest]
  }
  return argsParser(rest, argArray.concat(argument))
}

var fnCallParser = parser.bind(
  parser.all(parser.any(memberPropsParser, identifierParser), spaceParser),
  val => input => mayBe(
      argsParser(input),
      (args, rest) => val[1] === 'n' ? null : returnRest(estemplate.fnCall(val[0], args), input, rest.str)
    )
)

var ifExprParser = input => mayBe(
  parser.all(
    ifParser, expressionParser,
    spaceParser, thenParser, expressionParser,
    spaceParser, elseParser, expressionParser)(input),
    (val, rest) => {
      let [, condition, , , consequent, , , alternate] = val
      return returnRest(estemplate.ifthenelse(condition, consequent, alternate), input, rest.str)
    }
)
var multiLineParser = src => {
  if (src.length < 2) return src
  if (!indentParser(src[1])) return src
  const lead = src.splice(0, 1)
  const follow = src.splice(0, 1)
  src.unshift(lead + follow)
  return multiLineParser(src)
}

var statementParser = parser.any(returnParser, declParser, ifExprParser, fnDeclParser, fnCallParser, lambdaParser)

var programParser = (input, ast = estemplate.ast()) => {
  let result = statementParser(returnRest('', input, multiLineParser(input.str.split('\n')).join('\n'))[1])
  if (result === null) return updateAst(ast)
  if (typeof result[0] !== 'string') ast.body.push(result[0])
  return programParser(result[1], ast)
}

/* helper functions for binaryExprParser */
const precedence = operator => opPrec[operator].prec
const associativity = operator => opPrec[operator].assoc
const current = parser.any(unaryExprParser, binaryOperatorParser, spaceParser, fnCallParser, expressionParser)
const isBinaryOperator = token => opPrec[token] !== undefined
const isExpression = token => token.type !== undefined

var binaryExprParser = src => {
  let opStack = ['$']
  let operandStack = []
  let biExpr = estemplate.binaryExpression
  var descender = s => {
    let currentToken = current(s)
    let [token, rest] = currentToken === null ? [null, s] : currentToken
    let topOfOpStack = opStack[opStack.length - 1]

    if (token === null) {
      if (topOfOpStack !== '$') {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        return descender(rest)
      }
      return operandStack.length === 0 && opStack.length === 1 ? null : [operandStack.pop(), rest]
    }

    if (isExpression(token)) {
      operandStack.push(token)
      return descender(rest)
    }
    if (isBinaryOperator(token)) { // check operator stack
      if (precedence(token) > precedence(topOfOpStack)) {
        opStack.push(token)
        return descender(rest)
      }

      if (precedence(token) < precedence(topOfOpStack)) {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
      if (associativity(token) === associativity(topOfOpStack)) {
        if (associativity(token) === 'R') {
          opStack.push(token)
          return descender(rest)
        }
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
    }
    return descender(rest)
  }
  let [val, rest] = descender(src)
  return val === null ? null : [val, rest]
}
/*  Module Exports programParser  */
module.exports = programParser
<|endoftext|>


<commit_before>
/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , error:    5
  , noop:     6
};

var packetslist = Object.keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' }

/**
 * Encodes a packet.
 *
 *     <packet type id> [ `:` <data> ]
 *
 * Example:
 *
 *     5:hello world
 *     3
 *     4
 *
 * @api private
 */

exports.encodePacket = function (packet) {
  var encoded = packets[packet.type]

  // data fragment is optional
  if ('undefined' != typeof packet.data) {
    encoded += ':' + packet.data;
  }

  return '' + encoded;
};

/**
 * Decodes a packet.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data) {
  if (':' === data[1]) {
    var pieces = data.match(/([0-9]+):(.*)?/)

    if (!pieces) {
      // parser error - ignoring packet
      return err;
    }

    var type = pieces[1]
      , data = pieces[2]

    if (!packetslist[type]) {
      // parser error - ignoring packet
      return err;
    }

    return { type: packetslist[type], data: undefined === data ? '' : data };
  } else {
    if (Number(data) != data || !packetslist[data]) return err;
    return { type: packetslist[data] };
  }
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets) {
  var encoded = ''
    , message

  for (var i = 0, l = packets.length; i < l; i++) {
    message = exports.encodePacket(packets[i]);
    encoded += message.length + ':' + message;
  }

  return encoded;
};

/*
 * Decodes data when a payload is maybe expected.
 *
 * @param {String} data
 * @return {Array} packets
 * @api public
 */

exports.decodePayload = function (data) {
  var packets = []
    , length = ''
    , n, msg, packet

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data[i]

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return [err];
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return [err];
      }

      packet = exports.decodePacket(msg);

      if (err.type == packet.type && err.data == packet.data) {
        // parser error in individual packet - ignoring payload
        return [err];
      }

      packets.push(msg);

      // advance cursor
      i += n;
      length = ''
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return [err];
  }

  return packets;
};
<commit_msg>Allow for empty payloads. Only for consistency, we never should send empty payloads.<commit_after>
/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , error:    5
  , noop:     6
};

var packetslist = Object.keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' }

/**
 * Encodes a packet.
 *
 *     <packet type id> [ `:` <data> ]
 *
 * Example:
 *
 *     5:hello world
 *     3
 *     4
 *
 * @api private
 */

exports.encodePacket = function (packet) {
  var encoded = packets[packet.type]

  // data fragment is optional
  if ('undefined' != typeof packet.data) {
    encoded += ':' + packet.data;
  }

  return '' + encoded;
};

/**
 * Decodes a packet.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data) {
  if (':' === data[1]) {
    var pieces = data.match(/([0-9]+):(.*)?/)

    if (!pieces) {
      // parser error - ignoring packet
      return err;
    }

    var type = pieces[1]
      , data = pieces[2]

    if (!packetslist[type]) {
      // parser error - ignoring packet
      return err;
    }

    return { type: packetslist[type], data: undefined === data ? '' : data };
  } else {
    if (Number(data) != data || !packetslist[data]) return err;
    return { type: packetslist[data] };
  }
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets) {
  if (!packets.length) {
    return '0:';
  }

  var encoded = ''
    , message

  for (var i = 0, l = packets.length; i < l; i++) {
    message = exports.encodePacket(packets[i]);
    encoded += message.length + ':' + message;
  }

  return encoded;
};

/*
 * Decodes data when a payload is maybe expected.
 *
 * @param {String} data
 * @return {Array} packets
 * @api public
 */

exports.decodePayload = function (data) {
  var packets = []
    , length = ''
    , n, msg, packet

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data[i]

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return [err];
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return [err];
      }

      packet = exports.decodePacket(msg);

      if (err.type == packet.type && err.data == packet.data) {
        // parser error in individual packet - ignoring payload
        return [err];
      }

      packets.push(msg);

      // advance cursor
      i += n;
      length = ''
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return [err];
  }

  return packets;
};
<|endoftext|>


<commit_before>'use strict';

var 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')
  , sLast        = require('es5-ext/lib/String/prototype/last')
  , d            = require('es5-ext/lib/Object/descriptor')
  , indent       = require('es5-ext/lib/String/prototype/indent')
  , endsWith     = require('es5-ext/lib/String/prototype/ends-with')
  , deferred     = require('deferred')
  , path         = require('path')
  , stat         = require('fs').stat
  , nmSource     = process.binding('natives')
  , getRequire   = require('next/lib/module/get-require')
  , findRoot     = require('next/lib/module/find-package-root')
  , readFile     = require('fs2/lib/read-file')
  , findRequires = require('find-requires')

  , create = Object.create, keys = Object.keys, stringify = JSON.stringify
  , dirname = path.dirname, join = path.join, resolve = path.resolve
  , sep = path.sep
  , dotMatch = /^\.+$/
  , fileExists, dirExists, parseDependencies, parser, modulesToString;

fileExists = function (path) {
	var def = deferred();
	stat(path, function (err, stats) {
		def.resolve(err ? false : stats.isFile());
	});
	return def.promise;
};

dirExists = function (path) {
	var def = deferred();
	stat(path, function (err, stats) {
		def.resolve(err ? false : stats.isDirectory());
	});
	return def.promise;
};

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

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(sep)) : '/';
			if (!this.modules[name]) {
				this.packages[name] = root || sep;
				this.modules[name] = {};
			}
			scope = this.modules[name];
			path = name + (root ? sep : '') +
				(dirs = trimCommon.call(input, root || '').slice(1));
			dirs = dirs.split(sep);
			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 (sLast.call(content) !== '\n') {
				content += '\n';
			}
			scope[name] = content = new String(content);
			content.filename = filename.split(sep)
				.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, isDir;
		filename = join(filename);
		path = filename.split(sep);
		if (dotMatch.test(last.call(path))) {
			path.push('');
			filename += sep;
		}
		isDir = (sLast.call(filename) === sep);
		filename = resolve(dirname + sep + filename);
		return (isDir ? dirExists(filename)(function (exists) {
			if (exists) {
				path.pop();
				name = 'index.js';
				filename += sep + '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 += sep + '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, cache;
		filename = join(filename);
		name = filename.split(sep, 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) {
					cache = nmSource[name];
					delete nmSource[name];
					try {
						path = main = require.resolve(name);
					} catch (e3) {}
					nmSource[name] = cache;
					if (main === name) {
						throw new Error("Cannot require " + stringify(name) +
							" (as in '" + fromfile + "').\n       Native node.js modules" +
							" are not ported to client-side. You can however provide" +
							" alternative version of this module in your node_modules" +
							" path, it will be picked up by Webmake.\n");
					}
				}
				if (!main && endsWith.call(path, filename + '.js')) {
					this.packages[name] = path.slice(0, -(filename.length + 3) +
						name.length);
					return (this.modules[name] = {});
				}
				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.aside(function (module) {
					this.modules[name] = module;
				}.bind(this));
				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 = exports = function () {
	return create(parser, {
		modules: d({}),
		packages: d({}),
		modulesFiles: d([])
	});
};
exports.modulesToString = modulesToString;
<commit_msg>Meaningful error if run with old Node engine.<commit_after>'use strict';

var 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')
  , sLast        = require('es5-ext/lib/String/prototype/last')
  , d            = require('es5-ext/lib/Object/descriptor')
  , indent       = require('es5-ext/lib/String/prototype/indent')
  , endsWith     = require('es5-ext/lib/String/prototype/ends-with')
  , deferred     = require('deferred')
  , path         = require('path')
  , stat         = require('fs').stat
  , nmSource     = process.binding('natives')
  , getRequire   = require('next/lib/module/get-require')
  , findRoot     = require('next/lib/module/find-package-root')
  , readFile     = require('fs2/lib/read-file')
  , findRequires = require('find-requires')

  , create = Object.create, keys = Object.keys, stringify = JSON.stringify
  , dirname = path.dirname, join = path.join, resolve = path.resolve
  , sep = path.sep
  , dotMatch = /^\.+$/
  , fileExists, dirExists, parseDependencies, parser, modulesToString;

if (sep == null) {
	throw new Error("Unsupported Node version. Please upgrade," +
		" Webmake needs at least Node v0.8");
}

fileExists = function (path) {
	var def = deferred();
	stat(path, function (err, stats) {
		def.resolve(err ? false : stats.isFile());
	});
	return def.promise;
};

dirExists = function (path) {
	var def = deferred();
	stat(path, function (err, stats) {
		def.resolve(err ? false : stats.isDirectory());
	});
	return def.promise;
};

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

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(sep)) : '/';
			if (!this.modules[name]) {
				this.packages[name] = root || sep;
				this.modules[name] = {};
			}
			scope = this.modules[name];
			path = name + (root ? sep : '') +
				(dirs = trimCommon.call(input, root || '').slice(1));
			dirs = dirs.split(sep);
			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 (sLast.call(content) !== '\n') {
				content += '\n';
			}
			scope[name] = content = new String(content);
			content.filename = filename.split(sep)
				.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, isDir;
		filename = join(filename);
		path = filename.split(sep);
		if (dotMatch.test(last.call(path))) {
			path.push('');
			filename += sep;
		}
		isDir = (sLast.call(filename) === sep);
		filename = resolve(dirname + sep + filename);
		return (isDir ? dirExists(filename)(function (exists) {
			if (exists) {
				path.pop();
				name = 'index.js';
				filename += sep + '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 += sep + '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, cache;
		filename = join(filename);
		name = filename.split(sep, 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) {
					cache = nmSource[name];
					delete nmSource[name];
					try {
						path = main = require.resolve(name);
					} catch (e3) {}
					nmSource[name] = cache;
					if (main === name) {
						throw new Error("Cannot require " + stringify(name) +
							" (as in '" + fromfile + "').\n       Native node.js modules" +
							" are not ported to client-side. You can however provide" +
							" alternative version of this module in your node_modules" +
							" path, it will be picked up by Webmake.\n");
					}
				}
				if (!main && endsWith.call(path, filename + '.js')) {
					this.packages[name] = path.slice(0, -(filename.length + 3) +
						name.length);
					return (this.modules[name] = {});
				}
				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.aside(function (module) {
					this.modules[name] = module;
				}.bind(this));
				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 = exports = function () {
	return create(parser, {
		modules: d({}),
		packages: d({}),
		modulesFiles: d([])
	});
};
exports.modulesToString = modulesToString;
<|endoftext|>