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

Conversation

benjamn
Copy link
Owner

@benjamn benjamn commented Apr 24, 2017

@benjamn benjamn requested a review from jdalton April 24, 2017 23:52
@@ -1,6 +1,7 @@
"use strict";

const acorn = require("acorn");
const fixParser = require("./acorn-extensions.js").fix;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a more descriptive name than fix. It looks like the acorn-extensions could be a place to host more than 1 extension.

while (n-- > 0) this.nextToken();
const copy = Object.assign(Object.create(null), this);
Object.assign(this, old);
return copy;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the copying about?

Copy link
Owner Author

Choose a reason for hiding this comment

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

We're taking a snapshot of the parser so that we can roll it back after advancing its state to look at later tokens.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a shallow snapshot so not too bad.

// check for keywords used as local names
for (let i = 0; i < node.specifiers.length; i++) {
const local = node.specifiers[i].local;
if (local && (this.keywords.test(local.name) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the possible values of local? If it's either an object or undefined we should be explicit about the object check with typeof local === "object" && local !== null as v8 (and I guess others) favor that over a bool check.

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'll do you one better: this whole loop can be eliminated (soon)!


if (expect) {
this.unexpected();
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

expect the unexpected 🤣

}

function checkExport(exports, name, pos) {
if (!exports) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar object check for exports.

specifier.exported = this.parseIdent(true);
node.specifiers.push(
this.finishNode(specifier, "ExportNamespaceSpecifier")
);
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.

Just curious does the execution of the this.expect stuff just above matter?
I noticed one was before the const specifier =.....

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yes, this.expect(...) modifies this.start (by calling this.eat(...) which calls this.next()), which is used by this.startNode to set the .start location of the new node, so the relative ordering of this.expect(...) and this.startNode() is important.

Copy link
Contributor

Choose a reason for hiding this comment

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

😵

@jdalton
Copy link
Contributor

jdalton commented Apr 25, 2017

This is mostly all magic to me 😸

The only point of this method besides updating the map of exported symbol
names was to complain if there were duplicate export names, but we
override the raiseRecoverable method with noopRaiseRecoverable anyway, so
there's really no need to override checkExport.
The Reify parser is more like the "loose" Acorn parser in that it doesn't
care about enforcing expectations as long they don't affect the results of
the parsing. So a loop that serves only to call this.unexpected is a loop
that we can do without.
@benjamn
Copy link
Owner Author

benjamn commented Apr 25, 2017

Thanks for the feedback! I think you're gonna like the follow-up commits I just pushed.

}

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.

fixParser(parser);

acornExtensions.enableAll(parser);

return parser.parse();
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the lazy loading of acorn and acorn-extensions do?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Hmm, probably not much if we always use the parser when we import this module (which I think we do). Are you concerned it might hurt?

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!

@jdalton
Copy link
Contributor

jdalton commented Apr 25, 2017

👍

@benjamn benjamn merged commit 846ea05 into master Apr 25, 2017
@jdalton jdalton deleted the support-export-from-extensions branch April 25, 2017 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants