Created
April 6, 2014 21:38
-
-
Save jcmoore/10011743 to your computer and use it in GitHub Desktop.
extra lookup for tern.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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: { | |
'&': '&', | |
'<': '<', | |
'>': '>', | |
'"': '"', | |
"'": ''' | |
} | |
}; | |
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