Add a plugin mechanism to allow JSX parser to be a module
And export a few more things
This commit is contained in:
parent
06f3b3c224
commit
3365478645
218
acorn.js
218
acorn.js
@ -38,7 +38,7 @@
|
||||
exports.parse = function(input, options) {
|
||||
var p = new Parser(options, input);
|
||||
var startPos = p.options.locations ? [p.pos, p.curPosition()] : p.pos;
|
||||
p.readToken();
|
||||
p.nextToken();
|
||||
return p.parseTopLevel(p.options.program || p.startNodeAt(startPos));
|
||||
};
|
||||
|
||||
@ -116,16 +116,19 @@
|
||||
directSourceFile: null,
|
||||
// When enabled, parenthesized expressions are represented by
|
||||
// (non-standard) ParenthesizedExpression nodes
|
||||
preserveParens: false
|
||||
preserveParens: false,
|
||||
plugins: {}
|
||||
};
|
||||
|
||||
exports.plugins = {};
|
||||
|
||||
// This function tries to parse a single expression at a given
|
||||
// offset in a string. Useful for parsing mixed-language formats
|
||||
// that embed JavaScript expressions.
|
||||
|
||||
exports.parseExpressionAt = function(input, pos, options) {
|
||||
var p = new Parser(options, input, pos);
|
||||
p.readToken();
|
||||
p.nextToken();
|
||||
return p.parseExpression();
|
||||
};
|
||||
|
||||
@ -175,7 +178,7 @@
|
||||
|
||||
function getToken() {
|
||||
p.lastTokEnd = p.end;
|
||||
p.readToken();
|
||||
p.nextToken();
|
||||
return new Token(p);
|
||||
}
|
||||
|
||||
@ -272,7 +275,7 @@
|
||||
// to know when parsing a label, in order to allow or disallow
|
||||
// continue jumps to that label.
|
||||
|
||||
function TokenType(label, beforeExpr, binop, kind, keyword) {
|
||||
var TokenType = exports.TokenType = function(label, beforeExpr, binop, kind, keyword) {
|
||||
this.label = label;
|
||||
this.keyword = keyword;
|
||||
this.beforeExpr = !!beforeExpr;
|
||||
@ -282,8 +285,7 @@
|
||||
this.postfix = kind && kind.indexOf("postfix") > -1;
|
||||
this.binop = binop || null;
|
||||
this.updateContext = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function binop(name, prec) {
|
||||
return new TokenType(name, true, prec);
|
||||
@ -393,52 +395,6 @@
|
||||
kw("void", true, "prefix");
|
||||
kw("delete", true, "prefix");
|
||||
|
||||
// Update tokenizer context for a given token type
|
||||
|
||||
tt.parenR.updateContext = tt.braceR.updateContext = function() {
|
||||
var out = this.context.pop();
|
||||
if (out === b_stat && this.curTokContext() === f_expr) {
|
||||
this.context.pop();
|
||||
this.exprAllowed = false;
|
||||
} else if (out !== b_tmpl) {
|
||||
this.exprAllowed = !(out && out.isExpr);
|
||||
}
|
||||
};
|
||||
|
||||
tt.braceL.updateContext = function(prevType) {
|
||||
this.context.push(this.braceIsBlock(prevType) ? b_stat : b_expr);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.dollarBraceL.updateContext = function() {
|
||||
this.context.push(b_tmpl);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.parenL.updateContext = function(prevType) {
|
||||
var statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while;
|
||||
this.context.push(statementParens ? p_stat : p_expr);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.incDec.updateContext = function() {
|
||||
// tokExprAllowed stays unchanged
|
||||
};
|
||||
|
||||
tt._function.updateContext = function() {
|
||||
if (this.curTokContext() !== b_stat)
|
||||
this.context.push(f_expr);
|
||||
this.exprAllowed = false;
|
||||
};
|
||||
|
||||
tt.backQuote.updateContext = function() {
|
||||
if (this.curTokContext() === q_tmpl)
|
||||
this.context.pop();
|
||||
else
|
||||
this.context.push(q_tmpl);
|
||||
this.exprAllowed = false;
|
||||
};
|
||||
|
||||
// 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
|
||||
@ -552,9 +508,9 @@
|
||||
|
||||
var newline = /[\n\r\u2028\u2029]/;
|
||||
|
||||
function isNewLine(code) {
|
||||
var isNewLine = exports.isNewLine = function(code) {
|
||||
return code === 10 || code === 13 || code === 0x2028 || code == 0x2029;
|
||||
}
|
||||
};
|
||||
|
||||
// Matches a whole line break (where CRLF is considered a single
|
||||
// line break). Used to count lines.
|
||||
@ -621,8 +577,9 @@
|
||||
//
|
||||
// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
|
||||
|
||||
function Parser(options, input, startPos) {
|
||||
var Parser = exports.Parser = function(options, input, startPos) {
|
||||
this.options = parseOptions(options);
|
||||
this.loadPlugins(this.options.plugins);
|
||||
this.sourceFile = this.options.sourceFile || null;
|
||||
this.isKeyword = this.options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword;
|
||||
this.input = String(input);
|
||||
@ -657,7 +614,7 @@
|
||||
// The context stack is used to superficially track syntactic
|
||||
// context to predict whether a regular expression is allowed in a
|
||||
// given position.
|
||||
this.context = [b_stat];
|
||||
this.context = [tc.b_stat];
|
||||
this.exprAllowed = true;
|
||||
|
||||
// Flags to track whether we are in strict mode, a function, a
|
||||
@ -669,12 +626,35 @@
|
||||
// If enabled, skip leading hashbang line.
|
||||
if (this.pos === 0 && this.options.allowHashBang && this.input.slice(0, 2) === '#!')
|
||||
this.skipLineComment(2);
|
||||
}
|
||||
};
|
||||
|
||||
// Shorthand because we are going to be adding a _lot_ of methods to
|
||||
// this.
|
||||
var pp = Parser.prototype;
|
||||
|
||||
pp.extend = function(name, f) {
|
||||
this[name] = f(this[name]);
|
||||
};
|
||||
|
||||
pp.loadPlugins = function(plugins) {
|
||||
for (var name in plugins) {
|
||||
var plugin = exports.plugins[name];
|
||||
if (!plugin) throw new Error("Plugin '" + name + "' not found");
|
||||
plugin(this, plugins[name]);
|
||||
}
|
||||
};
|
||||
|
||||
function combineHooks(hooks) {
|
||||
if (!hooks.length) return null;
|
||||
return function(arg) {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
var result = hooks[i].call(this, arg);
|
||||
if (result !== false) return result;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// Move to the next token
|
||||
|
||||
pp.next = function() {
|
||||
@ -684,7 +664,7 @@
|
||||
this.lastTokEnd = this.end;
|
||||
this.lastTokStart = this.start;
|
||||
this.lastTokEndLoc = this.endLoc;
|
||||
this.readToken();
|
||||
this.nextToken();
|
||||
};
|
||||
|
||||
// Toggle strict mode. Re-reads the next number or string to please
|
||||
@ -700,28 +680,30 @@
|
||||
--this.curLine;
|
||||
}
|
||||
}
|
||||
this.readToken();
|
||||
this.nextToken();
|
||||
};
|
||||
|
||||
pp.curTokContext = function() {
|
||||
pp.curContext = function() {
|
||||
return this.context[this.context.length - 1];
|
||||
};
|
||||
|
||||
// Read a single token, updating the parser object's token-related
|
||||
// properties.
|
||||
|
||||
pp.readToken = function() {
|
||||
var inTemplate = this.curTokContext() === q_tmpl;
|
||||
if (!inTemplate) this.skipSpace();
|
||||
pp.nextToken = function() {
|
||||
var curContext = this.curContext();
|
||||
if (!curContext || !curContext.preserveSpace) this.skipSpace();
|
||||
|
||||
this.start = this.pos;
|
||||
if (this.options.locations) this.startLoc = this.curPosition();
|
||||
if (this.pos >= this.input.length) return this.finishToken(tt.eof);
|
||||
|
||||
if (inTemplate) return this.readTmplToken();
|
||||
if (curContext === tc.q_tmpl) return this.readTmplToken();
|
||||
|
||||
var code = this.fullCharCodeAtPos();
|
||||
this.readToken(this.fullCharCodeAtPos());
|
||||
};
|
||||
|
||||
pp.readToken = function(code) {
|
||||
// Identifier or keyword. '\uXXXX' sequences are allowed in
|
||||
// identifiers, so '\' also dispatches to that.
|
||||
if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */)
|
||||
@ -819,20 +801,32 @@
|
||||
// given point in the program is loosely based on sweet.js' approach.
|
||||
// See https://github.com/mozilla/sweet.js/wiki/design
|
||||
|
||||
var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}, b_tmpl = {token: "${", isExpr: true};
|
||||
var p_stat = {token: "(", isExpr: false}, p_expr = {token: "(", isExpr: true};
|
||||
var q_tmpl = {token: "`", isExpr: true}, f_expr = {token: "function", isExpr: true};
|
||||
var TokContext = exports.TokContext = function(token, isExpr, preserveSpace) {
|
||||
this.token = token;
|
||||
this.isExpr = isExpr;
|
||||
this.preserveSpace = preserveSpace;
|
||||
};
|
||||
|
||||
var tc = exports.tokContexts = {
|
||||
b_stat: new TokContext("{", false),
|
||||
b_expr: new TokContext("{", true),
|
||||
b_tmpl: new TokContext("${", true),
|
||||
p_stat: new TokContext("(", false),
|
||||
p_expr: new TokContext("(", true),
|
||||
q_tmpl: new TokContext("`", true, true),
|
||||
f_expr: new TokContext("function", true)
|
||||
};
|
||||
|
||||
pp.braceIsBlock = function(prevType) {
|
||||
var parent;
|
||||
if (prevType === tt.colon && (parent = this.curTokContext()).token == "{")
|
||||
if (prevType === tt.colon && (parent = this.curContext()).token == "{")
|
||||
return !parent.isExpr;
|
||||
if (prevType === tt._return)
|
||||
return newline.test(this.input.slice(this.lastTokEnd, this.start));
|
||||
if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof)
|
||||
return true;
|
||||
if (prevType == tt.braceL)
|
||||
return this.curTokContext() === b_stat;
|
||||
return this.curContext() === tc.b_stat;
|
||||
return !this.exprAllowed;
|
||||
};
|
||||
|
||||
@ -848,7 +842,11 @@
|
||||
this.type = type;
|
||||
this.value = val;
|
||||
|
||||
var update;
|
||||
this.updateContext(prevType);
|
||||
};
|
||||
|
||||
pp.updateContext = function(prevType) {
|
||||
var update, type = this.type;
|
||||
if (type.keyword && prevType == tt.dot)
|
||||
this.exprAllowed = false;
|
||||
else if (update = type.updateContext)
|
||||
@ -857,6 +855,54 @@
|
||||
this.exprAllowed = type.beforeExpr;
|
||||
};
|
||||
|
||||
// Token-specific context update code
|
||||
|
||||
tt.parenR.updateContext = tt.braceR.updateContext = function() {
|
||||
var out = this.context.pop();
|
||||
if (out === tc.b_stat && this.curContext() === tc.f_expr) {
|
||||
this.context.pop();
|
||||
this.exprAllowed = false;
|
||||
} else if (out === tc.b_tmpl) {
|
||||
this.exprAllowed = true;
|
||||
} else {
|
||||
this.exprAllowed = !(out && out.isExpr);
|
||||
}
|
||||
};
|
||||
|
||||
tt.braceL.updateContext = function(prevType) {
|
||||
this.context.push(this.braceIsBlock(prevType) ? tc.b_stat : tc.b_expr);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.dollarBraceL.updateContext = function() {
|
||||
this.context.push(tc.b_tmpl);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.parenL.updateContext = function(prevType) {
|
||||
var statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while;
|
||||
this.context.push(statementParens ? tc.p_stat : tc.p_expr);
|
||||
this.exprAllowed = true;
|
||||
};
|
||||
|
||||
tt.incDec.updateContext = function() {
|
||||
// tokExprAllowed stays unchanged
|
||||
};
|
||||
|
||||
tt._function.updateContext = function() {
|
||||
if (this.curContext() !== tc.b_stat)
|
||||
this.context.push(tc.f_expr);
|
||||
this.exprAllowed = false;
|
||||
};
|
||||
|
||||
tt.backQuote.updateContext = function() {
|
||||
if (this.curContext() === tc.q_tmpl)
|
||||
this.context.pop();
|
||||
else
|
||||
this.context.push(tc.q_tmpl);
|
||||
this.exprAllowed = false;
|
||||
};
|
||||
|
||||
// ### Token reading
|
||||
|
||||
// This is the function that is called to fetch the next token. It
|
||||
@ -913,7 +959,7 @@
|
||||
// A `-->` line comment
|
||||
this.skipLineComment(3);
|
||||
this.skipSpace();
|
||||
return this.readToken();
|
||||
return this.nextToken();
|
||||
}
|
||||
return this.finishOp(tt.incDec, 2);
|
||||
}
|
||||
@ -934,7 +980,7 @@
|
||||
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
||||
this.skipLineComment(4);
|
||||
this.skipSpace();
|
||||
return this.readToken();
|
||||
return this.nextToken();
|
||||
}
|
||||
if (next === 61)
|
||||
size = this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2;
|
||||
@ -2276,19 +2322,13 @@
|
||||
return id;
|
||||
|
||||
case tt.regexp:
|
||||
var node = this.startNode();
|
||||
node.regex = {pattern: this.value.pattern, flags: this.value.flags};
|
||||
node.value = this.value.value;
|
||||
node.raw = this.input.slice(this.start, this.end);
|
||||
this.next();
|
||||
return this.finishNode(node, "Literal");
|
||||
var value = this.value;
|
||||
var node = this.parseLiteral(value.value);
|
||||
node.regex = {pattern: value.pattern, flags: value.flags};
|
||||
return node;
|
||||
|
||||
case tt.num: case tt.string:
|
||||
var node = this.startNode();
|
||||
node.value = this.value;
|
||||
node.raw = this.input.slice(this.start, this.end);
|
||||
this.next();
|
||||
return this.finishNode(node, "Literal");
|
||||
return this.parseLiteral(this.value);
|
||||
|
||||
case tt._null: case tt._true: case tt._false:
|
||||
var node = this.startNode();
|
||||
@ -2332,6 +2372,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
pp.parseLiteral = function(value) {
|
||||
var node = this.startNode();
|
||||
node.value = value;
|
||||
node.raw = this.input.slice(this.start, this.end);
|
||||
this.next();
|
||||
return this.finishNode(node, "Literal");
|
||||
};
|
||||
|
||||
pp.parseParenAndDistinguishExpression = function() {
|
||||
var start = this.currentPos(), val;
|
||||
if (this.options.ecmaVersion >= 6) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user