From 336547864581ea8a547c1e89f4dd3d87e9d1e04f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 24 Feb 2015 12:51:48 +0100 Subject: [PATCH] Add a plugin mechanism to allow JSX parser to be a module And export a few more things --- acorn.js | 218 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 133 insertions(+), 85 deletions(-) diff --git a/acorn.js b/acorn.js index c623d8003f..6ec3a38eba 100644 --- a/acorn.js +++ b/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 @@ // `