Spread attribute support and small optimizations.

Conflicts:
	acorn.js
	docs/acorn.html
	package.json
	test/tests.js
This commit is contained in:
Ingvar Stepanyan 2014-07-27 02:56:41 +03:00
parent fe6cafa141
commit 35c8f104bd
7 changed files with 3910 additions and 2227 deletions

View File

@ -1,5 +1,7 @@
# Acorn (JSX edition) # Acorn (JSX edition)
[![Build Status](https://travis-ci.org/RReverser/acorn.svg?branch=master)](https://travis-ci.org/RReverser/acorn)
This is modification of [Acorn][acorn] - a tiny, fast JavaScript parser, written completely in JavaScript. This is modification of [Acorn][acorn] - a tiny, fast JavaScript parser, written completely in JavaScript.
It was forked to create experimental, alternative, faster [React.js JSX][jsx] parser by integrating pieces It was forked to create experimental, alternative, faster [React.js JSX][jsx] parser by integrating pieces

236
acorn.js
View File

@ -351,6 +351,7 @@
var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true};
var _arrow = {type: "=>", beforeExpr: true}, _bquote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; var _arrow = {type: "=>", beforeExpr: true}, _bquote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true};
var _ltSlash = {type: "</"};
// Operators. These carry several kinds of properties to help the // Operators. These carry several kinds of properties to help the
// parser use them properly (the presence of these properties is // parser use them properly (the presence of these properties is
@ -385,6 +386,9 @@
// '*' may be multiply or have special meaning in ES6 // '*' may be multiply or have special meaning in ES6
var _star = {binop: 10, beforeExpr: true}; var _star = {binop: 10, beforeExpr: true};
// '<', '>' may be relational or have special meaning in JSX
var _lt = {binop: 7, beforeExpr: true}, _gt = {binop: 7, beforeExpr: true};
// Provide access to the token types for external users of the // Provide access to the token types for external users of the
// tokenizer. // tokenizer.
@ -544,14 +548,14 @@
tokEnd = tokPos; tokEnd = tokPos;
if (options.locations) tokEndLoc = new Position; if (options.locations) tokEndLoc = new Position;
tokType = type; tokType = type;
if (shouldSkipSpace !== false && !(inXJSTag && val === '>') && !(inXJSChild && tokType !== _braceL)) { if (shouldSkipSpace !== false && !(inXJSTag && type === _gt) && !(inXJSChild && tokType !== _braceL)) {
skipSpace(); skipSpace();
} }
tokVal = val; tokVal = val;
tokRegexpAllowed = type.beforeExpr; tokRegexpAllowed = type.beforeExpr;
if (options.onToken) { if (options.onToken) {
options.onToken(getCurrentToken()); options.onToken(getCurrentToken());
} }
} }
function skipBlockComment() { function skipBlockComment() {
@ -712,9 +716,16 @@
skipSpace(); skipSpace();
return readToken(); return readToken();
} }
if (next === 61) if (next === 61) {
size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2; size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
return finishOp(_relational, size); return finishOp(_relational, size);
}
if (next === 47) {
// '</', beginning of JSX closing element
size = 2;
return finishOp(_ltSlash, size);
}
return finishOp(code === 60 ? _lt : _gt, size);
} }
function readToken_eq_excl(code) { // '=!', '=>' function readToken_eq_excl(code) { // '=!', '=>'
@ -732,34 +743,34 @@
function getTemplateToken(code) { function getTemplateToken(code) {
// '`' and '${' have special meanings, but they should follow // '`' and '${' have special meanings, but they should follow
// string (can be empty) // string (can be empty)
if (tokType === _string) { if (tokType === _string) {
if (code === 96) { // '`' if (code === 96) { // '`'
++tokPos; ++tokPos;
return finishToken(_bquote); return finishToken(_bquote);
} else } else
if (code === 36 && input.charCodeAt(tokPos + 1) === 123) { // '${' if (code === 36 && input.charCodeAt(tokPos + 1) === 123) { // '${'
tokPos += 2; tokPos += 2;
return finishToken(_dollarBraceL); return finishToken(_dollarBraceL);
}
} }
}
if (code === 125) { // '}' if (code === 125) { // '}'
++tokPos; ++tokPos;
return finishToken(_braceR, undefined, false); return finishToken(_braceR, undefined, false);
} }
// anything else is considered string literal // anything else is considered string literal
return readTmplString(); return readTmplString();
} }
function getTokenFromCode(code) { function getTokenFromCode(code) {
switch (code) { switch(code) {
// The interpretation of a dot depends on whether it is followed // The interpretation of a dot depends on whether it is followed
// by a digit or another two dots. // by a digit or another two dots.
case 46: // '.' case 46: // '.'
return readToken_dot(); return readToken_dot();
// Punctuation tokens. // Punctuation tokens.
case 40: ++tokPos; return finishToken(_parenL); case 40: ++tokPos; return finishToken(_parenL);
case 41: ++tokPos; return finishToken(_parenR); case 41: ++tokPos; return finishToken(_parenR);
case 59: ++tokPos; return finishToken(_semi); case 59: ++tokPos; return finishToken(_semi);
@ -784,12 +795,12 @@
if (next === 111 || next === 79) return readRadixNumber(8); // '0o', '0O' - octal number if (next === 111 || next === 79) return readRadixNumber(8); // '0o', '0O' - octal number
if (next === 98 || next === 66) return readRadixNumber(2); // '0b', '0B' - binary number if (next === 98 || next === 66) return readRadixNumber(2); // '0b', '0B' - binary number
} }
// Anything else beginning with a digit is an integer, octal // Anything else beginning with a digit is an integer, octal
// number, or float. // number, or float.
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9 case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
return readNumber(false); return readNumber(false);
// Quotes produce strings. // Quotes produce strings.
case 34: case 39: // '"', "'" case 34: case 39: // '"', "'"
return inXJSTag ? readXJSStringLiteral() : readString(code); return inXJSTag ? readXJSStringLiteral() : readString(code);
@ -993,9 +1004,9 @@
++tokPos; ++tokPos;
if (newline.test(String.fromCharCode(ch))) { if (newline.test(String.fromCharCode(ch))) {
raise(tokStart, "Unterminated string constant"); raise(tokStart, "Unterminated string constant");
} }
out += String.fromCharCode(ch); // '\' out += String.fromCharCode(ch); // '\'
} }
} }
} }
@ -1011,15 +1022,15 @@
} else { } else {
++tokPos; ++tokPos;
if (newline.test(String.fromCharCode(ch))) { if (newline.test(String.fromCharCode(ch))) {
if (ch === 13 && input.charCodeAt(tokPos) === 10) { if (ch === 13 && input.charCodeAt(tokPos) === 10) {
++tokPos; ++tokPos;
ch = 10; ch = 10;
} }
if (options.locations) { if (options.locations) {
++tokCurLine; ++tokCurLine;
tokLineStart = tokPos; tokLineStart = tokPos;
} }
} }
out += String.fromCharCode(ch); // '\' out += String.fromCharCode(ch); // '\'
} }
} }
@ -1611,7 +1622,7 @@
function has(obj, propName) { function has(obj, propName) {
return Object.prototype.hasOwnProperty.call(obj, propName); return Object.prototype.hasOwnProperty.call(obj, propName);
} }
// Convert existing expression atom to assignable pattern // Convert existing expression atom to assignable pattern
// if possible. // if possible.
function toAssignable(node, allowSpread, checkType) { function toAssignable(node, allowSpread, checkType) {
@ -1746,8 +1757,8 @@
break; break;
default: default:
raise(expr.start, "Assigning to rvalue"); raise(expr.start, "Assigning to rvalue");
} }
} }
// ### Statement parsing // ### Statement parsing
@ -2304,28 +2315,28 @@
if (options.ecmaVersion >= 6 && tokType === _for) { if (options.ecmaVersion >= 6 && tokType === _for) {
val = parseComprehension(startNode(), true); val = parseComprehension(startNode(), true);
} else { } else {
var oldParenL = ++metParenL; var oldParenL = ++metParenL;
if (tokType !== _parenR) { if (tokType !== _parenR) {
val = parseExpression(); val = parseExpression();
exprList = val.type === "SequenceExpression" ? val.expressions : [val]; exprList = val.type === "SequenceExpression" ? val.expressions : [val];
} else { } else {
exprList = []; exprList = [];
} }
expect(_parenR); expect(_parenR);
// if '=>' follows '(...)', convert contents to arguments // if '=>' follows '(...)', convert contents to arguments
if (metParenL === oldParenL && eat(_arrow)) { if (metParenL === oldParenL && eat(_arrow)) {
val = parseArrowExpression(startNode(), exprList); val = parseArrowExpression(startNode(), exprList);
} else { } else {
// forbid '()' before everything but '=>' // forbid '()' before everything but '=>'
if (!val) unexpected(lastStart); if (!val) unexpected(lastStart);
// forbid '...' in sequence expressions // forbid '...' in sequence expressions
if (options.ecmaVersion >= 6) { if (options.ecmaVersion >= 6) {
for (var i = 0; i < exprList.length; i++) { for (var i = 0; i < exprList.length; i++) {
if (exprList[i].type === "SpreadElement") unexpected(); if (exprList[i].type === "SpreadElement") unexpected();
}
} }
} }
} }
}
val.start = tokStart1; val.start = tokStart1;
val.end = lastEnd; val.end = lastEnd;
if (options.locations) { if (options.locations) {
@ -2343,7 +2354,7 @@
// check whether this is array comprehension or regular array // check whether this is array comprehension or regular array
if (options.ecmaVersion >= 6 && tokType === _for) { if (options.ecmaVersion >= 6 && tokType === _for) {
return parseComprehension(node, false); return parseComprehension(node, false);
} }
node.elements = parseExprList(_bracketR, true, true); node.elements = parseExprList(_bracketR, true, true);
return finishNode(node, "ArrayExpression"); return finishNode(node, "ArrayExpression");
@ -2367,10 +2378,8 @@
case _bquote: case _bquote:
return parseTemplate(); return parseTemplate();
case _relational: case _lt:
if (tokVal === '<') { return parseXJSElement();
return parseXJSElement();
}
default: default:
unexpected(); unexpected();
@ -2467,9 +2476,9 @@
checkPropClash(prop, propHash); checkPropClash(prop, propHash);
node.properties.push(finishNode(prop, "Property")); node.properties.push(finishNode(prop, "Property"));
} }
return finishNode(node, "ObjectExpression"); return finishNode(node, "ObjectExpression");
} }
function parsePropertyName(prop) { function parsePropertyName(prop) {
if (options.ecmaVersion >= 6) { if (options.ecmaVersion >= 6) {
@ -2494,7 +2503,7 @@
node.defaults = []; node.defaults = [];
node.rest = null; node.rest = null;
node.generator = false; node.generator = false;
} }
} }
// Parse a function declaration or literal (depending on the // Parse a function declaration or literal (depending on the
@ -2602,11 +2611,11 @@
node.body = parseExpression(true); node.body = parseExpression(true);
node.expression = true; node.expression = true;
} else { } else {
// Start a new scope with regard to labels and the `inFunction` // Start a new scope with regard to labels and the `inFunction`
// flag (restore them to their old value afterwards). // flag (restore them to their old value afterwards).
var oldInFunc = inFunction, oldInGen = inGenerator, oldLabels = labels; var oldInFunc = inFunction, oldInGen = inGenerator, oldLabels = labels;
inFunction = true; inGenerator = node.generator; labels = []; inFunction = true; inGenerator = node.generator; labels = [];
node.body = parseBlock(true); node.body = parseBlock(true);
node.expression = false; node.expression = false;
inFunction = oldInFunc; inGenerator = oldInGen; labels = oldLabels; inFunction = oldInFunc; inGenerator = oldInGen; labels = oldLabels;
} }
@ -2640,9 +2649,9 @@
if (tokType === _name && tokVal === "static") { if (tokType === _name && tokVal === "static") {
next(); next();
method['static'] = true; method['static'] = true;
} else { } else {
method['static'] = false; method['static'] = false;
} }
var isGenerator = eat(_star); var isGenerator = eat(_star);
parsePropertyName(method); parsePropertyName(method);
if (tokType === _name && !method.computed && method.key.type === "Identifier" && if (tokType === _name && !method.computed && method.key.type === "Identifier" &&
@ -2851,8 +2860,8 @@
node.delegate = false; node.delegate = false;
node.argument = null; node.argument = null;
} else { } else {
node.delegate = eat(_star); node.delegate = eat(_star);
node.argument = parseExpression(true); node.argument = parseExpression(true);
} }
return finishNode(node, "YieldExpression"); return finishNode(node, "YieldExpression");
} }
@ -2972,24 +2981,27 @@
// Parses any type of JSX attribute value. // Parses any type of JSX attribute value.
function parseXJSAttributeValue() { function parseXJSAttributeValue() {
var node; switch (tokType) {
if (tokType === _braceL) { case _braceL:
node = parseXJSExpressionContainer(); var node = parseXJSExpressionContainer();
if (node.expression.type === "XJSEmptyExpression") { if (node.expression.type === "XJSEmptyExpression") {
raise( raise(
node.start, node.start,
'XJS attributes must only be assigned a non-empty ' + 'XJS attributes must only be assigned a non-empty ' +
'expression' 'expression'
); );
} }
} else if (tokVal === '<') { return node;
node = parseXJSElement();
} else if (tokType === _xjsText) { case _lt:
node = parseExprAtom(); return parseXJSElement();
} else {
raise(tokStart, "XJS value should be either an expression or a quoted XJS text"); case _xjsText:
return parseExprAtom();
default:
raise(tokStart, "XJS value should be either an expression or a quoted XJS text");
} }
return node;
} }
// XJSEmptyExpression is unique type since it doesn't actually parse anything, // XJSEmptyExpression is unique type since it doesn't actually parse anything,
@ -3022,28 +3034,54 @@
var origInXJSTag = inXJSTag; var origInXJSTag = inXJSTag;
inXJSTag = false; inXJSTag = false;
expect(_braceL); next();
node.expression = tokType === _braceR ? parseXJSEmptyExpression() : parseExpression(); node.expression = tokType === _braceR ? parseXJSEmptyExpression() : parseExpression();
inXJSTag = origInXJSTag; inXJSTag = origInXJSTag;
expect(_braceR); expect(_braceR);
return finishNode(node, "XJSExpressionContainer"); return finishNode(node, "XJSExpressionContainer");
} }
// Parses following JSX attribute name-value pair. // Parses following JSX attribute name-value pair.
function parseXJSAttribute() { function parseXJSAttribute() {
var node = startNode(); if (tokType === _braceL) {
var tokStart1 = tokStart, tokStartLoc1 = tokStartLoc;
var origInXJSTag = inXJSTag;
inXJSTag = false;
next();
var node = parseSpread();
inXJSTag = origInXJSTag;
expect(_braceR);
node.type = "XJSSpreadAttribute";
node.start = tokStart1;
node.end = lastEnd;
if (options.locations) {
node.loc.start = tokStartLoc1;
node.loc.end = lastEndLoc;
}
if (options.ranges) {
node.range = [tokStart1, lastEnd];
}
return node;
}
var node = startNode();
node.name = parseXJSAttributeName(); node.name = parseXJSAttributeName();
// HTML empty attribute // HTML empty attribute
if (tokVal === "=") { if (tokType === _eq) {
next(); next();
node.value = parseXJSAttributeValue(); node.value = parseXJSAttributeValue();
} else {
node.value = null;
} }
return finishNode(node, "XJSAttribute"); return finishNode(node, "XJSAttribute");
@ -3074,11 +3112,11 @@
inXJSChild = false; inXJSChild = false;
inXJSTag = true; inXJSTag = true;
expectChar('<'); next();
node.name = parseXJSElementName(); node.name = parseXJSElementName();
while (tokType !== _eof && tokType !== _slash && tokVal !== '>') { while (tokType !== _eof && tokType !== _slash && tokType !== _gt) {
attributes.push(parseXJSAttribute()); attributes.push(parseXJSAttribute());
} }
@ -3091,7 +3129,7 @@
inXJSChild = true; inXJSChild = true;
} }
expectChar('>'); expect(_gt);
return finishNode(node, "XJSOpeningElement"); return finishNode(node, "XJSOpeningElement");
} }
@ -3105,8 +3143,7 @@
inXJSChild = false; inXJSChild = false;
inXJSTag = true; inXJSTag = true;
tokRegexpAllowed = false; tokRegexpAllowed = false;
expectChar('<'); expect(_ltSlash);
expect(_slash);
node.name = parseXJSElementName(); node.name = parseXJSElementName();
skipSpace(); skipSpace();
// A valid token is expected after >, so parser needs to know // A valid token is expected after >, so parser needs to know
@ -3114,7 +3151,7 @@
inXJSChild = origInXJSChild; inXJSChild = origInXJSChild;
inXJSTag = origInXJSTag; inXJSTag = origInXJSTag;
tokRegexpAllowed = false; tokRegexpAllowed = false;
expectChar('>'); expect(_gt);
return finishNode(node, "XJSClosingElement"); return finishNode(node, "XJSClosingElement");
} }
@ -3127,14 +3164,15 @@
var origInXJSChild = inXJSChild; var origInXJSChild = inXJSChild;
var openingElement = parseXJSOpeningElement(); var openingElement = parseXJSOpeningElement();
var closingElement = null;
if (!openingElement.selfClosing) { if (!openingElement.selfClosing) {
while (tokType !== _eof && !(tokVal === '<' && nextChar() === '/')) { while (tokType !== _eof && tokType !== _ltSlash) {
inXJSChild = true; inXJSChild = true;
children.push(parseXJSChild()); children.push(parseXJSChild());
} }
inXJSChild = origInXJSChild; inXJSChild = origInXJSChild;
var closingElement = parseXJSClosingElement(); closingElement = parseXJSClosingElement();
if (getQualifiedXJSName(closingElement.name) !== getQualifiedXJSName(openingElement.name)) { if (getQualifiedXJSName(closingElement.name) !== getQualifiedXJSName(openingElement.name)) {
raise( raise(
closingElement.start, closingElement.start,
@ -3152,7 +3190,7 @@
// element, we disallow it here in the parser in order to provide a // element, we disallow it here in the parser in order to provide a
// better error message. (In the rare case that the less-than operator // better error message. (In the rare case that the less-than operator
// was intended, the left tag can be wrapped in parentheses.) // was intended, the left tag can be wrapped in parentheses.)
if (!origInXJSChild && tokVal === '<') { if (!origInXJSChild && tokType === _lt) {
raise(tokStart, "Adjacent XJS elements must be wrapped in an enclosing tag"); raise(tokStart, "Adjacent XJS elements must be wrapped in an enclosing tag");
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "acorn-jsx", "name": "acorn-jsx",
"description": "Alternative React JSX parser", "description": "Alternative React JSX parser",
"main": "acorn.js", "main": "acorn.js",
"version": "0.7.1", "version": "0.7.1-1",
"maintainers": [ "maintainers": [
{ {
"name": "Marijn Haverbeke", "name": "Marijn Haverbeke",

View File

@ -1,6 +1,7 @@
var driver = require("./driver.js"); var driver = require("./driver.js");
require("./tests.js"); require("./tests.js");
require("./tests-harmony.js"); require("./tests-harmony.js");
require("./tests-jsx.js");
var testsRun = 0, failed = 0; var testsRun = 0, failed = 0;
function report(state, code, message) { function report(state, code, message) {

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ if (typeof exports != "undefined") {
var testFail = require("./driver.js").testFail; var testFail = require("./driver.js").testFail;
var testAssert = require("./driver.js").testAssert; var testAssert = require("./driver.js").testAssert;
var acorn = require(".."); var acorn = require("..");
require("./tests-jsx.js");
} }
test("this\n", { test("this\n", {