Skip to content

Instantly share code, notes, and snippets.

@jcmoore
Created April 6, 2014 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcmoore/10011743 to your computer and use it in GitHub Desktop.
Save jcmoore/10011743 to your computer and use it in GitHub Desktop.
extra lookup for tern.js
<html>
<head>
<script>
window.onload = (function () {
var infer = tern;
var code = document.querySelector(".javascript").innerHTML.trim();
var cx = infer.withContext(new infer.Context(), function () {
//infer.findExpressionAround(cx.topScope.node, 293062, 294655, cx.topScope).node.body.scope
//infer.findExpressionAround(cx.topScope.node, 29037, 29271, cx.topScope).node.body.scope
var ccx = infer.cx();
var ast = infer.parse(code);
infer.analyze(ast, "editor");
ccx.topScope.node = ccx.topScope.node || ast;
ast.scope = ccx.topScope;
return infer.cx();
});
var copy = infer.findExpressionAround(cx.topScope.node, 29039, 29273, cx.topScope).node.body.scope;
var original = copy.copiedScope || null;
console.log(JSON.stringify([!!copy.node, original && !!original.node]));
console.log(JSON.stringify([!!copy.getProp("obj").originNode, original && !!original.getProp("obj").originNode]));
/*
// Line 882-ish in https://raw.githubusercontent.com/jashkenas/underscore/ac256438e72df7e69d67149b408ea3c603fc0daf/underscore.js
// Line 923-ish below
_.defaults = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
}
*/
return cx;
});
</script>
<script class="javascript" id="https://raw.githubusercontent.com/jashkenas/underscore/ac256438e72df7e69d67149b408ea3c603fc0daf/underscore.js" type="text/text">
// Underscore.js 1.6.0
// http://underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var
push = ArrayProto.push,
slice = ArrayProto.slice,
concat = ArrayProto.concat,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// Current version.
_.VERSION = '1.6.0';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles raw objects in addition to array-likes. Treats all
// sparse array-likes as if they were dense.
_.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return obj;
if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj;
};
// Return the results of applying the iterator to each element.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
_.each(obj, function(value, index, list) {
results.push(iterator.call(context, value, index, list));
});
return results;
};
var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
_.each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
var length = obj.length;
if (length !== +length) {
var keys = _.keys(obj);
length = keys.length;
}
_.each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
memo = obj[index];
initial = true;
} else {
memo = iterator.call(context, memo, obj[index], index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, predicate, context) {
var result;
_.some(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Aliased as `select`.
_.filter = _.select = function(obj, predicate, context) {
var results = [];
if (obj == null) return results;
_.each(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
return _.filter(obj, _.negate(predicate), context);
};
// Determine whether all of the elements match a truth test.
// Aliased as `all`.
_.every = _.all = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = true;
if (obj == null) return result;
_.each(obj, function(value, index, list) {
if (!(result = result && predicate.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if at least one element in the object matches a truth test.
// Aliased as `any`.
_.some = _.any = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = false;
if (obj == null) return result;
_.each(obj, function(value, index, list) {
if (result || (result = predicate.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if the array or object contains a given value (using `===`).
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
if (obj == null) return false;
return _.some(obj, function(value) {
return value === target;
});
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
return (isFunc ? method : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, _.property(key));
};
// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
_.where = function(obj, attrs) {
return _.filter(obj, _.matches(attrs));
};
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matches(attrs));
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
var result = -Infinity, lastComputed = -Infinity,
value, computed;
if (!iterator && _.isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value > result) {
result = value;
}
}
} else {
_.each(obj, function(value, index, list) {
computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed > lastComputed) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
var result = Infinity, lastComputed = Infinity,
value, computed;
if (!iterator && _.isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value < result) {
result = value;
}
}
} else {
_.each(obj, function(value, index, list) {
computed = iterator ? iterator.call(context, value, index, list) : value;
if (computed < lastComputed) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Shuffle an array, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) {
var rand;
var index = 0;
var shuffled = [];
_.each(obj, function(value) {
rand = _.random(index++);
shuffled[index - 1] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
// Sample **n** random values from a collection.
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) {
if (n == null || guard) {
if (obj.length !== +obj.length) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
return _.shuffle(obj).slice(0, Math.max(0, n));
};
// An internal function to generate lookup iterators.
var lookupIterator = function(value) {
if (value == null) return _.identity;
if (_.isFunction(value)) return value;
return _.property(value);
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
iterator = lookupIterator(iterator);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
// An internal function used for aggregate "group by" operations.
var group = function(behavior) {
return function(obj, iterator, context) {
var result = {};
iterator = lookupIterator(iterator);
_.each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
});
return result;
};
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = group(function(result, key, value) {
_.has(result, key) ? result[key].push(value) : result[key] = [value];
});
// Indexes the object's values by a criterion, similar to `groupBy`, but for
// when you know that your index values will be unique.
_.indexBy = group(function(result, key, value) {
result[key] = value;
});
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
_.countBy = group(function(result, key) {
_.has(result, key) ? result[key]++ : result[key] = 1;
});
// Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator, context) {
iterator = lookupIterator(iterator);
var value = iterator.call(context, obj);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >>> 1;
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
}
return low;
};
// Safely create a real, live array from anything iterable.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (obj.length === +obj.length) return _.map(obj, _.identity);
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0;
if ((n == null) || guard) return array[0];
if (n < 0) return [];
return slice.call(array, 0, n);
};
// Returns everything but the last entry of the array. Especially useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if (array == null) return void 0;
if ((n == null) || guard) return array[array.length - 1];
return slice.call(array, Math.max(array.length - n, 0));
};
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **n** will return
// the rest N values in the array. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, (n == null) || guard ? 1 : n);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, _.identity);
};
// Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, strict, output) {
if (shallow && _.every(input, _.isArray)) {
return concat.apply(output, input);
}
for (var i = 0, length = input.length; i < length; i++) {
var value = input[i];
if (!_.isArray(value) && !_.isArguments(value)) {
if (!strict) output.push(value);
} else if (shallow) {
push.apply(output, value);
} else {
flatten(value, shallow, strict, output);
}
}
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
return flatten(array, shallow, false, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Split an array into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(obj, predicate, context) {
predicate = lookupIterator(predicate);
var pass = [], fail = [];
_.each(obj, function(elem) {
(predicate.call(context, elem) ? pass : fail).push(elem);
});
return [pass, fail];
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator, context) {
if (array == null) return [];
if (_.isFunction(isSorted)) {
context = iterator;
iterator = isSorted;
isSorted = false;
}
var result = [];
var seen = [];
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (iterator) value = iterator.call(context, value, i, array);
if (isSorted ? (!i || seen !== value) : !_.contains(seen, value)) {
if (isSorted) seen = value;
else seen.push(value);
result.push(array[i]);
}
}
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(flatten(arguments, true, true, []));
};
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
if (array == null) return [];
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = array.length; i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = flatten(slice.call(arguments, 1), true, true, []);
return _.filter(array, function(value){ return !_.contains(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var length = _.max(_.pluck(arguments, 'length').concat(0));
var results = new Array(length);
for (var i = 0; i < length; i++) {
results[i] = _.pluck(arguments, '' + i);
}
return results;
};
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
if (list == null) return {};
var result = {};
for (var i = 0, length = list.length; i < length; i++) {
if (values) {
result[list[i]] = values[i];
} else {
result[list[i][0]] = list[i][1];
}
}
return result;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i = 0, length = array.length;
if (isSorted) {
if (typeof isSorted == 'number') {
i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
} else {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
for (; i < length; i++) if (array[i] === item) return i;
return -1;
};
_.lastIndexOf = function(array, item, from) {
if (array == null) return -1;
var i = from == null ? array.length : from;
while (i--) if (array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var length = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(length);
while(idx < length) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder, allowing any combination of arguments to be pre-filled.
_.partial = function(func) {
var boundArgs = slice.call(arguments, 1);
return function() {
var position = 0;
var args = boundArgs.slice();
for (var i = 0, length = args.length; i < length; i++) {
if (args[i] === _) args[i] = arguments[position++];
}
while (position < arguments.length) args.push(arguments[position++]);
return func.apply(this, args);
};
};
// Bind a number of an object's methods to that object. Remaining arguments
// are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length === 0) throw new Error('bindAll must be passed function names');
_.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(null, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
options || (options = {});
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
var last = _.now() - timestamp;
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
timestamp = _.now();
var callNow = immediate && !timeout;
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
memo = func.apply(this, arguments);
func = null;
return memo;
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return _.partial(wrapper, func);
};
// Returns a negated version of the passed-in predicate.
_.negate = function(predicate) {
return function() {
return !predicate.apply(this, arguments);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = function(obj) {
if (!_.isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
var keys = _.keys(obj);
var length = keys.length;
var values = new Array(length);
for (var i = 0; i < length; i++) {
values[i] = obj[keys[i]];
}
return values;
};
// Convert an object into a list of `[key, value]` pairs.
_.pairs = function(obj) {
var keys = _.keys(obj);
var length = keys.length;
var pairs = new Array(length);
for (var i = 0; i < length; i++) {
pairs[i] = [keys[i], obj[keys[i]]];
}
return pairs;
};
// Invert the keys and values of an object. The values must be serializable.
_.invert = function(obj) {
var result = {};
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
result[obj[keys[i]]] = keys[i];
}
return result;
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};
// Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj, iterator, context) {
var result = {};
if (_.isFunction(iterator)) {
for (var key in obj) {
var value = obj[key];
if (iterator.call(context, value, key, obj)) result[key] = value;
}
} else {
var keys = concat.apply([], slice.call(arguments, 1));
for (var i = 0, length = keys.length; i < length; i++) {
var key = keys[i];
if (key in obj) result[key] = obj[key];
}
}
return result;
};
// Return a copy of the object without the blacklisted properties.
_.omit = function(obj, iterator, context) {
var keys;
if (_.isFunction(iterator)) {
iterator = _.negate(iterator);
} else {
keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
iterator = function(value, key) { return !_.contains(keys, key); };
}
return _.pick(obj, iterator, context);
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function for `isEqual`.
var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (aStack[length] == a) return bStack[length] == b;
}
// Objects with different constructors are not equivalent, but `Object`s
// from different frames are.
var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
_.isFunction(bCtor) && (bCtor instanceof bCtor))
&& ('constructor' in a && 'constructor' in b)) {
return false;
}
// Add the first object to the stack of traversed objects.
aStack.push(a);
bStack.push(b);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
if (!(result = eq(a[size], b[size], aStack, bStack))) break;
}
}
} else {
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
aStack.pop();
bStack.pop();
return result;
};
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, [], []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']';
};
});
// Define a fallback version of the method in browsers (ahem, IE), where
// there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
_.isFunction = function(obj) {
return typeof obj === 'function';
};
}
// Is a given object a finite number?
_.isFinite = function(obj) {
return isFinite(obj) && !isNaN(parseFloat(obj));
};
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) {
return _.isNumber(obj) && obj != +obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
_.constant = function(value) {
return function() {
return value;
};
};
_.noop = function(){};
_.property = function(key) {
return function(obj) {
return obj[key];
};
};
// Returns a predicate for checking whether an object has a given set of `key:value` pairs.
_.matches = function(attrs) {
return function(obj) {
if (obj === attrs) return true;
for (var key in attrs) {
if (attrs[key] !== obj[key])
return false;
}
return true;
}
};
// Run a function **n** times.
_.times = function(n, iterator, context) {
var accum = Array(Math.max(0, n));
for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
return accum;
};
// Return a random integer between min and max (inclusive).
_.random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
};
// A (possibly faster) way to get the current timestamp as an integer.
_.now = Date.now || function() { return new Date().getTime(); };
// List of HTML entities for escaping.
var entityMap = {
escape: {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;'
}
};
entityMap.unescape = _.invert(entityMap.escape);
// Regexes containing the keys and values listed immediately above.
var entityRegexes = {
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
};
// Functions for escaping and unescaping strings to/from HTML interpolation.
_.each(['escape', 'unescape'], function(method) {
_[method] = function(string) {
if (string == null) return '';
return ('' + string).replace(entityRegexes[method], function(match) {
return entityMap[method][match];
});
};
});
// If the value of the named `property` is a function then invoke it with the
// `object` as context; otherwise, return it.
_.result = function(object, property) {
if (object == null) return void 0;
var value = object[property];
return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result.call(this, func.apply(_, args));
};
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
var escapeChar = function(match) {
return '\\' + escapes[match];
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset).replace(escaper, escapeChar);
index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
// Adobe VMs need the match returned to produce the correct offest.
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled source as a convenience for precompilation.
var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// OOP
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
// Helper function to continue chaining intermediate results.
var result = function(obj) {
return this._chain ? _(obj).chain() : obj;
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments);
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
return result.call(this, obj);
};
});
// Add all accessor Array functions to the wrapper.
_.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments));
};
});
_.extend(_.prototype, {
// Start chaining a wrapped Underscore object.
chain: function() {
this._chain = true;
return this;
},
// Extracts the result from a wrapped and chained object.
value: function() {
return this._wrapped;
}
});
// AMD registration happens at the end for compatibility with AMD loaders
// that may not enforce next-turn semantics on modules. Even though general
// practice for AMD registration is to be anonymous, underscore registers
// as a named module because, like jQuery, it is a base library that is
// popular enough to be bundled in a third party lib, but not be part of
// an AMD load request. Those cases could generate an error when an
// anonymous define() is called outside of a loader request.
if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
}
}).call(this);
</script>
<script id="https://raw.githubusercontent.com/marijnh/acorn/4869ccfa55121ba707cefacde4af701c49383156/util/walk.js">
// AST walker module for Mozilla Parser API compatible trees
(function(mod) {
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env
})(function(exports) {
"use strict";
// A simple walk is one where you simply specify callbacks to be
// called on specific nodes. The last two arguments are optional. A
// simple use would be
//
// walk.simple(myTree, {
// Expression: function(node) { ... }
// });
//
// to do something with all expressions. All Parser API node types
// can be used to identify node types, as well as Expression,
// Statement, and ScopeBody, which denote categories of nodes.
//
// The base argument can be used to pass a custom (recursive)
// walker, and state can be used to give this walked an initial
// state.
exports.simple = function(node, visitors, base, state) {
if (!base) base = exports.base;
function c(node, st, override) {
var type = override || node.type, found = visitors[type];
base[type](node, st, c);
if (found) found(node, st);
}
c(node, state);
};
// An ancestor walk builds up an array of ancestor nodes (including
// the current node) and passes them to the callback as the state parameter.
exports.ancestor = function(node, visitors, base, state) {
if (!base) base = exports.base;
if (!state) state = [];
function c(node, st, override) {
var type = override || node.type, found = visitors[type];
if (node != st[st.length - 1]) {
st = st.slice();
st.push(node);
}
base[type](node, st, c);
if (found) found(node, st);
}
c(node, state);
};
// A recursive walk is one where your functions override the default
// walkers. They can modify and replace the state parameter that's
// threaded through the walk, and can opt how and whether to walk
// their child nodes (by calling their third argument on these
// nodes).
exports.recursive = function(node, state, funcs, base) {
var visitor = funcs ? exports.make(funcs, base) : base;
function c(node, st, override) {
visitor[override || node.type](node, st, c);
}
c(node, state);
};
function makeTest(test) {
if (typeof test == "string")
return function(type) { return type == test; };
else if (!test)
return function() { return true; };
else
return test;
}
function Found(node, state) { this.node = node; this.state = state; }
// Find a node with a given start, end, and type (all are optional,
// null can be used as wildcard). Returns a {node, state} object, or
// undefined when it doesn't find a matching node.
exports.findNodeAt = function(node, start, end, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
var type = override || node.type;
if ((start == null || node.start <= start) &&
(end == null || node.end >= end))
base[type](node, st, c);
if (test(type, node) &&
(start == null || node.start == start) &&
(end == null || node.end == end))
throw new Found(node, st);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the innermost node of a given type that contains the given
// position. Interface similar to findNodeAt.
exports.findNodeAround = function(node, pos, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
var type = override || node.type;
if (node.start > pos || node.end < pos) return;
base[type](node, st, c);
if (test(type, node)) throw new Found(node, st);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the outermost matching node after a given position.
exports.findNodeAfter = function(node, pos, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
if (node.end < pos) return;
var type = override || node.type;
if (node.start >= pos && test(type, node)) throw new Found(node, st);
base[type](node, st, c);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the outermost matching node before a given position.
exports.findNodeBefore = function(node, pos, test, base, state) {
test = makeTest(test);
if (!base) base = exports.base;
var max;
var c = function(node, st, override) {
if (node.start > pos) return;
var type = override || node.type;
if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node))
max = new Found(node, st);
base[type](node, st, c);
};
c(node, state);
return max;
};
// Used to create a custom walker. Will fill in all missing node
// type properties with the defaults.
exports.make = function(funcs, base) {
if (!base) base = exports.base;
var visitor = {};
for (var type in base) visitor[type] = base[type];
for (var type in funcs) visitor[type] = funcs[type];
return visitor;
};
function skipThrough(node, st, c) { c(node, st); }
function ignore(_node, _st, _c) {}
// Node walkers.
var base = exports.base = {};
base.Program = base.BlockStatement = function(node, st, c) {
for (var i = 0; i < node.body.length; ++i)
c(node.body[i], st, "Statement");
};
base.Statement = skipThrough;
base.EmptyStatement = ignore;
base.ExpressionStatement = function(node, st, c) {
c(node.expression, st, "Expression");
};
base.IfStatement = function(node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Statement");
if (node.alternate) c(node.alternate, st, "Statement");
};
base.LabeledStatement = function(node, st, c) {
c(node.body, st, "Statement");
};
base.BreakStatement = base.ContinueStatement = ignore;
base.WithStatement = function(node, st, c) {
c(node.object, st, "Expression");
c(node.body, st, "Statement");
};
base.SwitchStatement = function(node, st, c) {
c(node.discriminant, st, "Expression");
for (var i = 0; i < node.cases.length; ++i) {
var cs = node.cases[i];
if (cs.test) c(cs.test, st, "Expression");
for (var j = 0; j < cs.consequent.length; ++j)
c(cs.consequent[j], st, "Statement");
}
};
base.ReturnStatement = function(node, st, c) {
if (node.argument) c(node.argument, st, "Expression");
};
base.ThrowStatement = function(node, st, c) {
c(node.argument, st, "Expression");
};
base.TryStatement = function(node, st, c) {
c(node.block, st, "Statement");
if (node.handler) c(node.handler.body, st, "ScopeBody");
if (node.finalizer) c(node.finalizer, st, "Statement");
};
base.WhileStatement = function(node, st, c) {
c(node.test, st, "Expression");
c(node.body, st, "Statement");
};
base.DoWhileStatement = base.WhileStatement;
base.ForStatement = function(node, st, c) {
if (node.init) c(node.init, st, "ForInit");
if (node.test) c(node.test, st, "Expression");
if (node.update) c(node.update, st, "Expression");
c(node.body, st, "Statement");
};
base.ForInStatement = function(node, st, c) {
c(node.left, st, "ForInit");
c(node.right, st, "Expression");
c(node.body, st, "Statement");
};
base.ForInit = function(node, st, c) {
if (node.type == "VariableDeclaration") c(node, st);
else c(node, st, "Expression");
};
base.DebuggerStatement = ignore;
base.FunctionDeclaration = function(node, st, c) {
c(node, st, "Function");
};
base.VariableDeclaration = function(node, st, c) {
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
if (decl.init) c(decl.init, st, "Expression");
}
};
base.Function = function(node, st, c) {
c(node.body, st, "ScopeBody");
};
base.ScopeBody = function(node, st, c) {
c(node, st, "Statement");
};
base.Expression = skipThrough;
base.ThisExpression = ignore;
base.ArrayExpression = function(node, st, c) {
for (var i = 0; i < node.elements.length; ++i) {
var elt = node.elements[i];
if (elt) c(elt, st, "Expression");
}
};
base.ObjectExpression = function(node, st, c) {
for (var i = 0; i < node.properties.length; ++i)
c(node.properties[i].value, st, "Expression");
};
base.FunctionExpression = base.FunctionDeclaration;
base.SequenceExpression = function(node, st, c) {
for (var i = 0; i < node.expressions.length; ++i)
c(node.expressions[i], st, "Expression");
};
base.UnaryExpression = base.UpdateExpression = function(node, st, c) {
c(node.argument, st, "Expression");
};
base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) {
c(node.left, st, "Expression");
c(node.right, st, "Expression");
};
base.ConditionalExpression = function(node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Expression");
c(node.alternate, st, "Expression");
};
base.NewExpression = base.CallExpression = function(node, st, c) {
c(node.callee, st, "Expression");
if (node.arguments) for (var i = 0; i < node.arguments.length; ++i)
c(node.arguments[i], st, "Expression");
};
base.MemberExpression = function(node, st, c) {
c(node.object, st, "Expression");
if (node.computed) c(node.property, st, "Expression");
};
base.Identifier = base.Literal = ignore;
// A custom walker that keeps track of the scope chain and the
// variables defined in it.
function makeScope(prev, isCatch) {
return {vars: Object.create(null), prev: prev, isCatch: isCatch};
}
function normalScope(scope) {
while (scope.isCatch) scope = scope.prev;
return scope;
}
exports.scopeVisitor = exports.make({
Function: function(node, scope, c) {
var inner = makeScope(scope);
for (var i = 0; i < node.params.length; ++i)
inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
if (node.id) {
var decl = node.type == "FunctionDeclaration";
(decl ? normalScope(scope) : inner).vars[node.id.name] =
{type: decl ? "function" : "function name", node: node.id};
}
c(node.body, inner, "ScopeBody");
},
TryStatement: function(node, scope, c) {
c(node.block, scope, "Statement");
if (node.handler) {
var inner = makeScope(scope, true);
inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param};
c(node.handler.body, inner, "ScopeBody");
}
if (node.finalizer) c(node.finalizer, scope, "Statement");
},
VariableDeclaration: function(node, scope, c) {
var target = normalScope(scope);
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
target.vars[decl.id.name] = {type: "var", node: decl.id};
if (decl.init) c(decl.init, scope, "Expression");
}
}
});
});
</script>
<script id="https://raw.githubusercontent.com/marijnh/acorn/4869ccfa55121ba707cefacde4af701c49383156/acorn.js">
// Acorn is a tiny, fast JavaScript parser written in JavaScript.
//
// Acorn was written by Marijn Haverbeke and released under an MIT
// license. The Unicode regexps (for identifiers and whitespace) were
// taken from [Esprima](http://esprima.org) by Ariya Hidayat.
//
// Git repositories for Acorn are available at
//
// http://marijnhaverbeke.nl/git/acorn
// https://github.com/marijnh/acorn.git
//
// Please use the [github bug tracker][ghbt] to report issues.
//
// [ghbt]: https://github.com/marijnh/acorn/issues
//
// This file defines the main parser interface. The library also comes
// with a [error-tolerant parser][dammit] and an
// [abstract syntax tree walker][walk], defined in other files.
//
// [dammit]: acorn_loose.js
// [walk]: util/walk.js
(function(root, mod) {
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
mod(root.acorn || (root.acorn = {})); // Plain browser env
})(this, function(exports) {
"use strict";
exports.version = "0.5.1";
// The main exported interface (under `self.acorn` when in the
// browser) is a `parse` function that takes a code string and
// returns an abstract syntax tree as specified by [Mozilla parser
// API][api], with the caveat that the SpiderMonkey-specific syntax
// (`let`, `yield`, inline XML, etc) is not recognized.
//
// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
var options, input, inputLen, sourceFile;
exports.parse = function(inpt, opts) {
input = String(inpt); inputLen = input.length;
setOptions(opts);
initTokenState();
return parseTopLevel(options.program);
};
// A second optional argument can be given to further configure
// the parser process. These options are recognized:
var defaultOptions = exports.defaultOptions = {
// `ecmaVersion` indicates the ECMAScript version to parse. Must
// be either 3 or 5. This
// influences support for strict mode, the set of reserved words, and
// support for getters and setter.
ecmaVersion: 5,
// Turn on `strictSemicolons` to prevent the parser from doing
// automatic semicolon insertion.
strictSemicolons: false,
// When `allowTrailingCommas` is false, the parser will not allow
// trailing commas in array and object literals.
allowTrailingCommas: true,
// By default, reserved words are not enforced. Enable
// `forbidReserved` to enforce them. When this option has the
// value "everywhere", reserved words and keywords can also not be
// used as property names.
forbidReserved: false,
// When enabled, a return at the top level is not considered an
// error.
allowReturnOutsideFunction: false,
// When `locations` is on, `loc` properties holding objects with
// `start` and `end` properties in `{line, column}` form (with
// line being 1-based and column 0-based) will be attached to the
// nodes.
locations: false,
// A function can be passed as `onComment` option, which will
// cause Acorn to call that function with `(block, text, start,
// end)` parameters whenever a comment is skipped. `block` is a
// boolean indicating whether this is a block (`/* */`) comment,
// `text` is the content of the comment, and `start` and `end` are
// character offsets that denote the start and end of the comment.
// When the `locations` option is on, two more parameters are
// passed, the full `{line, column}` locations of the start and
// end of the comments. Note that you are not allowed to call the
// parser from the callback—that will corrupt its internal state.
onComment: null,
// Nodes have their start and end characters offsets recorded in
// `start` and `end` properties (directly on the node, rather than
// the `loc` object, which holds line/column data. To also add a
// [semi-standardized][range] `range` property holding a `[start,
// end]` array with the same numbers, set the `ranges` option to
// `true`.
//
// [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
ranges: false,
// It is possible to parse multiple files into a single AST by
// passing the tree produced by parsing the first file as
// `program` option in subsequent parses. This will add the
// toplevel forms of the parsed file to the `Program` (top) node
// of an existing parse tree.
program: null,
// When `locations` is on, you can pass this to record the source
// file in every node's `loc` object.
sourceFile: null,
// This value, if given, is stored in every node, whether
// `locations` is on or off.
directSourceFile: null
};
function setOptions(opts) {
options = opts || {};
for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
options[opt] = defaultOptions[opt];
sourceFile = options.sourceFile || null;
}
// The `getLineInfo` function is mostly useful when the
// `locations` option is off (for performance reasons) and you
// want to find the line/column position for a given character
// offset. `input` should be the code string that the offset refers
// into.
var getLineInfo = exports.getLineInfo = function(input, offset) {
for (var line = 1, cur = 0;;) {
lineBreak.lastIndex = cur;
var match = lineBreak.exec(input);
if (match && match.index < offset) {
++line;
cur = match.index + match[0].length;
} else break;
}
return {line: line, column: offset - cur};
};
// Acorn is organized as a tokenizer and a recursive-descent parser.
// The `tokenize` export provides an interface to the tokenizer.
// Because the tokenizer is optimized for being efficiently used by
// the Acorn parser itself, this interface is somewhat crude and not
// very modular. Performing another parse or call to `tokenize` will
// reset the internal state, and invalidate existing tokenizers.
exports.tokenize = function(inpt, opts) {
input = String(inpt); inputLen = input.length;
setOptions(opts);
initTokenState();
var t = {};
function getToken(forceRegexp) {
lastEnd = tokEnd;
readToken(forceRegexp);
t.start = tokStart; t.end = tokEnd;
t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
t.type = tokType; t.value = tokVal;
return t;
}
getToken.jumpTo = function(pos, reAllowed) {
tokPos = pos;
if (options.locations) {
tokCurLine = 1;
tokLineStart = lineBreak.lastIndex = 0;
var match;
while ((match = lineBreak.exec(input)) && match.index < pos) {
++tokCurLine;
tokLineStart = match.index + match[0].length;
}
}
tokRegexpAllowed = reAllowed;
skipSpace();
};
return getToken;
};
// State is kept in (closure-)global variables. We already saw the
// `options`, `input`, and `inputLen` variables above.
// The current position of the tokenizer in the input.
var tokPos;
// The start and end offsets of the current token.
var tokStart, tokEnd;
// When `options.locations` is true, these hold objects
// containing the tokens start and end line/column pairs.
var tokStartLoc, tokEndLoc;
// The type and value of the current token. Token types are objects,
// named by variables against which they can be compared, and
// holding properties that describe them (indicating, for example,
// the precedence of an infix operator, and the original name of a
// keyword token). The kind of value that's held in `tokVal` depends
// on the type of the token. For literals, it is the literal value,
// for operators, the operator name, and so on.
var tokType, tokVal;
// Interal state for the tokenizer. To distinguish between division
// operators and regular expressions, it remembers whether the last
// token was one that is allowed to be followed by an expression.
// (If it is, a slash is probably a regexp, if it isn't it's a
// division operator. See the `parseStatement` function for a
// caveat.)
var tokRegexpAllowed;
// When `options.locations` is true, these are used to keep
// track of the current line, and know when a new line has been
// entered.
var tokCurLine, tokLineStart;
// These store the position of the previous token, which is useful
// when finishing a node and assigning its `end` position.
var lastStart, lastEnd, lastEndLoc;
// This is the parser's state. `inFunction` is used to reject
// `return` statements outside of functions, `labels` to verify that
// `break` and `continue` have somewhere to jump to, and `strict`
// indicates whether strict mode is on.
var inFunction, labels, strict;
// This function is used to raise exceptions on parse errors. It
// takes an offset integer (into the current `input`) to indicate
// the location of the error, attaches the position to the end
// of the error message, and then raises a `SyntaxError` with that
// message.
function raise(pos, message) {
var loc = getLineInfo(input, pos);
message += " (" + loc.line + ":" + loc.column + ")";
var err = new SyntaxError(message);
err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
throw err;
}
// Reused empty array added for node fields that are always empty.
var empty = [];
// ## Token types
// The assignment of fine-grained, information-carrying type objects
// allows the tokenizer to store the information it has about a
// token in a way that is very cheap for the parser to look up.
// All token type variables start with an underscore, to make them
// easy to recognize.
// These are the general types. The `type` property is only used to
// make them recognizeable when debugging.
var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
var _name = {type: "name"}, _eof = {type: "eof"};
// Keyword tokens. The `keyword` property (also used in keyword-like
// operators) indicates that the token originated from an
// identifier-like word, which is used when parsing property names.
//
// The `beforeExpr` property is used to disambiguate between regular
// expressions and divisions. It is set on all token types that can
// be followed by an expression (thus, a slash after them would be a
// regular expression).
//
// `isLoop` marks a keyword as starting a loop, which is important
// to know when parsing a label, in order to allow or disallow
// continue jumps to that label.
var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
var _this = {keyword: "this"};
// The keywords that denote values.
var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
var _false = {keyword: "false", atomValue: false};
// Some keywords are treated as regular operators. `in` sometimes
// (when parsing `for`) needs to be tested against specifically, so
// we assign a variable name to it for quick comparing.
var _in = {keyword: "in", binop: 7, beforeExpr: true};
// Map keyword names to token types.
var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
"continue": _continue, "debugger": _debugger, "default": _default,
"do": _do, "else": _else, "finally": _finally, "for": _for,
"function": _function, "if": _if, "return": _return, "switch": _switch,
"throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
"null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
"instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
"typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
"void": {keyword: "void", prefix: true, beforeExpr: true},
"delete": {keyword: "delete", prefix: true, beforeExpr: true}};
// Punctuation token types. Again, the `type` property is purely for debugging.
var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
// Operators. These carry several kinds of properties to help the
// parser use them properly (the presence of these properties is
// what categorizes them as operators).
//
// `binop`, when present, specifies that this operator is a binary
// operator, and will refer to its precedence.
//
// `prefix` and `postfix` mark the operator as a prefix or postfix
// unary operator. `isUpdate` specifies that the node produced by
// the operator should be of type UpdateExpression rather than
// simply UnaryExpression (`++` and `--`).
//
// `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
// binary operators with a very low precedence, that should result
// in AssignmentExpression nodes.
var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
var _assign = {isAssign: true, beforeExpr: true};
var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
var _logicalOR = {binop: 1, beforeExpr: true};
var _logicalAND = {binop: 2, beforeExpr: true};
var _bitwiseOR = {binop: 3, beforeExpr: true};
var _bitwiseXOR = {binop: 4, beforeExpr: true};
var _bitwiseAND = {binop: 5, beforeExpr: true};
var _equality = {binop: 6, beforeExpr: true};
var _relational = {binop: 7, beforeExpr: true};
var _bitShift = {binop: 8, beforeExpr: true};
var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
var _multiplyModulo = {binop: 10, beforeExpr: true};
// Provide access to the token types for external users of the
// tokenizer.
exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
num: _num, regexp: _regexp, string: _string};
for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
// This is a trick taken from Esprima. It turns out that, on
// non-Chrome browsers, to check whether a string is in a set, a
// predicate containing a big ugly `switch` statement is faster than
// a regular expression, and on Chrome the two are about on par.
// This function uses `eval` (non-lexical) to produce such a
// predicate from a space-separated string of words.
//
// It starts by sorting the words by length.
function makePredicate(words) {
words = words.split(" ");
var f = "", cats = [];
out: for (var i = 0; i < words.length; ++i) {
for (var j = 0; j < cats.length; ++j)
if (cats[j][0].length == words[i].length) {
cats[j].push(words[i]);
continue out;
}
cats.push([words[i]]);
}
function compareTo(arr) {
if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
f += "switch(str){";
for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
f += "return true}return false;";
}
// When there are more than three length categories, an outer
// switch first dispatches on the lengths, to save on comparisons.
if (cats.length > 3) {
cats.sort(function(a, b) {return b.length - a.length;});
f += "switch(str.length){";
for (var i = 0; i < cats.length; ++i) {
var cat = cats[i];
f += "case " + cat[0].length + ":";
compareTo(cat);
}
f += "}";
// Otherwise, simply generate a flat `switch` statement.
} else {
compareTo(words);
}
return new Function("str", f);
}
// The ECMAScript 3 reserved word list.
var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
// ECMAScript 5 reserved words.
var isReservedWord5 = makePredicate("class enum extends super const export import");
// The additional reserved words in strict mode.
var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
// The forbidden variable names in strict mode.
var isStrictBadIdWord = makePredicate("eval arguments");
// And the keywords.
var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this");
// ## Character categories
// Big ugly regular expressions that match characters in the
// whitespace, identifier, and identifier-start categories. These
// are only applied when a character is found to actually have a
// code point above 128.
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
// Whether a single character denotes a newline.
var newline = /[\n\r\u2028\u2029]/;
// Matches a whole line break (where CRLF is considered a single
// line break). Used to count lines.
var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;
// Test whether a given character code starts an identifier.
var isIdentifierStart = exports.isIdentifierStart = function(code) {
if (code < 65) return code === 36;
if (code < 91) return true;
if (code < 97) return code === 95;
if (code < 123)return true;
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
};
// Test whether a given character is part of an identifier.
var isIdentifierChar = exports.isIdentifierChar = function(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code < 91) return true;
if (code < 97) return code === 95;
if (code < 123)return true;
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
};
// ## Tokenizer
// These are used when `options.locations` is on, for the
// `tokStartLoc` and `tokEndLoc` properties.
function Position() {
this.line = tokCurLine;
this.column = tokPos - tokLineStart;
}
// Reset the token state. Used at the start of a parse.
function initTokenState() {
tokCurLine = 1;
tokPos = tokLineStart = 0;
tokRegexpAllowed = true;
skipSpace();
}
// Called at the end of every token. Sets `tokEnd`, `tokVal`, and
// `tokRegexpAllowed`, and skips the space after the token, so that
// the next one's `tokStart` will point at the right position.
function finishToken(type, val) {
tokEnd = tokPos;
if (options.locations) tokEndLoc = new Position;
tokType = type;
skipSpace();
tokVal = val;
tokRegexpAllowed = type.beforeExpr;
}
function skipBlockComment() {
var startLoc = options.onComment && options.locations && new Position;
var start = tokPos, end = input.indexOf("*/", tokPos += 2);
if (end === -1) raise(tokPos - 2, "Unterminated comment");
tokPos = end + 2;
if (options.locations) {
lineBreak.lastIndex = start;
var match;
while ((match = lineBreak.exec(input)) && match.index < tokPos) {
++tokCurLine;
tokLineStart = match.index + match[0].length;
}
}
if (options.onComment)
options.onComment(true, input.slice(start + 2, end), start, tokPos,
startLoc, options.locations && new Position);
}
function skipLineComment() {
var start = tokPos;
var startLoc = options.onComment && options.locations && new Position;
var ch = input.charCodeAt(tokPos+=2);
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
++tokPos;
ch = input.charCodeAt(tokPos);
}
if (options.onComment)
options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,
startLoc, options.locations && new Position);
}
// Called at the start of the parse and after every token. Skips
// whitespace and comments, and.
function skipSpace() {
while (tokPos < inputLen) {
var ch = input.charCodeAt(tokPos);
if (ch === 32) { // ' '
++tokPos;
} else if (ch === 13) {
++tokPos;
var next = input.charCodeAt(tokPos);
if (next === 10) {
++tokPos;
}
if (options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
} else if (ch === 10 || ch === 8232 || ch === 8233) {
++tokPos;
if (options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
} else if (ch > 8 && ch < 14) {
++tokPos;
} else if (ch === 47) { // '/'
var next = input.charCodeAt(tokPos + 1);
if (next === 42) { // '*'
skipBlockComment();
} else if (next === 47) { // '/'
skipLineComment();
} else break;
} else if (ch === 160) { // '\xa0'
++tokPos;
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
++tokPos;
} else {
break;
}
}
}
// ### Token reading
// This is the function that is called to fetch the next token. It
// is somewhat obscure, because it works in character codes rather
// than characters, and because operator parsing has been inlined
// into it.
//
// All in the name of speed.
//
// The `forceRegexp` parameter is used in the one case where the
// `tokRegexpAllowed` trick does not work. See `parseStatement`.
function readToken_dot() {
var next = input.charCodeAt(tokPos + 1);
if (next >= 48 && next <= 57) return readNumber(true);
++tokPos;
return finishToken(_dot);
}
function readToken_slash() { // '/'
var next = input.charCodeAt(tokPos + 1);
if (tokRegexpAllowed) {++tokPos; return readRegexp();}
if (next === 61) return finishOp(_assign, 2);
return finishOp(_slash, 1);
}
function readToken_mult_modulo() { // '%*'
var next = input.charCodeAt(tokPos + 1);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_multiplyModulo, 1);
}
function readToken_pipe_amp(code) { // '|&'
var next = input.charCodeAt(tokPos + 1);
if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
if (next === 61) return finishOp(_assign, 2);
return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
}
function readToken_caret() { // '^'
var next = input.charCodeAt(tokPos + 1);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_bitwiseXOR, 1);
}
function readToken_plus_min(code) { // '+-'
var next = input.charCodeAt(tokPos + 1);
if (next === code) {
if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
newline.test(input.slice(lastEnd, tokPos))) {
// A `-->` line comment
tokPos += 3;
skipLineComment();
skipSpace();
return readToken();
}
return finishOp(_incDec, 2);
}
if (next === 61) return finishOp(_assign, 2);
return finishOp(_plusMin, 1);
}
function readToken_lt_gt(code) { // '<>'
var next = input.charCodeAt(tokPos + 1);
var size = 1;
if (next === code) {
size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
return finishOp(_bitShift, size);
}
if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
input.charCodeAt(tokPos + 3) == 45) {
// `<!--`, an XML-style comment that should be interpreted as a line comment
tokPos += 4;
skipLineComment();
skipSpace();
return readToken();
}
if (next === 61)
size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
return finishOp(_relational, size);
}
function readToken_eq_excl(code) { // '=!'
var next = input.charCodeAt(tokPos + 1);
if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
return finishOp(code === 61 ? _eq : _prefix, 1);
}
function getTokenFromCode(code) {
switch(code) {
// The interpretation of a dot depends on whether it is followed
// by a digit.
case 46: // '.'
return readToken_dot();
// Punctuation tokens.
case 40: ++tokPos; return finishToken(_parenL);
case 41: ++tokPos; return finishToken(_parenR);
case 59: ++tokPos; return finishToken(_semi);
case 44: ++tokPos; return finishToken(_comma);
case 91: ++tokPos; return finishToken(_bracketL);
case 93: ++tokPos; return finishToken(_bracketR);
case 123: ++tokPos; return finishToken(_braceL);
case 125: ++tokPos; return finishToken(_braceR);
case 58: ++tokPos; return finishToken(_colon);
case 63: ++tokPos; return finishToken(_question);
// '0x' is a hexadecimal number.
case 48: // '0'
var next = input.charCodeAt(tokPos + 1);
if (next === 120 || next === 88) return readHexNumber();
// Anything else beginning with a digit is an integer, octal
// number, or float.
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
return readNumber(false);
// Quotes produce strings.
case 34: case 39: // '"', "'"
return readString(code);
// Operators are parsed inline in tiny state machines. '=' (61) is
// often referred to. `finishOp` simply skips the amount of
// characters it is given as second argument, and returns a token
// of the type given by its first argument.
case 47: // '/'
return readToken_slash(code);
case 37: case 42: // '%*'
return readToken_mult_modulo();
case 124: case 38: // '|&'
return readToken_pipe_amp(code);
case 94: // '^'
return readToken_caret();
case 43: case 45: // '+-'
return readToken_plus_min(code);
case 60: case 62: // '<>'
return readToken_lt_gt(code);
case 61: case 33: // '=!'
return readToken_eq_excl(code);
case 126: // '~'
return finishOp(_prefix, 1);
}
return false;
}
function readToken(forceRegexp) {
if (!forceRegexp) tokStart = tokPos;
else tokPos = tokStart + 1;
if (options.locations) tokStartLoc = new Position;
if (forceRegexp) return readRegexp();
if (tokPos >= inputLen) return finishToken(_eof);
var code = input.charCodeAt(tokPos);
// Identifier or keyword. '\uXXXX' sequences are allowed in
// identifiers, so '\' also dispatches to that.
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
var tok = getTokenFromCode(code);
if (tok === false) {
// If we are here, we either found a non-ASCII identifier
// character, or something that's entirely disallowed.
var ch = String.fromCharCode(code);
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
raise(tokPos, "Unexpected character '" + ch + "'");
}
return tok;
}
function finishOp(type, size) {
var str = input.slice(tokPos, tokPos + size);
tokPos += size;
finishToken(type, str);
}
// Parse a regular expression. Some context-awareness is necessary,
// since a '/' inside a '[]' set does not end the expression.
function readRegexp() {
var content = "", escaped, inClass, start = tokPos;
for (;;) {
if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
var ch = input.charAt(tokPos);
if (newline.test(ch)) raise(start, "Unterminated regular expression");
if (!escaped) {
if (ch === "[") inClass = true;
else if (ch === "]" && inClass) inClass = false;
else if (ch === "/" && !inClass) break;
escaped = ch === "\\";
} else escaped = false;
++tokPos;
}
var content = input.slice(start, tokPos);
++tokPos;
// Need to use `readWord1` because '\uXXXX' sequences are allowed
// here (don't ask).
var mods = readWord1();
if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regular expression flag");
try {
var value = new RegExp(content, mods);
} catch (e) {
if (e instanceof SyntaxError) raise(start, "Error parsing regular expression: " + e.message);
raise(e);
}
return finishToken(_regexp, value);
}
// Read an integer in the given radix. Return null if zero digits
// were read, the integer value otherwise. When `len` is given, this
// will return `null` unless the integer has exactly `len` digits.
function readInt(radix, len) {
var start = tokPos, total = 0;
for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
var code = input.charCodeAt(tokPos), val;
if (code >= 97) val = code - 97 + 10; // a
else if (code >= 65) val = code - 65 + 10; // A
else if (code >= 48 && code <= 57) val = code - 48; // 0-9
else val = Infinity;
if (val >= radix) break;
++tokPos;
total = total * radix + val;
}
if (tokPos === start || len != null && tokPos - start !== len) return null;
return total;
}
function readHexNumber() {
tokPos += 2; // 0x
var val = readInt(16);
if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
return finishToken(_num, val);
}
// Read an integer, octal integer, or floating-point number.
function readNumber(startsWithDot) {
var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
if (input.charCodeAt(tokPos) === 46) {
++tokPos;
readInt(10);
isFloat = true;
}
var next = input.charCodeAt(tokPos);
if (next === 69 || next === 101) { // 'eE'
next = input.charCodeAt(++tokPos);
if (next === 43 || next === 45) ++tokPos; // '+-'
if (readInt(10) === null) raise(start, "Invalid number");
isFloat = true;
}
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
var str = input.slice(start, tokPos), val;
if (isFloat) val = parseFloat(str);
else if (!octal || str.length === 1) val = parseInt(str, 10);
else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
else val = parseInt(str, 8);
return finishToken(_num, val);
}
// Read a string value, interpreting backslash-escapes.
function readString(quote) {
tokPos++;
var out = "";
for (;;) {
if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
var ch = input.charCodeAt(tokPos);
if (ch === quote) {
++tokPos;
return finishToken(_string, out);
}
if (ch === 92) { // '\'
ch = input.charCodeAt(++tokPos);
var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
if (octal) octal = octal[0];
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
if (octal === "0") octal = null;
++tokPos;
if (octal) {
if (strict) raise(tokPos - 2, "Octal literal in strict mode");
out += String.fromCharCode(parseInt(octal, 8));
tokPos += octal.length - 1;
} else {
switch (ch) {
case 110: out += "\n"; break; // 'n' -> '\n'
case 114: out += "\r"; break; // 'r' -> '\r'
case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x'
case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u'
case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U'
case 116: out += "\t"; break; // 't' -> '\t'
case 98: out += "\b"; break; // 'b' -> '\b'
case 118: out += "\u000b"; break; // 'v' -> '\u000b'
case 102: out += "\f"; break; // 'f' -> '\f'
case 48: out += "\0"; break; // 0 -> '\0'
case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n'
case 10: // ' \n'
if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
break;
default: out += String.fromCharCode(ch); break;
}
}
} else {
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
out += String.fromCharCode(ch); // '\'
++tokPos;
}
}
}
// Used to read character escape sequences ('\x', '\u', '\U').
function readHexChar(len) {
var n = readInt(16, len);
if (n === null) raise(tokStart, "Bad character escape sequence");
return n;
}
// Used to signal to callers of `readWord1` whether the word
// contained any escape sequences. This is needed because words with
// escape sequences must not be interpreted as keywords.
var containsEsc;
// Read an identifier, and return it as a string. Sets `containsEsc`
// to whether the word contained a '\u' escape.
//
// Only builds up the word character-by-character when it actually
// containeds an escape, as a micro-optimization.
function readWord1() {
containsEsc = false;
var word, first = true, start = tokPos;
for (;;) {
var ch = input.charCodeAt(tokPos);
if (isIdentifierChar(ch)) {
if (containsEsc) word += input.charAt(tokPos);
++tokPos;
} else if (ch === 92) { // "\"
if (!containsEsc) word = input.slice(start, tokPos);
containsEsc = true;
if (input.charCodeAt(++tokPos) != 117) // "u"
raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
++tokPos;
var esc = readHexChar(4);
var escStr = String.fromCharCode(esc);
if (!escStr) raise(tokPos - 1, "Invalid Unicode escape");
if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))
raise(tokPos - 4, "Invalid Unicode escape");
word += escStr;
} else {
break;
}
first = false;
}
return containsEsc ? word : input.slice(start, tokPos);
}
// Read an identifier or keyword token. Will check for reserved
// words when necessary.
function readWord() {
var word = readWord1();
var type = _name;
if (!containsEsc && isKeyword(word))
type = keywordTypes[word];
return finishToken(type, word);
}
// ## Parser
// A recursive descent parser operates by defining functions for all
// syntactic elements, and recursively calling those, each function
// advancing the input stream and returning an AST node. Precedence
// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
// instead of `(!x)[1]` is handled by the fact that the parser
// function that parses unary prefix operators is called first, and
// in turn calls the function that parses `[]` subscripts — that
// way, it'll receive the node for `x[1]` already parsed, and wraps
// *that* in the unary operator node.
//
// Acorn uses an [operator precedence parser][opp] to handle binary
// operator precedence, because it is much more compact than using
// the technique outlined above, which uses different, nesting
// functions to specify precedence, for all of the ten binary
// precedence levels that JavaScript defines.
//
// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
// ### Parser utilities
// Continue to the next token.
function next() {
lastStart = tokStart;
lastEnd = tokEnd;
lastEndLoc = tokEndLoc;
readToken();
}
// Enter strict mode. Re-reads the next token to please pedantic
// tests ("use strict"; 010; -- should fail).
function setStrict(strct) {
strict = strct;
tokPos = tokStart;
if (options.locations) {
while (tokPos < tokLineStart) {
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
--tokCurLine;
}
}
skipSpace();
readToken();
}
// Start an AST node, attaching a start offset.
function Node() {
this.type = null;
this.start = tokStart;
this.end = null;
}
exports.Node = Node;
function SourceLocation() {
this.start = tokStartLoc;
this.end = null;
if (sourceFile !== null) this.source = sourceFile;
}
function startNode() {
var node = new Node();
if (options.locations)
node.loc = new SourceLocation();
if (options.directSourceFile)
node.sourceFile = options.directSourceFile;
if (options.ranges)
node.range = [tokStart, 0];
return node;
}
// Start a node whose start offset information should be based on
// the start of another node. For example, a binary operator node is
// only started after its left-hand side has already been parsed.
function startNodeFrom(other) {
var node = new Node();
node.start = other.start;
if (options.locations) {
node.loc = new SourceLocation();
node.loc.start = other.loc.start;
}
if (options.ranges)
node.range = [other.range[0], 0];
return node;
}
// Finish an AST node, adding `type` and `end` properties.
function finishNode(node, type) {
node.type = type;
node.end = lastEnd;
if (options.locations)
node.loc.end = lastEndLoc;
if (options.ranges)
node.range[1] = lastEnd;
return node;
}
// Test whether a statement node is the string literal `"use strict"`.
function isUseStrict(stmt) {
return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
}
// Predicate that tests whether the next token is of the given
// type, and if yes, consumes it as a side effect.
function eat(type) {
if (tokType === type) {
next();
return true;
}
}
// Test whether a semicolon can be inserted at the current position.
function canInsertSemicolon() {
return !options.strictSemicolons &&
(tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));
}
// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.
function semicolon() {
if (!eat(_semi) && !canInsertSemicolon()) unexpected();
}
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error.
function expect(type) {
if (tokType === type) next();
else unexpected();
}
// Raise an unexpected token error.
function unexpected() {
raise(tokStart, "Unexpected token");
}
// Verify that a node is an lval — something that can be assigned
// to.
function checkLVal(expr) {
if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
raise(expr.start, "Assigning to rvalue");
if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
raise(expr.start, "Assigning to " + expr.name + " in strict mode");
}
// ### Statement parsing
// Parse a program. Initializes the parser, reads any number of
// statements, and wraps them in a Program node. Optionally takes a
// `program` argument. If present, the statements will be appended
// to its body instead of creating a new node.
function parseTopLevel(program) {
lastStart = lastEnd = tokPos;
if (options.locations) lastEndLoc = new Position;
inFunction = strict = null;
labels = [];
readToken();
var node = program || startNode(), first = true;
if (!program) node.body = [];
while (tokType !== _eof) {
var stmt = parseStatement();
node.body.push(stmt);
if (first && isUseStrict(stmt)) setStrict(true);
first = false;
}
return finishNode(node, "Program");
}
var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
// Parse a single statement.
//
// If expecting a statement and finding a slash operator, parse a
// regular expression literal. This is to handle cases like
// `if (foo) /blah/.exec(foo);`, where looking at the previous token
// does not help.
function parseStatement() {
if (tokType === _slash || tokType === _assign && tokVal == "/=")
readToken(true);
var starttype = tokType, node = startNode();
// Most types of statements are recognized by the keyword they
// start with. Many are trivial to parse, some require a bit of
// complexity.
switch (starttype) {
case _break: case _continue:
next();
var isBreak = starttype === _break;
if (eat(_semi) || canInsertSemicolon()) node.label = null;
else if (tokType !== _name) unexpected();
else {
node.label = parseIdent();
semicolon();
}
// Verify that there is an actual destination to break or
// continue to.
for (var i = 0; i < labels.length; ++i) {
var lab = labels[i];
if (node.label == null || lab.name === node.label.name) {
if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
if (node.label && isBreak) break;
}
}
if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
case _debugger:
next();
semicolon();
return finishNode(node, "DebuggerStatement");
case _do:
next();
labels.push(loopLabel);
node.body = parseStatement();
labels.pop();
expect(_while);
node.test = parseParenExpression();
semicolon();
return finishNode(node, "DoWhileStatement");
// Disambiguating between a `for` and a `for`/`in` loop is
// non-trivial. Basically, we have to parse the init `var`
// statement or expression, disallowing the `in` operator (see
// the second parameter to `parseExpression`), and then check
// whether the next token is `in`. When there is no init part
// (semicolon immediately after the opening parenthesis), it is
// a regular `for` loop.
case _for:
next();
labels.push(loopLabel);
expect(_parenL);
if (tokType === _semi) return parseFor(node, null);
if (tokType === _var) {
var init = startNode();
next();
parseVar(init, true);
finishNode(init, "VariableDeclaration");
if (init.declarations.length === 1 && eat(_in))
return parseForIn(node, init);
return parseFor(node, init);
}
var init = parseExpression(false, true);
if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
return parseFor(node, init);
case _function:
next();
return parseFunction(node, true);
case _if:
next();
node.test = parseParenExpression();
node.consequent = parseStatement();
node.alternate = eat(_else) ? parseStatement() : null;
return finishNode(node, "IfStatement");
case _return:
if (!inFunction && !options.allowReturnOutsideFunction)
raise(tokStart, "'return' outside of function");
next();
// In `return` (and `break`/`continue`), the keywords with
// optional arguments, we eagerly look for a semicolon or the
// possibility to insert one.
if (eat(_semi) || canInsertSemicolon()) node.argument = null;
else { node.argument = parseExpression(); semicolon(); }
return finishNode(node, "ReturnStatement");
case _switch:
next();
node.discriminant = parseParenExpression();
node.cases = [];
expect(_braceL);
labels.push(switchLabel);
// Statements under must be grouped (by label) in SwitchCase
// nodes. `cur` is used to keep the node that we are currently
// adding statements to.
for (var cur, sawDefault; tokType != _braceR;) {
if (tokType === _case || tokType === _default) {
var isCase = tokType === _case;
if (cur) finishNode(cur, "SwitchCase");
node.cases.push(cur = startNode());
cur.consequent = [];
next();
if (isCase) cur.test = parseExpression();
else {
if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
cur.test = null;
}
expect(_colon);
} else {
if (!cur) unexpected();
cur.consequent.push(parseStatement());
}
}
if (cur) finishNode(cur, "SwitchCase");
next(); // Closing brace
labels.pop();
return finishNode(node, "SwitchStatement");
case _throw:
next();
if (newline.test(input.slice(lastEnd, tokStart)))
raise(lastEnd, "Illegal newline after throw");
node.argument = parseExpression();
semicolon();
return finishNode(node, "ThrowStatement");
case _try:
next();
node.block = parseBlock();
node.handler = null;
if (tokType === _catch) {
var clause = startNode();
next();
expect(_parenL);
clause.param = parseIdent();
if (strict && isStrictBadIdWord(clause.param.name))
raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
expect(_parenR);
clause.guard = null;
clause.body = parseBlock();
node.handler = finishNode(clause, "CatchClause");
}
node.guardedHandlers = empty;
node.finalizer = eat(_finally) ? parseBlock() : null;
if (!node.handler && !node.finalizer)
raise(node.start, "Missing catch or finally clause");
return finishNode(node, "TryStatement");
case _var:
next();
parseVar(node);
semicolon();
return finishNode(node, "VariableDeclaration");
case _while:
next();
node.test = parseParenExpression();
labels.push(loopLabel);
node.body = parseStatement();
labels.pop();
return finishNode(node, "WhileStatement");
case _with:
if (strict) raise(tokStart, "'with' in strict mode");
next();
node.object = parseParenExpression();
node.body = parseStatement();
return finishNode(node, "WithStatement");
case _braceL:
return parseBlock();
case _semi:
next();
return finishNode(node, "EmptyStatement");
// If the statement does not start with a statement keyword or a
// brace, it's an ExpressionStatement or LabeledStatement. We
// simply start parsing an expression, and afterwards, if the
// next token is a colon and the expression was a simple
// Identifier node, we switch to interpreting it as a label.
default:
var maybeName = tokVal, expr = parseExpression();
if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
for (var i = 0; i < labels.length; ++i)
if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
labels.push({name: maybeName, kind: kind});
node.body = parseStatement();
labels.pop();
node.label = expr;
return finishNode(node, "LabeledStatement");
} else {
node.expression = expr;
semicolon();
return finishNode(node, "ExpressionStatement");
}
}
}
// Used for constructs like `switch` and `if` that insist on
// parentheses around their expression.
function parseParenExpression() {
expect(_parenL);
var val = parseExpression();
expect(_parenR);
return val;
}
// Parse a semicolon-enclosed block of statements, handling `"use
// strict"` declarations when `allowStrict` is true (used for
// function bodies).
function parseBlock(allowStrict) {
var node = startNode(), first = true, strict = false, oldStrict;
node.body = [];
expect(_braceL);
while (!eat(_braceR)) {
var stmt = parseStatement();
node.body.push(stmt);
if (first && allowStrict && isUseStrict(stmt)) {
oldStrict = strict;
setStrict(strict = true);
}
first = false;
}
if (strict && !oldStrict) setStrict(false);
return finishNode(node, "BlockStatement");
}
// Parse a regular `for` loop. The disambiguation code in
// `parseStatement` will already have parsed the init statement or
// expression.
function parseFor(node, init) {
node.init = init;
expect(_semi);
node.test = tokType === _semi ? null : parseExpression();
expect(_semi);
node.update = tokType === _parenR ? null : parseExpression();
expect(_parenR);
node.body = parseStatement();
labels.pop();
return finishNode(node, "ForStatement");
}
// Parse a `for`/`in` loop.
function parseForIn(node, init) {
node.left = init;
node.right = parseExpression();
expect(_parenR);
node.body = parseStatement();
labels.pop();
return finishNode(node, "ForInStatement");
}
// Parse a list of variable declarations.
function parseVar(node, noIn) {
node.declarations = [];
node.kind = "var";
for (;;) {
var decl = startNode();
decl.id = parseIdent();
if (strict && isStrictBadIdWord(decl.id.name))
raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
node.declarations.push(finishNode(decl, "VariableDeclarator"));
if (!eat(_comma)) break;
}
return node;
}
// ### Expression parsing
// These nest, from the most general expression type at the top to
// 'atomic', nondivisible expression types at the bottom. Most of
// the functions will simply let the function(s) below them parse,
// and, *if* the syntactic construct they handle is present, wrap
// the AST node that the inner parser gave them in another node.
// Parse a full expression. The arguments are used to forbid comma
// sequences (in argument lists, array literals, or object literals)
// or the `in` operator (in for loops initalization expressions).
function parseExpression(noComma, noIn) {
var expr = parseMaybeAssign(noIn);
if (!noComma && tokType === _comma) {
var node = startNodeFrom(expr);
node.expressions = [expr];
while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
return finishNode(node, "SequenceExpression");
}
return expr;
}
// Parse an assignment expression. This includes applications of
// operators like `+=`.
function parseMaybeAssign(noIn) {
var left = parseMaybeConditional(noIn);
if (tokType.isAssign) {
var node = startNodeFrom(left);
node.operator = tokVal;
node.left = left;
next();
node.right = parseMaybeAssign(noIn);
checkLVal(left);
return finishNode(node, "AssignmentExpression");
}
return left;
}
// Parse a ternary conditional (`?:`) operator.
function parseMaybeConditional(noIn) {
var expr = parseExprOps(noIn);
if (eat(_question)) {
var node = startNodeFrom(expr);
node.test = expr;
node.consequent = parseExpression(true);
expect(_colon);
node.alternate = parseExpression(true, noIn);
return finishNode(node, "ConditionalExpression");
}
return expr;
}
// Start the precedence parser.
function parseExprOps(noIn) {
return parseExprOp(parseMaybeUnary(), -1, noIn);
}
// Parse binary operators with the operator precedence parsing
// algorithm. `left` is the left-hand side of the operator.
// `minPrec` provides context that allows the function to stop and
// defer further parser to one of its callers when it encounters an
// operator that has a lower precedence than the set it is parsing.
function parseExprOp(left, minPrec, noIn) {
var prec = tokType.binop;
if (prec != null && (!noIn || tokType !== _in)) {
if (prec > minPrec) {
var node = startNodeFrom(left);
node.left = left;
node.operator = tokVal;
var op = tokType;
next();
node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
return parseExprOp(exprNode, minPrec, noIn);
}
}
return left;
}
// Parse unary operators, both prefix and postfix.
function parseMaybeUnary() {
if (tokType.prefix) {
var node = startNode(), update = tokType.isUpdate;
node.operator = tokVal;
node.prefix = true;
tokRegexpAllowed = true;
next();
node.argument = parseMaybeUnary();
if (update) checkLVal(node.argument);
else if (strict && node.operator === "delete" &&
node.argument.type === "Identifier")
raise(node.start, "Deleting local variable in strict mode");
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
}
var expr = parseExprSubscripts();
while (tokType.postfix && !canInsertSemicolon()) {
var node = startNodeFrom(expr);
node.operator = tokVal;
node.prefix = false;
node.argument = expr;
checkLVal(expr);
next();
expr = finishNode(node, "UpdateExpression");
}
return expr;
}
// Parse call, dot, and `[]`-subscript expressions.
function parseExprSubscripts() {
return parseSubscripts(parseExprAtom());
}
function parseSubscripts(base, noCalls) {
if (eat(_dot)) {
var node = startNodeFrom(base);
node.object = base;
node.property = parseIdent(true);
node.computed = false;
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (eat(_bracketL)) {
var node = startNodeFrom(base);
node.object = base;
node.property = parseExpression();
node.computed = true;
expect(_bracketR);
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (!noCalls && eat(_parenL)) {
var node = startNodeFrom(base);
node.callee = base;
node.arguments = parseExprList(_parenR, false);
return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
} else return base;
}
// Parse an atomic expression — either a single token that is an
// expression, an expression started by a keyword like `function` or
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
// or `{}`.
function parseExprAtom() {
switch (tokType) {
case _this:
var node = startNode();
next();
return finishNode(node, "ThisExpression");
case _name:
return parseIdent();
case _num: case _string: case _regexp:
var node = startNode();
node.value = tokVal;
node.raw = input.slice(tokStart, tokEnd);
next();
return finishNode(node, "Literal");
case _null: case _true: case _false:
var node = startNode();
node.value = tokType.atomValue;
node.raw = tokType.keyword;
next();
return finishNode(node, "Literal");
case _parenL:
var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart;
next();
var val = parseExpression();
val.start = tokStart1;
val.end = tokEnd;
if (options.locations) {
val.loc.start = tokStartLoc1;
val.loc.end = tokEndLoc;
}
if (options.ranges)
val.range = [tokStart1, tokEnd];
expect(_parenR);
return val;
case _bracketL:
var node = startNode();
next();
node.elements = parseExprList(_bracketR, true, true);
return finishNode(node, "ArrayExpression");
case _braceL:
return parseObj();
case _function:
var node = startNode();
next();
return parseFunction(node, false);
case _new:
return parseNew();
default:
unexpected();
}
}
// New's precedence is slightly tricky. It must allow its argument
// to be a `[]` or dot subscript expression, but not a call — at
// least, not without wrapping it in parentheses. Thus, it uses the
function parseNew() {
var node = startNode();
next();
node.callee = parseSubscripts(parseExprAtom(), true);
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
else node.arguments = empty;
return finishNode(node, "NewExpression");
}
// Parse an object literal.
function parseObj() {
var node = startNode(), first = true, sawGetSet = false;
node.properties = [];
next();
while (!eat(_braceR)) {
if (!first) {
expect(_comma);
if (options.allowTrailingCommas && eat(_braceR)) break;
} else first = false;
var prop = {key: parsePropertyName()}, isGetSet = false, kind;
if (eat(_colon)) {
prop.value = parseExpression(true);
kind = prop.kind = "init";
} else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
(prop.key.name === "get" || prop.key.name === "set")) {
isGetSet = sawGetSet = true;
kind = prop.kind = prop.key.name;
prop.key = parsePropertyName();
if (tokType !== _parenL) unexpected();
prop.value = parseFunction(startNode(), false);
} else unexpected();
// getters and setters are not allowed to clash — either with
// each other or with an init property — and in strict mode,
// init properties are also not allowed to be repeated.
if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
for (var i = 0; i < node.properties.length; ++i) {
var other = node.properties[i];
if (other.key.name === prop.key.name) {
var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
kind === "init" && (other.kind === "get" || other.kind === "set");
if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
if (conflict) raise(prop.key.start, "Redefinition of property");
}
}
}
node.properties.push(prop);
}
return finishNode(node, "ObjectExpression");
}
function parsePropertyName() {
if (tokType === _num || tokType === _string) return parseExprAtom();
return parseIdent(true);
}
// Parse a function declaration or literal (depending on the
// `isStatement` parameter).
function parseFunction(node, isStatement) {
if (tokType === _name) node.id = parseIdent();
else if (isStatement) unexpected();
else node.id = null;
node.params = [];
var first = true;
expect(_parenL);
while (!eat(_parenR)) {
if (!first) expect(_comma); else first = false;
node.params.push(parseIdent());
}
// Start a new scope with regard to labels and the `inFunction`
// flag (restore them to their old value afterwards).
var oldInFunc = inFunction, oldLabels = labels;
inFunction = true; labels = [];
node.body = parseBlock(true);
inFunction = oldInFunc; labels = oldLabels;
// If this is a strict mode function, verify that argument names
// are not repeated, and it does not try to bind the words `eval`
// or `arguments`.
if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
var id = i < 0 ? node.id : node.params[i];
if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
raise(id.start, "Defining '" + id.name + "' in strict mode");
if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
raise(id.start, "Argument name clash in strict mode");
}
}
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
}
// Parses a comma-separated list of expressions, and returns them as
// an array. `close` is the token type that ends the list, and
// `allowEmpty` can be turned on to allow subsequent commas with
// nothing in between them to be parsed as `null` (which is needed
// for array literals).
function parseExprList(close, allowTrailingComma, allowEmpty) {
var elts = [], first = true;
while (!eat(close)) {
if (!first) {
expect(_comma);
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
} else first = false;
if (allowEmpty && tokType === _comma) elts.push(null);
else elts.push(parseExpression(true));
}
return elts;
}
// Parse the next token as an identifier. If `liberal` is true (used
// when parsing properties), it will also convert keywords into
// identifiers.
function parseIdent(liberal) {
var node = startNode();
if (liberal && options.forbidReserved == "everywhere") liberal = false;
if (tokType === _name) {
if (!liberal &&
(options.forbidReserved &&
(options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||
strict && isStrictReservedWord(tokVal)) &&
input.slice(tokStart, tokEnd).indexOf("\\") == -1)
raise(tokStart, "The keyword '" + tokVal + "' is reserved");
node.name = tokVal;
} else if (liberal && tokType.keyword) {
node.name = tokType.keyword;
} else {
unexpected();
}
tokRegexpAllowed = false;
next();
return finishNode(node, "Identifier");
}
});
</script>
<script id="https://raw.githubusercontent.com/marijnh/tern/1abe6ff5963dcfffb5499c7ae75ced31af99d92e/lib/signal.js">
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return mod(exports);
if (typeof define == "function" && define.amd) // AMD
return define(["exports"], mod);
mod((self.tern || (self.tern = {})).signal = {}); // Plain browser env
})(function(exports) {
function on(type, f) {
var handlers = this._handlers || (this._handlers = Object.create(null));
(handlers[type] || (handlers[type] = [])).push(f);
}
function off(type, f) {
var arr = this._handlers && this._handlers[type];
if (arr) for (var i = 0; i < arr.length; ++i)
if (arr[i] == f) { arr.splice(i, 1); break; }
}
function signal(type, a1, a2, a3, a4) {
var arr = this._handlers && this._handlers[type];
if (arr) for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4);
}
exports.mixin = function(obj) {
obj.on = on; obj.off = off; obj.signal = signal;
return obj;
};
});
</script>
<script id="https://raw.githubusercontent.com/marijnh/tern/1abe6ff5963dcfffb5499c7ae75ced31af99d92e/lib/def.js">
// Type description parser
//
// Type description JSON files (such as ecma5.json and browser.json)
// are used to
//
// A) describe types that come from native code
//
// B) to cheaply load the types for big libraries, or libraries that
// can't be inferred well
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return exports.init = mod;
if (typeof define == "function" && define.amd) // AMD
return define({init: mod});
tern.def = {init: mod};
})(function(exports, infer) {
"use strict";
function hop(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
this.pos = start || 0;
this.spec = spec;
this.base = base;
this.forceNew = forceNew;
};
TypeParser.prototype = {
eat: function(str) {
if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) {
this.pos += str.length;
return true;
}
},
word: function(re) {
var word = "", ch, re = re || /[\w$]/;
while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; }
return word;
},
error: function() {
throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")");
},
parseFnType: function(name, top) {
var args = [], names = [];
if (!this.eat(")")) for (var i = 0; ; ++i) {
var colon = this.spec.indexOf(": ", this.pos), argname;
if (colon != -1) {
argname = this.spec.slice(this.pos, colon);
if (/^[$\w?]+$/.test(argname))
this.pos = colon + 2;
else
argname = null;
}
names.push(argname);
args.push(this.parseType());
if (!this.eat(", ")) {
this.eat(")") || this.error();
break;
}
}
var retType, computeRet, computeRetStart, fn;
if (this.eat(" -> ")) {
if (top && this.spec.indexOf("!", this.pos) > -1) {
retType = infer.ANull;
computeRetStart = this.pos;
computeRet = this.parseRetType();
} else retType = this.parseType();
} else retType = infer.ANull;
if (top && (fn = this.base))
infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
else
fn = new infer.Fn(name, infer.ANull, args, names, retType);
if (computeRet) fn.computeRet = computeRet;
if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos);
return fn;
},
parseType: function(name, top) {
if (this.eat("fn(")) {
return this.parseFnType(name, top);
} else if (this.eat("[")) {
var inner = this.parseType();
if (inner == infer.ANull && this.spec == "[b.<i>]") {
var b = parsePath("b");
console.log(b.props["<i>"].types.length);
}
this.eat("]") || this.error();
if (top && this.base) {
infer.Arr.call(this.base, inner);
return this.base;
}
return new infer.Arr(inner);
} else if (this.eat("+")) {
var path = this.word(/[\w$<>\.!]/);
var base = parsePath(path + ".prototype");
if (!(base instanceof infer.Obj)) base = parsePath(path);
if (!(base instanceof infer.Obj)) return base;
if (top && this.forceNew) return new infer.Obj(base);
return infer.getInstance(base);
} else if (this.eat("?")) {
return infer.ANull;
} else {
return this.fromWord(this.word(/[\w$<>\.!`]/));
}
},
fromWord: function(spec) {
var cx = infer.cx();
switch (spec) {
case "number": return cx.num;
case "string": return cx.str;
case "bool": return cx.bool;
case "<top>": return cx.topScope;
}
if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec];
return parsePath(spec);
},
parseBaseRetType: function() {
if (this.eat("[")) {
var inner = this.parseRetType();
this.eat("]") || this.error();
return function(self, args) { return new infer.Arr(inner(self, args)); };
} else if (this.eat("+")) {
var base = this.parseRetType();
return function(self, args) { return infer.getInstance(base(self, args)); };
} else if (this.eat("!")) {
var arg = this.word(/\d/);
if (arg) {
arg = Number(arg);
return function(_self, args) {return args[arg] || infer.ANull;};
} else if (this.eat("this")) {
return function(self) {return self;};
} else if (this.eat("custom:")) {
var fname = this.word(/[\w$]/);
return customFunctions[fname] || function() { return infer.ANull; };
} else {
return this.fromWord("!" + arg + this.word(/[\w$<>\.!]/));
}
}
var t = this.parseType();
return function(){return t;};
},
extendRetType: function(base) {
var propName = this.word(/[\w<>$!]/) || this.error();
if (propName == "!ret") return function(self, args) {
var lhs = base(self, args);
if (lhs.retval) return lhs.retval;
var rv = new infer.AVal;
lhs.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
return rv;
};
return function(self, args) {return base(self, args).getProp(propName);};
},
parseRetType: function() {
var tp = this.parseBaseRetType();
while (this.eat(".")) tp = this.extendRetType(tp);
return tp;
}
};
function parseType(spec, name, base, forceNew) {
var type = new TypeParser(spec, null, base, forceNew).parseType(name, true);
if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) {
var arg = type.args[i];
if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) {
var fArg = fArgs[i];
if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull));
});
})(i);
return type;
}
function addEffect(fn, handler, replaceRet) {
var oldCmp = fn.computeRet, rv = fn.retval;
fn.computeRet = function(self, args, argNodes) {
var handled = handler(self, args, argNodes);
var old = oldCmp ? oldCmp(self, args, argNodes) : rv;
return replaceRet ? handled : old;
};
}
var parseEffect = exports.parseEffect = function(effect, fn) {
var m;
if (effect.indexOf("propagate ") == 0) {
var p = new TypeParser(effect, 10);
var getOrigin = p.parseRetType();
if (!p.eat(" ")) p.error();
var getTarget = p.parseRetType();
addEffect(fn, function(self, args) {
getOrigin(self, args).propagate(getTarget(self, args));
});
} else if (effect.indexOf("call ") == 0) {
var andRet = effect.indexOf("and return ", 5) == 5;
var p = new TypeParser(effect, andRet ? 16 : 5);
var getCallee = p.parseRetType(), getSelf = null, getArgs = [];
if (p.eat(" this=")) getSelf = p.parseRetType();
while (p.eat(" ")) getArgs.push(p.parseRetType());
addEffect(fn, function(self, args) {
var callee = getCallee(self, args);
var slf = getSelf ? getSelf(self, args) : infer.ANull, as = [];
for (var i = 0; i < getArgs.length; ++i) as.push(getArgs[i](self, args));
var result = andRet ? new infer.AVal : infer.ANull;
callee.propagate(new infer.IsCallee(slf, as, null, result));
return result;
}, andRet);
} else if (m = effect.match(/^custom (\S+)\s*(.*)/)) {
var customFunc = customFunctions[m[1]];
if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc);
} else if (effect.indexOf("copy ") == 0) {
var p = new TypeParser(effect, 5);
var getFrom = p.parseRetType();
p.eat(" ");
var getTo = p.parseRetType();
addEffect(fn, function(self, args) {
var from = getFrom(self, args), to = getTo(self, args);
from.forAllProps(function(prop, val, local) {
if (local && prop != "<i>")
to.propagate(new infer.PropHasSubset(prop, val));
});
});
} else {
throw new Error("Unknown effect type: " + effect);
}
};
var currentTopScope;
var parsePath = exports.parsePath = function(path) {
var cx = infer.cx(), cached = cx.paths[path], origPath = path;
if (cached != null) return cached;
cx.paths[path] = infer.ANull;
var base = currentTopScope || cx.topScope;
if (cx.localDefs) for (var name in cx.localDefs) {
if (path.indexOf(name) == 0) {
if (path == name) return cx.paths[path] = cx.localDefs[path];
if (path.charAt(name.length) == ".") {
base = cx.localDefs[name];
path = path.slice(name.length + 1);
break;
}
}
}
var parts = path.split(".");
for (var i = 0; i < parts.length && base != infer.ANull; ++i) {
var prop = parts[i];
if (prop.charAt(0) == "!") {
if (prop == "!proto") {
base = (base instanceof infer.Obj && base.proto) || infer.ANull;
} else {
var fn = base.getFunctionType();
if (!fn) {
base = infer.ANull;
} else if (prop == "!ret") {
base = fn.retval && fn.retval.getType(false) || infer.ANull;
} else {
var arg = fn.args && fn.args[Number(prop.slice(1))];
base = (arg && arg.getType(false)) || infer.ANull;
}
}
} else if (base instanceof infer.Obj) {
var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop];
if (!propVal || propVal.isEmpty())
base = infer.ANull;
else
base = propVal.types[0];
}
}
// Uncomment this to get feedback on your poorly written .json files
// if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")");
cx.paths[origPath] = base == infer.ANull ? null : base;
return base;
};
function emptyObj(ctor) {
var empty = Object.create(ctor.prototype);
empty.props = Object.create(null);
empty.isShell = true;
return empty;
}
function isSimpleAnnotation(spec) {
if (!spec["!type"] || /^(fn\(|\[)/.test(spec["!type"])) return false;
for (var prop in spec)
if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data")
return false;
return true;
}
function passOne(base, spec, path) {
if (!base) {
var tp = spec["!type"];
if (tp) {
if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn);
else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr);
else throw new Error("Invalid !type spec: " + tp);
} else if (spec["!stdProto"]) {
base = infer.cx().protos[spec["!stdProto"]];
} else {
base = emptyObj(infer.Obj);
}
base.name = path;
}
for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
var inner = spec[name];
if (typeof inner == "string" || isSimpleAnnotation(inner)) continue;
var prop = base.defProp(name);
passOne(prop.getType(false), inner, path ? path + "." + name : name).propagate(prop);
}
return base;
}
function passTwo(base, spec, path) {
if (base.isShell) {
delete base.isShell;
var tp = spec["!type"];
if (tp) {
parseType(tp, path, base);
} else {
var proto = spec["!proto"] && parseType(spec["!proto"]);
infer.Obj.call(base, proto instanceof infer.Obj ? proto : true, path);
}
}
var effects = spec["!effects"];
if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i)
parseEffect(effects[i], base);
copyInfo(spec, base);
for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name;
var type = known.getType(false);
if (typeof inner == "string") {
if (type) continue;
parseType(inner, innerPath).propagate(known);
} else {
if (!isSimpleAnnotation(inner)) {
passTwo(type, inner, innerPath);
} else if (!type) {
parseType(inner["!type"], innerPath, null, true).propagate(known);
type = known.getType(false);
if (type instanceof infer.Obj) copyInfo(inner, type);
} else continue;
if (inner["!doc"]) known.doc = inner["!doc"];
if (inner["!url"]) known.url = inner["!url"];
if (inner["!span"]) known.span = inner["!span"];
}
}
}
function copyInfo(spec, type) {
if (spec["!doc"]) type.doc = spec["!doc"];
if (spec["!url"]) type.url = spec["!url"];
if (spec["!span"]) type.span = spec["!span"];
if (spec["!data"]) type.metaData = spec["!data"];
}
function runPasses(type, arg) {
var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type];
if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg);
}
function doLoadEnvironment(data, scope) {
var cx = infer.cx();
infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
runPasses("preLoadDef", data);
passOne(scope, data);
var def = data["!define"];
if (def) {
for (var name in def) {
var spec = def[name];
cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name);
}
for (var name in def) {
var spec = def[name];
if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name);
}
}
passTwo(scope, data);
runPasses("postLoadDef", data);
cx.curOrigin = cx.localDefs = null;
}
exports.load = function(data, scope) {
if (!scope) scope = infer.cx().topScope;
var oldScope = currentTopScope;
currentTopScope = scope;
try {
doLoadEnvironment(data, scope);
} finally {
currentTopScope = oldScope;
}
};
// Used to register custom logic for more involved effect or type
// computation.
var customFunctions = Object.create(null);
infer.registerFunction = function(name, f) { customFunctions[name] = f; };
var IsCreated = infer.constraint("created, target, spec", {
addType: function(tp) {
if (tp instanceof infer.Obj && this.created++ < 5) {
var derived = new infer.Obj(tp), spec = this.spec;
if (spec instanceof infer.AVal) spec = spec.getType(false);
if (spec instanceof infer.Obj) for (var prop in spec.props) {
var cur = spec.props[prop].types[0];
var p = derived.defProp(prop);
if (cur && cur instanceof infer.Obj && cur.props.value) {
var vtp = cur.props.value.getType(false);
if (vtp) p.addType(vtp);
}
}
this.target.addType(derived);
}
}
});
infer.registerFunction("Object_create", function(_self, args, argNodes) {
if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null)
return new infer.Obj();
var result = new infer.AVal;
if (args[0]) args[0].propagate(new IsCreated(0, result, args[1]));
return result;
});
var IsBound = infer.constraint("self, args, target", {
addType: function(tp) {
if (!(tp instanceof infer.Fn)) return;
this.target.addType(new infer.Fn(tp.name, tp.self, tp.args.slice(this.args.length),
tp.argNames.slice(this.args.length), tp.retval));
this.self.propagate(tp.self);
for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
this.args[i].propagate(tp.args[i]);
}
});
infer.registerFunction("Function_bind", function(self, args) {
if (!args.length) return infer.ANull;
var result = new infer.AVal;
self.propagate(new IsBound(args[0], args.slice(1), result));
return result;
});
infer.registerFunction("Array_ctor", function(_self, args) {
var arr = new infer.Arr;
if (args.length != 1 || !args[0].hasType(infer.cx().num)) {
var content = arr.getProp("<i>");
for (var i = 0; i < args.length; ++i) args[i].propagate(content);
}
return arr;
});
return exports;
});
</script>
<script id="https://raw.githubusercontent.com/jcmoore/tern/88b721f7afd387f6823ab8478af5dbf5b8e1bf1b/lib/infer.js">
// Main type inference engine
// Walks an AST, building up a graph of abstract values and contraints
// that cause types to flow from one node to another. Also defines a
// number of utilities for accessing ASTs and scopes.
// Analysis is done in a context, which is tracked by the dynamically
// bound cx variable. Use withContext to set the current context.
// For memory-saving reasons, individual types export an interface
// similar to abstract values (which can hold multiple types), and can
// thus be used in place abstract values that only ever contain a
// single type.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/util/walk"),
require("./def"), require("./signal"));
if (typeof define == "function" && define.amd) // AMD
return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/util/walk", "./def", "./signal"], mod);
mod(self.tern || (self.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
})(function(exports, acorn, acorn_loose, walk, def, signal) {
"use strict";
var toString = exports.toString = function(type, maxDepth, parent) {
return !type || type == parent ? "?": type.toString(maxDepth);
};
// A variant of AVal used for unknown, dead-end values. Also serves
// as prototype for AVals, Types, and Constraints because it
// implements 'empty' versions of all the methods that the code
// expects.
var ANull = exports.ANull = signal.mixin({
addType: function() {},
propagate: function() {},
getProp: function() { return ANull; },
forAllProps: function() {},
hasType: function() { return false; },
isEmpty: function() { return true; },
getFunctionType: function() {},
getType: function() {},
gatherProperties: function() {},
propagatesTo: function() {},
typeHint: function() {},
propHint: function() {}
});
function extend(proto, props) {
var obj = Object.create(proto);
if (props) for (var prop in props) obj[prop] = props[prop];
return obj;
}
// ABSTRACT VALUES
var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, WG_MULTI_MEMBER = 5,
WG_CATCH_ERROR = 5, WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2;
var AVal = exports.AVal = function() {
this.types = [];
this.forward = null;
this.maxWeight = 0;
};
AVal.prototype = extend(ANull, {
addType: function(type, weight) {
weight = weight || WG_DEFAULT;
if (this.maxWeight < weight) {
this.maxWeight = weight;
if (this.types.length == 1 && this.types[0] == type) return;
this.types.length = 0;
} else if (this.maxWeight > weight || this.types.indexOf(type) > -1) {
return;
}
this.signal("addType", type);
this.types.push(type);
var forward = this.forward;
if (forward) withWorklist(function(add) {
for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight);
});
},
propagate: function(target, weight) {
if (target == ANull || (target instanceof Type)) return;
if (weight && weight < WG_DEFAULT) target = new Muffle(target, weight);
(this.forward || (this.forward = [])).push(target);
var types = this.types;
if (types.length) withWorklist(function(add) {
for (var i = 0; i < types.length; ++i) add(types[i], target, weight);
});
},
getProp: function(prop) {
if (prop == "__proto__" || prop == "✖") return ANull;
var found = (this.props || (this.props = Object.create(null)))[prop];
if (!found) {
found = this.props[prop] = new AVal;
this.propagate(new PropIsSubset(prop, found));
}
return found;
},
forAllProps: function(c) {
this.propagate(new ForAllProps(c));
},
hasType: function(type) {
return this.types.indexOf(type) > -1;
},
isEmpty: function() { return this.types.length == 0; },
getFunctionType: function() {
for (var i = this.types.length - 1; i >= 0; --i)
if (this.types[i] instanceof Fn) return this.types[i];
},
getType: function(guess) {
if (this.types.length == 0 && guess !== false) return this.makeupType();
if (this.types.length == 1) return this.types[0];
return canonicalType(this.types);
},
computedPropType: function() {
if (!this.propertyOf || !this.propertyOf.hasProp("<i>")) return null;
var computedProp = this.propertyOf.getProp("<i>");
if (computedProp == this) return null;
return computedProp.getType();
},
makeupType: function() {
var computed = this.computedPropType();
if (computed) return computed;
if (!this.forward) return null;
for (var i = this.forward.length - 1; i >= 0; --i) {
var hint = this.forward[i].typeHint();
if (hint && !hint.isEmpty()) {guessing = true; return hint;}
}
var props = Object.create(null), foundProp = null;
for (var i = 0; i < this.forward.length; ++i) {
var prop = this.forward[i].propHint();
if (prop && prop != "length" && prop != "<i>" && prop != "✖") {
props[prop] = true;
foundProp = prop;
}
}
if (!foundProp) return null;
var objs = objsWithProp(foundProp);
if (objs) {
var matches = [];
search: for (var i = 0; i < objs.length; ++i) {
var obj = objs[i];
for (var prop in props) if (!obj.hasProp(prop)) continue search;
if (obj.hasCtor) obj = getInstance(obj);
matches.push(obj);
}
var canon = canonicalType(matches);
if (canon) {guessing = true; return canon;}
}
},
typeHint: function() { return this.types.length ? this.getType() : null; },
propagatesTo: function() { return this; },
gatherProperties: function(f, depth) {
for (var i = 0; i < this.types.length; ++i)
this.types[i].gatherProperties(f, depth);
},
guessProperties: function(f) {
if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
var prop = this.forward[i].propHint();
if (prop) f(prop, null, 0);
}
var computed = this.computedPropType();
if (computed) computed.gatherProperties(f);
}
});
function canonicalType(types) {
var arrays = 0, fns = 0, objs = 0, prim = null;
for (var i = 0; i < types.length; ++i) {
var tp = types[i];
if (tp instanceof Arr) ++arrays;
else if (tp instanceof Fn) ++fns;
else if (tp instanceof Obj) ++objs;
else if (tp instanceof Prim) {
if (prim && tp.name != prim.name) return null;
prim = tp;
}
}
var kinds = (arrays && 1) + (fns && 1) + (objs && 1) + (prim && 1);
if (kinds > 1) return null;
if (prim) return prim;
var maxScore = 0, maxTp = null;
for (var i = 0; i < types.length; ++i) {
var tp = types[i], score = 0;
if (arrays) {
score = tp.getProp("<i>").isEmpty() ? 1 : 2;
} else if (fns) {
score = 1;
for (var j = 0; j < tp.args.length; ++j) if (!tp.args[j].isEmpty()) ++score;
if (!tp.retval.isEmpty()) ++score;
} else if (objs) {
score = tp.name ? 100 : 2;
}
if (score >= maxScore) { maxScore = score; maxTp = tp; }
}
return maxTp;
}
// PROPAGATION STRATEGIES
function Constraint() {}
Constraint.prototype = extend(ANull, {
init: function() { this.origin = cx.curOrigin; }
});
var constraint = exports.constraint = function(props, methods) {
var body = "this.init();";
props = props ? props.split(", ") : [];
for (var i = 0; i < props.length; ++i)
body += "this." + props[i] + " = " + props[i] + ";";
var ctor = Function.apply(null, props.concat([body]));
ctor.prototype = Object.create(Constraint.prototype);
for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
return ctor;
};
var PropIsSubset = constraint("prop, target", {
addType: function(type, weight) {
if (type.getProp)
type.getProp(this.prop).propagate(this.target, weight);
},
propHint: function() { return this.prop; },
propagatesTo: function() {
if (this.prop == "<i>" || !/[^\w_]/.test(this.prop))
return {target: this.target, pathExt: "." + this.prop};
}
});
var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", {
addType: function(type, weight) {
if (!(type instanceof Obj)) return;
var prop = type.defProp(this.prop, this.originNode);
prop.origin = this.origin;
this.type.propagate(prop, weight);
},
propHint: function() { return this.prop; }
});
var ForAllProps = constraint("c", {
addType: function(type) {
if (!(type instanceof Obj)) return;
type.forAllProps(this.c);
}
});
function withDisabledComputing(fn, body) {
cx.disabledComputing = {fn: fn, prev: cx.disabledComputing};
try {
return body();
} finally {
cx.disabledComputing = cx.disabledComputing.prev;
}
}
var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", {
init: function() {
Constraint.prototype.init();
this.disabled = cx.disabledComputing;
},
addType: function(fn, weight) {
if (!(fn instanceof Fn)) return;
for (var i = 0; i < this.args.length; ++i) {
if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight);
if (fn.arguments) this.args[i].propagate(fn.arguments, weight);
}
this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight);
var compute = fn.computeRet;
if (compute) for (var d = this.disabled; d; d = d.prev)
if (d.fn == fn || fn.name && d.fn.name == fn.name) compute = null;
if (compute)
compute(this.self, this.args, this.argNodes).propagate(this.retval, weight);
else
fn.retval.propagate(this.retval, weight);
},
typeHint: function() {
var names = [];
for (var i = 0; i < this.args.length; ++i) names.push("?");
return new Fn(null, this.self, this.args, names, ANull);
},
propagatesTo: function() {
return {target: this.retval, pathExt: ".!ret"};
}
});
var HasMethodCall = constraint("propName, args, argNodes, retval", {
init: function() {
Constraint.prototype.init();
this.disabled = cx.disabledComputing;
},
addType: function(obj, weight) {
var callee = new IsCallee(obj, this.args, this.argNodes, this.retval);
callee.disabled = this.disabled;
obj.getProp(this.propName).propagate(callee, weight);
},
propHint: function() { return this.propName; }
});
var IsCtor = exports.IsCtor = constraint("target, noReuse", {
addType: function(f, weight) {
if (!(f instanceof Fn)) return;
f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight);
}
});
var getInstance = exports.getInstance = function(obj, ctor) {
if (ctor === false) return new Obj(obj);
if (!ctor) ctor = obj.hasCtor;
if (!obj.instances) obj.instances = [];
for (var i = 0; i < obj.instances.length; ++i) {
var cur = obj.instances[i];
if (cur.ctor == ctor) return cur.instance;
}
var instance = new Obj(obj, ctor && ctor.name);
instance.origin = obj.origin;
obj.instances.push({ctor: ctor, instance: instance});
return instance;
};
var IsProto = exports.IsProto = constraint("ctor, target", {
addType: function(o, _weight) {
if (!(o instanceof Obj)) return;
if ((this.count = (this.count || 0) + 1) > 8) return;
if (o == cx.protos.Array)
this.target.addType(new Arr);
else
this.target.addType(getInstance(o, this.ctor));
}
});
var FnPrototype = constraint("fn", {
addType: function(o, _weight) {
if (o instanceof Obj && !o.hasCtor) {
o.hasCtor = this.fn;
var adder = new SpeculativeThis(o, this.fn);
adder.addType(this.fn);
o.forAllProps(function(_prop, val, local) {
if (local) val.propagate(adder);
});
}
}
});
var IsAdded = constraint("other, target", {
addType: function(type, weight) {
if (type == cx.str)
this.target.addType(cx.str, weight);
else if (type == cx.num && this.other.hasType(cx.num))
this.target.addType(cx.num, weight);
},
typeHint: function() { return this.other; }
});
var IfObj = exports.IfObj = constraint("target", {
addType: function(t, weight) {
if (t instanceof Obj) this.target.addType(t, weight);
},
propagatesTo: function() { return this.target; }
});
var SpeculativeThis = constraint("obj, ctor", {
addType: function(tp) {
if (tp instanceof Fn && tp.self && tp.self.isEmpty())
tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS);
}
});
var Muffle = constraint("inner, weight", {
addType: function(tp, weight) {
this.inner.addType(tp, Math.min(weight, this.weight));
},
propagatesTo: function() { return this.inner.propagatesTo(); },
typeHint: function() { return this.inner.typeHint(); },
propHint: function() { return this.inner.propHint(); }
});
// TYPE OBJECTS
var Type = exports.Type = function() {};
Type.prototype = extend(ANull, {
constructor: Type,
propagate: function(c, w) { c.addType(this, w); },
hasType: function(other) { return other == this; },
isEmpty: function() { return false; },
typeHint: function() { return this; },
getType: function() { return this; }
});
var Prim = exports.Prim = function(proto, name) { this.name = name; this.proto = proto; };
Prim.prototype = extend(Type.prototype, {
constructor: Prim,
toString: function() { return this.name; },
getProp: function(prop) {return this.proto.hasProp(prop) || ANull;},
gatherProperties: function(f, depth) {
if (this.proto) this.proto.gatherProperties(f, depth);
}
});
var Obj = exports.Obj = function(proto, name) {
if (!this.props) this.props = Object.create(null);
this.proto = proto === true ? cx.protos.Object : proto;
if (proto && !name && proto.name && !(this instanceof Fn)) {
var match = /^(.*)\.prototype$/.exec(this.proto.name);
if (match) name = match[1];
}
this.name = name;
this.maybeProps = null;
this.origin = cx.curOrigin;
};
Obj.prototype = extend(Type.prototype, {
constructor: Obj,
toString: function(maxDepth) {
if (!maxDepth && this.name) return this.name;
var props = [], etc = false;
for (var prop in this.props) if (prop != "<i>") {
if (props.length > 5) { etc = true; break; }
if (maxDepth)
props.push(prop + ": " + toString(this.props[prop].getType(), maxDepth - 1));
else
props.push(prop);
}
props.sort();
if (etc) props.push("...");
return "{" + props.join(", ") + "}";
},
hasProp: function(prop, searchProto) {
var found = this.props[prop];
if (searchProto !== false)
for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop];
return found;
},
defProp: function(prop, originNode) {
var found = this.hasProp(prop, false);
if (found) {
if (originNode && !found.originNode) found.originNode = originNode;
return found;
}
if (prop == "__proto__" || prop == "✖") return ANull;
var av = this.maybeProps && this.maybeProps[prop];
if (av) {
delete this.maybeProps[prop];
this.maybeUnregProtoPropHandler();
} else {
av = new AVal;
av.propertyOf = this;
}
this.props[prop] = av;
av.originNode = originNode;
av.origin = cx.curOrigin;
this.broadcastProp(prop, av, true);
return av;
},
getProp: function(prop) {
var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]);
if (found) return found;
if (prop == "__proto__" || prop == "✖") return ANull;
var av = this.ensureMaybeProps()[prop] = new AVal;
av.propertyOf = this;
return av;
},
broadcastProp: function(prop, val, local) {
if (local) {
this.signal("addProp", prop, val);
// If this is a scope, it shouldn't be registered
if (!(this instanceof Scope)) registerProp(prop, this);
}
if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) {
var h = this.onNewProp[i];
h.onProtoProp ? h.onProtoProp(prop, val, local) : h(prop, val, local);
}
},
onProtoProp: function(prop, val, _local) {
var maybe = this.maybeProps && this.maybeProps[prop];
if (maybe) {
delete this.maybeProps[prop];
this.maybeUnregProtoPropHandler();
this.proto.getProp(prop).propagate(maybe);
}
this.broadcastProp(prop, val, false);
},
ensureMaybeProps: function() {
if (!this.maybeProps) {
if (this.proto) this.proto.forAllProps(this);
this.maybeProps = Object.create(null);
}
return this.maybeProps;
},
removeProp: function(prop) {
var av = this.props[prop];
delete this.props[prop];
this.ensureMaybeProps()[prop] = av;
},
forAllProps: function(c) {
if (!this.onNewProp) {
this.onNewProp = [];
if (this.proto) this.proto.forAllProps(this);
}
this.onNewProp.push(c);
for (var o = this; o; o = o.proto) for (var prop in o.props) {
if (c.onProtoProp)
c.onProtoProp(prop, o.props[prop], o == this);
else
c(prop, o.props[prop], o == this);
}
},
maybeUnregProtoPropHandler: function() {
if (this.maybeProps) {
for (var _n in this.maybeProps) return;
this.maybeProps = null;
}
if (!this.proto || this.onNewProp && this.onNewProp.length) return;
this.proto.unregPropHandler(this);
},
unregPropHandler: function(handler) {
for (var i = 0; i < this.onNewProp.length; ++i)
if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; }
this.maybeUnregProtoPropHandler();
},
gatherProperties: function(f, depth) {
for (var prop in this.props) if (prop != "<i>")
f(prop, this, depth);
if (this.proto) this.proto.gatherProperties(f, depth + 1);
}
});
var Fn = exports.Fn = function(name, self, args, argNames, retval) {
Obj.call(this, cx.protos.Function, name);
this.self = self;
this.args = args;
this.argNames = argNames;
this.retval = retval;
};
Fn.prototype = extend(Obj.prototype, {
constructor: Fn,
toString: function(maxDepth) {
if (maxDepth) maxDepth--;
var str = "fn(";
for (var i = 0; i < this.args.length; ++i) {
if (i) str += ", ";
var name = this.argNames[i];
if (name && name != "?") str += name + ": ";
str += toString(this.args[i].getType(), maxDepth, this);
}
str += ")";
if (!this.retval.isEmpty())
str += " -> " + toString(this.retval.getType(), maxDepth, this);
return str;
},
getProp: function(prop) {
if (prop == "prototype") {
var known = this.hasProp(prop, false);
if (!known) {
known = this.defProp(prop);
var proto = new Obj(true, this.name && this.name + ".prototype");
proto.origin = this.origin;
known.addType(proto, WG_MADEUP_PROTO);
}
return known;
}
return Obj.prototype.getProp.call(this, prop);
},
defProp: function(prop, originNode) {
if (prop == "prototype") {
var found = this.hasProp(prop, false);
if (found) return found;
found = Obj.prototype.defProp.call(this, prop, originNode);
found.origin = this.origin;
found.propagate(new FnPrototype(this));
return found;
}
return Obj.prototype.defProp.call(this, prop, originNode);
},
getFunctionType: function() { return this; }
});
var Arr = exports.Arr = function(contentType) {
Obj.call(this, cx.protos.Array);
var content = this.defProp("<i>");
if (contentType) contentType.propagate(content);
};
Arr.prototype = extend(Obj.prototype, {
constructor: Arr,
toString: function(maxDepth) {
return "[" + toString(this.getProp("<i>").getType(), maxDepth, this) + "]";
}
});
// THE PROPERTY REGISTRY
function registerProp(prop, obj) {
var data = cx.props[prop] || (cx.props[prop] = []);
data.push(obj);
}
function objsWithProp(prop) {
return cx.props[prop];
}
// INFERENCE CONTEXT
exports.Context = function(defs, parent) {
this.parent = parent;
this.props = Object.create(null);
this.protos = Object.create(null);
this.origins = [];
this.curOrigin = "ecma5";
this.paths = Object.create(null);
this.definitions = Object.create(null);
this.purgeGen = 0;
this.workList = null;
this.disabledComputing = null;
exports.withContext(this, function() {
cx.protos.Object = new Obj(null, "Object.prototype");
cx.topScope = new Scope();
cx.topScope.name = "<top>";
cx.protos.Array = new Obj(true, "Array.prototype");
cx.protos.Function = new Obj(true, "Function.prototype");
cx.protos.RegExp = new Obj(true, "RegExp.prototype");
cx.protos.String = new Obj(true, "String.prototype");
cx.protos.Number = new Obj(true, "Number.prototype");
cx.protos.Boolean = new Obj(true, "Boolean.prototype");
cx.str = new Prim(cx.protos.String, "string");
cx.bool = new Prim(cx.protos.Boolean, "bool");
cx.num = new Prim(cx.protos.Number, "number");
cx.curOrigin = null;
if (defs) for (var i = 0; i < defs.length; ++i)
def.load(defs[i]);
});
};
var cx = null;
exports.cx = function() { return cx; };
exports.withContext = function(context, f) {
var old = cx;
cx = context;
try { return f(); }
finally { cx = old; }
};
exports.addOrigin = function(origin) {
if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
};
var baseMaxWorkDepth = 20, reduceMaxWorkDepth = .0001;
function withWorklist(f) {
if (cx.workList) return f(cx.workList);
var list = [], depth = 0;
var add = cx.workList = function(type, target, weight) {
if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length)
list.push(type, target, weight, depth);
};
try {
var ret = f(add);
for (var i = 0; i < list.length; i += 4) {
depth = list[i + 3] + 1;
list[i + 1].addType(list[i], list[i + 2]);
}
return ret;
} finally {
cx.workList = null;
}
}
// SCOPES
var Scope = exports.Scope = function(prev) {
Obj.call(this, prev || true);
this.prev = prev;
};
Scope.prototype = extend(Obj.prototype, {
constructor: Scope,
defVar: function(name, originNode) {
for (var s = this; ; s = s.proto) {
var found = s.props[name];
if (found) return found;
if (!s.prev) return s.defProp(name, originNode);
}
}
});
// RETVAL COMPUTATION HEURISTICS
function maybeInstantiate(scope, score) {
if (scope.fnType)
scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score;
}
var NotSmaller = {};
function nodeSmallerThan(node, n) {
try {
walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }});
return true;
} catch(e) {
if (e == NotSmaller) return false;
throw e;
}
}
function maybeTagAsInstantiated(node, scope) {
var score = scope.fnType.instantiateScore;
if (!cx.disabledComputing && score && scope.fnType.args.length && nodeSmallerThan(node, score * 5)) {
maybeInstantiate(scope.prev, score / 2);
setFunctionInstantiated(node, scope);
return true;
} else {
scope.fnType.instantiateScore = null;
}
}
function setFunctionInstantiated(node, scope) {
var fn = scope.fnType;
// Disconnect the arg avals, so that we can add info to them without side effects
for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal;
fn.self = new AVal;
fn.computeRet = function(self, args) {
// Prevent recursion
return withDisabledComputing(fn, function() {
var oldOrigin = cx.curOrigin;
cx.curOrigin = fn.origin;
var scopeCopy = new Scope(scope.prev);
scopeCopy.copiedScope = scope;
for (var v in scope.props) {
var local = scopeCopy.defProp(v);
for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length)
args[i].propagate(local);
}
var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
while (argNames.length < args.length) argNames.push("?");
scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull);
if (fn.arguments) {
var argset = scopeCopy.fnType.arguments = new AVal;
scopeCopy.defProp("arguments").addType(new Arr(argset));
for (var i = 0; i < args.length; ++i) args[i].propagate(argset);
}
node.body.scope = scopeCopy;
walk.recursive(node.body, scopeCopy, null, scopeGatherer);
walk.recursive(node.body, scopeCopy, null, inferWrapper);
cx.curOrigin = oldOrigin;
return scopeCopy.fnType.retval;
});
};
}
function maybeTagAsGeneric(scope) {
var fn = scope.fnType, target = fn.retval;
if (target == ANull) return;
var targetInner, asArray;
if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr)
target = asArray = targetInner.getProp("<i>");
function explore(aval, path, depth) {
if (depth > 3 || !aval.forward) return;
for (var i = 0; i < aval.forward.length; ++i) {
var prop = aval.forward[i].propagatesTo();
if (!prop) continue;
var newPath = path, dest;
if (prop instanceof AVal) {
dest = prop;
} else if (prop.target instanceof AVal) {
newPath += prop.pathExt;
dest = prop.target;
} else continue;
if (dest == target) return newPath;
var found = explore(dest, newPath, depth + 1);
if (found) return found;
}
}
var foundPath = explore(fn.self, "!this", 0);
for (var i = 0; !foundPath && i < fn.args.length; ++i)
foundPath = explore(fn.args[i], "!" + i, 0);
if (foundPath) {
if (asArray) foundPath = "[" + foundPath + "]";
var p = new def.TypeParser(foundPath);
fn.computeRet = p.parseRetType();
fn.computeRetSource = foundPath;
return true;
}
}
// SCOPE GATHERING PASS
function addVar(scope, nameNode) {
var val = scope.defProp(nameNode.name, nameNode);
if (val.maybePurge) val.maybePurge = false;
return val;
}
var scopeGatherer = walk.make({
Function: function(node, scope, c) {
var inner = node.body.scope = new Scope(scope);
inner.node = node;
var argVals = [], argNames = [];
for (var i = 0; i < node.params.length; ++i) {
var param = node.params[i];
argNames.push(param.name);
argVals.push(addVar(inner, param));
}
inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull);
inner.fnType.originNode = node;
if (node.id) {
var decl = node.type == "FunctionDeclaration";
addVar(decl ? scope : inner, node.id);
}
c(node.body, inner, "ScopeBody");
},
TryStatement: function(node, scope, c) {
c(node.block, scope, "Statement");
if (node.handler) {
var v = addVar(scope, node.handler.param);
c(node.handler.body, scope, "ScopeBody");
var e5 = cx.definitions.ecma5;
if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
}
if (node.finalizer) c(node.finalizer, scope, "Statement");
},
VariableDeclaration: function(node, scope, c) {
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
addVar(scope, decl.id);
if (decl.init) c(decl.init, scope, "Expression");
}
}
});
// CONSTRAINT GATHERING PASS
function propName(node, scope, c) {
var prop = node.property;
if (!node.computed) return prop.name;
if (prop.type == "Literal" && typeof prop.value == "string") return prop.value;
if (c) infer(prop, scope, c, ANull);
return "<i>";
}
function unopResultType(op) {
switch (op) {
case "+": case "-": case "~": return cx.num;
case "!": return cx.bool;
case "typeof": return cx.str;
case "void": case "delete": return ANull;
}
}
function binopIsBoolean(op) {
switch (op) {
case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=":
case "in": case "instanceof": return true;
}
}
function literalType(val) {
switch (typeof val) {
case "boolean": return cx.bool;
case "number": return cx.num;
case "string": return cx.str;
case "object":
case "function":
if (!val) return ANull;
return getInstance(cx.protos.RegExp);
}
}
function ret(f) {
return function(node, scope, c, out, name) {
var r = f(node, scope, c, name);
if (out) r.propagate(out);
return r;
};
}
function fill(f) {
return function(node, scope, c, out, name) {
if (!out) out = new AVal;
f(node, scope, c, out, name);
return out;
};
}
var inferExprVisitor = {
ArrayExpression: ret(function(node, scope, c) {
var eltval = new AVal;
for (var i = 0; i < node.elements.length; ++i) {
var elt = node.elements[i];
if (elt) infer(elt, scope, c, eltval);
}
return new Arr(eltval);
}),
ObjectExpression: ret(function(node, scope, c, name) {
var obj = node.objType = new Obj(true, name);
obj.originNode = node;
for (var i = 0; i < node.properties.length; ++i) {
var prop = node.properties[i], key = prop.key, name;
if (key.type == "Identifier") {
name = key.name;
} else if (typeof key.value == "string") {
name = key.value;
} else {
infer(prop.value, scope, c, ANull);
continue;
}
var val = obj.defProp(name, key);
val.initializer = true;
infer(prop.value, scope, c, val, name);
}
return obj;
}),
FunctionExpression: ret(function(node, scope, c, name) {
var inner = node.body.scope, fn = inner.fnType;
if (name && !fn.name) fn.name = name;
c(node.body, scope, "ScopeBody");
maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
if (node.id) inner.getProp(node.id.name).addType(fn);
return fn;
}),
SequenceExpression: ret(function(node, scope, c) {
for (var i = 0, l = node.expressions.length - 1; i < l; ++i)
infer(node.expressions[i], scope, c, ANull);
return infer(node.expressions[l], scope, c);
}),
UnaryExpression: ret(function(node, scope, c) {
infer(node.argument, scope, c, ANull);
return unopResultType(node.operator);
}),
UpdateExpression: ret(function(node, scope, c) {
infer(node.argument, scope, c, ANull);
return cx.num;
}),
BinaryExpression: ret(function(node, scope, c) {
if (node.operator == "+") {
var lhs = infer(node.left, scope, c);
var rhs = infer(node.right, scope, c);
if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num;
var result = new AVal;
lhs.propagate(new IsAdded(rhs, result));
rhs.propagate(new IsAdded(lhs, result));
return result;
} else {
infer(node.left, scope, c, ANull);
infer(node.right, scope, c, ANull);
return binopIsBoolean(node.operator) ? cx.bool : cx.num;
}
}),
AssignmentExpression: ret(function(node, scope, c) {
var rhs, name, pName;
if (node.left.type == "MemberExpression") {
pName = propName(node.left, scope, c);
if (node.left.object.type == "Identifier")
name = node.left.object.name + "." + pName;
} else {
name = node.left.name;
}
if (node.operator != "=" && node.operator != "+=") {
infer(node.right, scope, c, ANull);
rhs = cx.num;
} else {
rhs = infer(node.right, scope, c, null, name);
}
if (node.left.type == "MemberExpression") {
var obj = infer(node.left.object, scope, c);
if (pName == "prototype") maybeInstantiate(scope, 20);
if (pName == "<i>") {
// This is a hack to recognize for/in loops that copy
// properties, and do the copying ourselves, insofar as we
// manage, because such loops tend to be relevant for type
// information.
var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver;
if (over) {
maybeInstantiate(scope, 20);
var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v;
over.forAllProps(function(prop, val, local) {
if (local && prop != "prototype" && prop != "<i>")
obj.propagate(new PropHasSubset(prop, fromRight ? val : ANull));
});
return rhs;
}
}
obj.propagate(new PropHasSubset(pName, rhs, node.left.property));
} else { // Identifier
var v = scope.defVar(node.left.name, node.left);
if (v.maybePurge) v.maybePurge = false;
rhs.propagate(v);
}
return rhs;
}),
LogicalExpression: fill(function(node, scope, c, out) {
infer(node.left, scope, c, out);
infer(node.right, scope, c, out);
}),
ConditionalExpression: fill(function(node, scope, c, out) {
infer(node.test, scope, c, ANull);
infer(node.consequent, scope, c, out);
infer(node.alternate, scope, c, out);
}),
NewExpression: fill(function(node, scope, c, out, name) {
if (node.callee.type == "Identifier" && node.callee.name in scope.props)
maybeInstantiate(scope, 20);
for (var i = 0, args = []; i < node.arguments.length; ++i)
args.push(infer(node.arguments[i], scope, c));
var callee = infer(node.callee, scope, c);
var self = new AVal;
callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name)));
self.propagate(out, WG_NEW_INSTANCE);
callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out)));
}),
CallExpression: fill(function(node, scope, c, out) {
for (var i = 0, args = []; i < node.arguments.length; ++i)
args.push(infer(node.arguments[i], scope, c));
if (node.callee.type == "MemberExpression") {
var self = infer(node.callee.object, scope, c);
var pName = propName(node.callee, scope, c);
if ((pName == "call" || pName == "apply") &&
scope.fnType && scope.fnType.args.indexOf(self) > -1)
maybeInstantiate(scope, 30);
self.propagate(new HasMethodCall(pName, args, node.arguments, out));
} else {
var callee = infer(node.callee, scope, c);
if (scope.fnType && scope.fnType.args.indexOf(callee) > -1)
maybeInstantiate(scope, 30);
var knownFn = callee.getFunctionType();
if (knownFn && knownFn.instantiateScore && scope.fnType)
maybeInstantiate(scope, knownFn.instantiateScore / 5);
callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out));
}
}),
MemberExpression: fill(function(node, scope, c, out) {
var name = propName(node, scope);
var obj = infer(node.object, scope, c);
var prop = obj.getProp(name);
if (name == "<i>") {
var propType = infer(node.property, scope, c);
if (!propType.hasType(cx.num))
return prop.propagate(out, WG_MULTI_MEMBER);
}
prop.propagate(out);
}),
Identifier: ret(function(node, scope) {
if (node.name == "arguments" && scope.fnType && !(node.name in scope.props))
scope.defProp(node.name, scope.fnType.originNode)
.addType(new Arr(scope.fnType.arguments = new AVal));
return scope.getProp(node.name);
}),
ThisExpression: ret(function(_node, scope) {
return scope.fnType ? scope.fnType.self : cx.topScope;
}),
Literal: ret(function(node) {
return literalType(node.value);
})
};
function infer(node, scope, c, out, name) {
return inferExprVisitor[node.type](node, scope, c, out, name);
}
var inferWrapper = walk.make({
Expression: function(node, scope, c) {
infer(node, scope, c, ANull);
},
FunctionDeclaration: function(node, scope, c) {
var inner = node.body.scope, fn = inner.fnType;
c(node.body, scope, "ScopeBody");
maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
var prop = scope.getProp(node.id.name);
prop.addType(fn);
},
VariableDeclaration: function(node, scope, c) {
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i], prop = scope.getProp(decl.id.name);
if (decl.init)
infer(decl.init, scope, c, prop, decl.id.name);
}
},
ReturnStatement: function(node, scope, c) {
if (!node.argument) return;
var output = ANull;
if (scope.fnType) {
if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal;
output = scope.fnType.retval;
}
infer(node.argument, scope, c, output);
},
ForInStatement: function(node, scope, c) {
var source = infer(node.right, scope, c);
if ((node.right.type == "Identifier" && node.right.name in scope.props) ||
(node.right.type == "MemberExpression" && node.right.property.name == "prototype")) {
maybeInstantiate(scope, 5);
var varName;
if (node.left.type == "Identifier") {
varName = node.left.name;
} else if (node.left.type == "VariableDeclaration") {
varName = node.left.declarations[0].id.name;
}
if (varName && varName in scope.props)
scope.getProp(varName).iteratesOver = source;
}
c(node.body, scope, "Statement");
},
ScopeBody: function(node, scope, c) { c(node, node.scope || scope); }
});
// PARSING
function runPasses(passes, pass) {
var arr = passes && passes[pass];
var args = Array.prototype.slice.call(arguments, 2);
if (arr) for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
}
var parse = exports.parse = function(text, passes, options) {
var ast;
try { ast = acorn.parse(text, options); }
catch(e) { ast = acorn_loose.parse_dammit(text, options); }
runPasses(passes, "postParse", ast, text);
return ast;
};
// ANALYSIS INTERFACE
exports.analyze = function(ast, name, scope, passes) {
if (typeof ast == "string") ast = parse(ast);
if (!name) name = "file#" + cx.origins.length;
exports.addOrigin(cx.curOrigin = name);
if (!scope) scope = cx.topScope;
walk.recursive(ast, scope, null, scopeGatherer);
runPasses(passes, "preInfer", ast, scope);
walk.recursive(ast, scope, null, inferWrapper);
runPasses(passes, "postInfer", ast, scope);
cx.curOrigin = null;
};
// PURGING
exports.purgeTypes = function(origins, start, end) {
var test = makePredicate(origins, start, end);
++cx.purgeGen;
cx.topScope.purge(test);
for (var prop in cx.props) {
var list = cx.props[prop];
for (var i = 0; i < list.length; ++i) {
var obj = list[i], av = obj.props[prop];
if (!av || test(av, av.originNode)) list.splice(i--, 1);
}
if (!list.length) delete cx.props[prop];
}
};
function makePredicate(origins, start, end) {
var arr = Array.isArray(origins);
if (arr && origins.length == 1) { origins = origins[0]; arr = false; }
if (arr) {
if (end == null) return function(n) { return origins.indexOf(n.origin) > -1; };
return function(n, pos) { return pos && pos.start >= start && pos.end <= end && origins.indexOf(n.origin) > -1; };
} else {
if (end == null) return function(n) { return n.origin == origins; };
return function(n, pos) { return pos && pos.start >= start && pos.end <= end && n.origin == origins; };
}
}
AVal.prototype.purge = function(test) {
if (this.purgeGen == cx.purgeGen) return;
this.purgeGen = cx.purgeGen;
for (var i = 0; i < this.types.length; ++i) {
var type = this.types[i];
if (test(type, type.originNode))
this.types.splice(i--, 1);
else
type.purge(test);
}
if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
var f = this.forward[i];
if (test(f)) {
this.forward.splice(i--, 1);
if (this.props) this.props = null;
} else if (f.purge) {
f.purge(test);
}
}
};
ANull.purge = function() {};
Obj.prototype.purge = function(test) {
if (this.purgeGen == cx.purgeGen) return true;
this.purgeGen = cx.purgeGen;
for (var p in this.props) {
var av = this.props[p];
if (test(av, av.originNode))
this.removeProp(p);
av.purge(test);
}
};
Fn.prototype.purge = function(test) {
if (Obj.prototype.purge.call(this, test)) return;
this.self.purge(test);
this.retval.purge(test);
for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test);
};
exports.markVariablesDefinedBy = function(scope, origins, start, end) {
var test = makePredicate(origins, start, end);
for (var s = scope; s; s = s.prev) for (var p in s.props) {
var prop = s.props[p];
if (test(prop, prop.originNode)) {
prop.maybePurge = true;
if (start == null && prop.originNode) prop.originNode = null;
}
}
};
exports.purgeMarkedVariables = function(scope) {
for (var s = scope; s; s = s.prev) for (var p in s.props)
if (s.props[p].maybePurge) delete s.props[p];
};
// EXPRESSION TYPE DETERMINATION
function findByPropertyName(name) {
guessing = true;
var found = objsWithProp(name);
if (found) for (var i = 0; i < found.length; ++i) {
var val = found[i].getProp(name);
if (!val.isEmpty()) return val;
}
return ANull;
}
var typeFinder = {
ArrayExpression: function(node, scope) {
var eltval = new AVal;
for (var i = 0; i < node.elements.length; ++i) {
var elt = node.elements[i];
if (elt) findType(elt, scope).propagate(eltval);
}
return new Arr(eltval);
},
ObjectExpression: function(node) {
return node.objType;
},
FunctionExpression: function(node) {
return node.body.scope.fnType;
},
SequenceExpression: function(node, scope) {
return findType(node.expressions[node.expressions.length-1], scope);
},
UnaryExpression: function(node) {
return unopResultType(node.operator);
},
UpdateExpression: function() {
return cx.num;
},
BinaryExpression: function(node, scope) {
if (binopIsBoolean(node.operator)) return cx.bool;
if (node.operator == "+") {
var lhs = findType(node.left, scope);
var rhs = findType(node.right, scope);
if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
}
return cx.num;
},
AssignmentExpression: function(node, scope) {
return findType(node.right, scope);
},
LogicalExpression: function(node, scope) {
var lhs = findType(node.left, scope);
return lhs.isEmpty() ? findType(node.right, scope) : lhs;
},
ConditionalExpression: function(node, scope) {
var lhs = findType(node.consequent, scope);
return lhs.isEmpty() ? findType(node.alternate, scope) : lhs;
},
NewExpression: function(node, scope) {
var f = findType(node.callee, scope).getFunctionType();
var proto = f && f.getProp("prototype").getType();
if (!proto) return ANull;
return getInstance(proto, f);
},
CallExpression: function(node, scope) {
var f = findType(node.callee, scope).getFunctionType();
if (!f) return ANull;
if (f.computeRet) {
for (var i = 0, args = []; i < node.arguments.length; ++i)
args.push(findType(node.arguments[i], scope));
var self = ANull;
if (node.callee.type == "MemberExpression")
self = findType(node.callee.object, scope);
return f.computeRet(self, args, node.arguments);
} else {
return f.retval;
}
},
MemberExpression: function(node, scope) {
var propN = propName(node, scope), obj = findType(node.object, scope).getType();
if (obj) return obj.getProp(propN);
if (propN == "<i>") return ANull;
return findByPropertyName(propN);
},
Identifier: function(node, scope) {
return scope.hasProp(node.name) || ANull;
},
ThisExpression: function(_node, scope) {
return scope.fnType ? scope.fnType.self : cx.topScope;
},
Literal: function(node) {
return literalType(node.value);
}
};
function findType(node, scope) {
var found = typeFinder[node.type](node, scope);
return found;
}
var searchVisitor = exports.searchVisitor = walk.make({
Function: function(node, _st, c) {
var scope = node.body.scope;
if (node.id) c(node.id, scope);
for (var i = 0; i < node.params.length; ++i)
c(node.params[i], scope);
c(node.body, scope, "ScopeBody");
},
TryStatement: function(node, st, c) {
if (node.handler)
c(node.handler.param, st);
walk.base.TryStatement(node, st, c);
},
VariableDeclaration: function(node, st, c) {
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
c(decl.id, st);
if (decl.init) c(decl.init, st, "Expression");
}
}
});
exports.fullVisitor = walk.make({
MemberExpression: function(node, st, c) {
c(node.object, st, "Expression");
c(node.property, st, node.computed ? "Expression" : null);
},
ObjectExpression: function(node, st, c) {
for (var i = 0; i < node.properties.length; ++i) {
c(node.properties[i].value, st, "Expression");
c(node.properties[i].key, st);
}
}
}, searchVisitor);
exports.findExpressionAt = function(ast, start, end, defaultScope, filter) {
var test = filter || function(_t, node) {return typeFinder.hasOwnProperty(node.type);};
return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope);
};
exports.findExpressionAround = function(ast, start, end, defaultScope, filter) {
var test = filter || function(_t, node) {
if (start != null && node.start > start) return false;
return typeFinder.hasOwnProperty(node.type);
};
return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope);
};
exports.expressionType = function(found) {
return findType(found.node, found.state);
};
// Flag used to indicate that some wild guessing was used to produce
// a type or set of completions.
var guessing = false;
exports.resetGuessing = function(val) { guessing = val; };
exports.didGuess = function() { return guessing; };
exports.forAllPropertiesOf = function(type, f) {
type.gatherProperties(f, 0);
};
var refFindWalker = walk.make({}, searchVisitor);
exports.findRefs = function(ast, baseScope, name, refScope, f) {
refFindWalker.Identifier = function(node, scope) {
if (node.name != name) return;
for (var s = scope; s; s = s.prev) {
if (s == refScope) f(node, scope);
if (name in s.props) return;
}
};
walk.recursive(ast, baseScope, null, refFindWalker);
};
var simpleWalker = walk.make({
Function: function(node, _st, c) { c(node.body, node.body.scope, "ScopeBody"); }
});
exports.findPropRefs = function(ast, scope, objType, propName, f) {
walk.simple(ast, {
MemberExpression: function(node, scope) {
if (node.computed || node.property.name != propName) return;
if (findType(node.object, scope).getType() == objType) f(node.property);
},
ObjectExpression: function(node, scope) {
if (findType(node, scope).getType() != objType) return;
for (var i = 0; i < node.properties.length; ++i)
if (node.properties[i].key.name == propName) f(node.properties[i].key);
}
}, simpleWalker, scope);
};
// LOCAL-VARIABLE QUERIES
var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) {
var found = walk.findNodeAround(ast, pos, function(tp, node) {
return tp == "ScopeBody" && node.scope;
});
if (found) return found.node.scope;
else return defaultScope || cx.topScope;
};
exports.forAllLocalsAt = function(ast, pos, defaultScope, f) {
var scope = scopeAt(ast, pos, defaultScope);
scope.gatherProperties(f, 0);
};
// INIT DEF MODULE
// Delayed initialization because of cyclic dependencies.
def = exports.def = def.init({}, exports);
});
</script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment