Add a plugin mechanism to allow JSX parser to be a module

And export a few more things
This commit is contained in:
Marijn Haverbeke 2015-02-24 12:51:48 +01:00
parent 06f3b3c224
commit 3365478645

218
acorn.js
View File

@ -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) {