Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Export{Default,Namespace}Specifier parsing ourselves. #146

Merged
merged 6 commits into from
Apr 25, 2017
38 changes: 24 additions & 14 deletions lib/parsers/acorn-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ exports.enableTolerance = function (parser) {
function noopRaiseRecoverable() {}

exports.enableExportExtensions = function (parser) {
// Lookahead method.
parser.lookAhead = lookAhead;
// Our custom lookahead method.
parser.withLookAhead = withLookAhead;

// Export-related modifications.
parser.parseExport = parseExport;
Expand Down Expand Up @@ -55,7 +55,7 @@ function parseExport(node, exports) {
this.finishNode(specifier, "ExportDefaultSpecifier")
];
if (this.type === tt.comma &&
this.lookAhead(1).type === tt.star) {
peekNextType(this) === tt.star) {
// export def, * as ns from '...'
this.expect(tt.comma);
const specifier = this.startNode();
Expand Down Expand Up @@ -106,23 +106,33 @@ function parseExport(node, exports) {
return this.finishNode(node, "ExportNamedDeclaration");
}

function lookAhead(n) {
// Calls the given callback with the state of the parser temporarily
// advanced by calling this.nextToken() n times, then rolls the parser
// back to its original state and returns whatever the callback returned.
function withLookAhead(n, callback) {
const old = Object.assign(Object.create(null), this);
while (n-- > 0) this.nextToken();
const copy = Object.assign(Object.create(null), this);
Object.assign(this, old);
return copy;
try {
return callback.call(this);
} finally {
Copy link
Contributor

@jdalton jdalton Apr 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a typical callback look like?
If it's just () => parser.type you can optimize the callback call without the this binding and just do

return callback();

Or simplify it further.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see isCommaOrFrom uses this. No worries.

Copy link
Owner Author

@benjamn benjamn Apr 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another case (isCommaOrFrom) where we use this.

Any preference between these two options?

return callback.call(this);
// or
return callback(this);

Either of those could work for isCommaOrFrom.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In event loops it's always sad to see folks default to applying a this binding because many times it's not used and does add a cost to invocation. So if you could pass in the this as the first arg that is super nice. It's what I would recommend for event systems too!

Object.assign(this, old);
}
}

function peekNextType(parser) {
return parser.withLookAhead(1, () => parser.type);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this new interface, personally.

}

function isExportDefaultSpecifier() {
if (this.type !== tt.name) {
return false;
}
return this.type === tt.name &&
this.withLookAhead(1, isCommaOrFrom);
}

const lookAhead = this.lookAhead(1);
return lookAhead.type === tt.comma ||
(lookAhead.type === tt.name &&
lookAhead.value === "from");
function isCommaOrFrom() {
// Note: `this` should be the parser object.
return this.type === tt.comma ||
(this.type === tt.name &&
this.value === "from");
}

function parseExportSpecifiersMaybe(node) {
Expand Down