Skip to content

Commit

Permalink
Syntactic placeholders
Browse files Browse the repository at this point in the history
commit d5d4e74
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 20:41:48 2017 -0400

    Allow placeholder to be changed via config

commit 32e43a0
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 20:24:59 2017 -0400

    Spread placeholder tests

commit 73b9d32
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 15:31:50 2017 -0400

    Initial implementation of syntactic placeholders
  • Loading branch information
wcjohnson committed Jul 17, 2017
1 parent 7701096 commit 70ee2c8
Show file tree
Hide file tree
Showing 19 changed files with 599 additions and 6 deletions.
5 changes: 4 additions & 1 deletion src/options.js
Expand Up @@ -9,7 +9,8 @@ export const defaultOptions: {
allowImportExportEverywhere: boolean,
allowSuperOutsideMethod: boolean,
plugins: Array<string>,
strictMode: any
strictMode: any,
placeholder: string
} = {
// Source type ("script" or "module") for different semantics
sourceType: "script",
Expand All @@ -30,6 +31,8 @@ export const defaultOptions: {
plugins: [],
// TODO
strictMode: null,
// Default placeholder for use with syntacticPlaceholder plugin
placeholder: "_"
};

// Interpret and default an options object
Expand Down
19 changes: 15 additions & 4 deletions src/parser/expression.js
Expand Up @@ -412,7 +412,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
node.property = this.parseExprAtom();
node.computed = true;
} else {
node.property = this.parseIdentifier(true);
node.property = this.parseIdentifierOrPlaceholder(true);
node.computed = false;
}
base = this.finishNode(node, "MemberExpression");
Expand All @@ -431,7 +431,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
node.property = this.parseLiteral(this.state.value, "NumericLiteral");
node.computed = true;
} else {
node.property = this.parseIdentifier(true);
node.property = this.parseIdentifierOrPlaceholder(true);
node.computed = false;
}
} else if (op === "?[") {
Expand Down Expand Up @@ -651,7 +651,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
node = this.startNode();
const allowAwait = this.state.value === "await" && this.state.inAsync;
const allowYield = this.shouldAllowYieldIdentifier();
const id = this.parseIdentifier(allowAwait || allowYield);
const id = this.parseIdentifierOrPlaceholder(allowAwait || allowYield);

if (id.name === "await") {
if (this.state.inAsync || this.inModule) {
Expand All @@ -661,7 +661,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
this.next();
return this.parseFunction(node, false, false, true);
} else if (canBeArrow && id.name === "async" && this.match(tt.name)) {
const params = [this.parseIdentifier()];
const params = [this.parseIdentifierOrPlaceholder()];
this.check(tt.arrow);
// let foo = bar => {};
return this.parseArrowExpression(node, params, true);
Expand Down Expand Up @@ -1134,6 +1134,7 @@ pp.parseObj = function (isPattern, refShorthandDefaultPos) {
if (!isPattern && this.isContextual("async")) {
if (isGenerator) this.unexpected();

// TODO: syntacticPlaceholder: is a placeholder legal here?
const asyncId = this.parseIdentifier();
if (this.match(tt.colon) || this.match(tt.parenL) || this.match(tt.braceR) || this.match(tt.eq) || this.match(tt.comma) || (this.hasPlugin("lightscript") && this.isLineBreak())) {
prop.key = asyncId;
Expand Down Expand Up @@ -1262,6 +1263,7 @@ pp.parsePropertyName = function (prop) {
prop.computed = false;
const oldInPropertyName = this.state.inPropertyName;
this.state.inPropertyName = true;
// TODO: syntacticPlaceholder: is a placeholder legal here?
prop.key = (this.match(tt.num) || this.match(tt.string)) ? this.parseExprAtom() : this.parseIdentifier(true);
this.state.inPropertyName = oldInPropertyName;
}
Expand Down Expand Up @@ -1465,6 +1467,15 @@ pp.parseIdentifier = function (liberal) {
return this.finishNode(node, "Identifier");
};

// Syntactic placeholders: shunt based on plugin status
pp.parseIdentifierOrPlaceholder = function(liberal) {
if (this.hasPlugin("syntacticPlaceholder")) {
return this._parseIdentifierOrPlaceholder(liberal);
} else {
return this.parseIdentifier(liberal);
}
};

pp.checkReservedWord = function (word, startLoc, checkKeywords, isBinding) {
if (this.isReservedWord(word) || (checkKeywords && this.isKeyword(word))) {
this.raise(startLoc, word + " is a reserved word");
Expand Down
51 changes: 51 additions & 0 deletions src/plugins/syntacticPlaceholder.js
@@ -0,0 +1,51 @@
import Parser from "../parser";
import { types as tt } from "../tokenizer/types";
const pp = Parser.prototype;

export default function(parser) {
if (parser.__syntacticPlaceholderPluginInstalled) return;
parser.__syntacticPlaceholderPluginInstalled = true;

const ph = parser.options.placeholder || "_";
const quotedPh = (ph + "").replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&");
const phRegex = new RegExp("^" + quotedPh + "([0-9]*)$");

pp.isPlaceholderName = function(name) {
return phRegex.test(name);
};

// c/p parseIdentifier
pp._parseIdentifierOrPlaceholder = function(liberal) {
const node = this.startNode();
if (!liberal) {
this.checkReservedWord(this.state.value, this.state.start, !!this.state.type.keyword, false);
}

let name;
if (this.match(tt.name)) {
name = this.state.value;
} else if (this.state.type.keyword) {
name = this.state.type.keyword;
} else {
this.unexpected();
}

const matches = phRegex.exec(name);
if (matches) {
if (matches[1]) node.index = parseInt(matches[1]);
this.next();
return this.finishNode(node, "PlaceholderExpression");
}

node.name = name;

if (!liberal && node.name === "await" && this.state.inAsync) {
this.raise(node.start, "invalid use of await inside of an async function");
}

node.loc.identifierName = node.name;

this.next();
return this.finishNode(node, "Identifier");
};
}
2 changes: 1 addition & 1 deletion src/plugins/tildeCall.js
Expand Up @@ -13,7 +13,7 @@ export default function(parser) {
node.left = left;

// allow `this`, Identifier or MemberExpression, but not calls
const right = this.match(tt._this) ? this.parseExprAtom() : this.parseIdentifier();
const right = this.match(tt._this) ? this.parseExprAtom() : this.parseIdentifierOrPlaceholder();
node.right = this.parseSubscripts(right, this.state.start, this.state.startLoc, true);

// Allow safe tilde calls (a~b?(c))
Expand Down
5 changes: 5 additions & 0 deletions src/registerPlugins.js
Expand Up @@ -7,6 +7,7 @@ import safeCallExistentialPlugin from "./plugins/safeCallExistential";
import bangCallPlugin from "./plugins/bangCall";
import significantWhitespacePlugin from "./plugins/significantWhitespace";
import enhancedComprehensionPlugin from "./plugins/enhancedComprehension";
import syntacticPlaceholderPlugin from "./plugins/syntacticPlaceholder";
import { matchCoreSyntax, match } from "./plugins/match";

function noncePlugin() {}
Expand Down Expand Up @@ -79,4 +80,8 @@ export default function registerPlugins(plugins, metadata) {
registerPlugin("objectBlockAmbiguity_preferObject", noncePlugin, {
dependencies: ["lightscript"]
});

// Parse identifiers beginning with `_` or another user-chosen symbol
// as PlaceholderExpressions.
registerPlugin("syntacticPlaceholder", syntacticPlaceholderPlugin);
}
1 change: 1 addition & 0 deletions test/fixtures/syntactic-placeholder/basic/basic/actual.js
@@ -0,0 +1 @@
_
64 changes: 64 additions & 0 deletions test/fixtures/syntactic-placeholder/basic/basic/expected.json
@@ -0,0 +1,64 @@
{
"type": "File",
"start": 0,
"end": 1,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 1,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 1
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 1,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 1
}
},
"expression": {
"type": "PlaceholderExpression",
"start": 0,
"end": 1,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 1
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
_1
65 changes: 65 additions & 0 deletions test/fixtures/syntactic-placeholder/basic/indexed/expected.json
@@ -0,0 +1,65 @@
{
"type": "File",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
}
},
"program": {
"type": "Program",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
}
},
"expression": {
"type": "PlaceholderExpression",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
}
},
"index": 1
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
-> [..._1]

0 comments on commit 70ee2c8

Please sign in to comment.