Skip to content

Commit

Permalink
2.3.0
Browse files Browse the repository at this point in the history
- Syntactic placeholders
- Pipe calls `|>`, `<|`
- Track block nesting level in parser state
- Fix bang call subscript unwinding crossing block boundaries
- Fix premature “comprehensions are illegal” error disallowing patterns with “for” or “case” keys
- Don’t lint when testing

commit 64f066f
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Sep 25 15:36:41 2017 -0400

    Fix for bang call subscripting issue across block boundaries

commit bda54e5
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Sep 25 14:38:44 2017 -0400

    Comprehension fixes

commit 476419a
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sat Sep 23 15:53:55 2017 -0400

    Run lint and flow at `preversion`, not `test`

commit 266f948
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Sep 18 23:16:07 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-alpha.3

commit 2342c39
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Tue Sep 5 16:51:01 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-alpha.2

commit 21a26a8
Merge: e61679c 7c5e20e
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Tue Sep 5 15:24:38 2017 -0400

    Merge branch 'prerelease/2.3.0' of https://github.com/wcjohnson/babylon-lightscript into prerelease/2.3.0

commit e61679c
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Tue Sep 5 15:24:20 2017 -0400

    Unit test for unfortunate flow typecast in if test clause

commit 7c5e20e
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Aug 6 12:58:46 2017 -0400

    Misc cleanup

    - Remove errant copypasta from tildeCall.js
    - Clean up spacing/comments

commit ffb7ddb
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Wed Jul 19 21:43:42 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-alpha.1

commit fb570ee
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Wed Jul 19 21:28:23 2017 -0400

    Pipe call improvements

    - Support arrows as pipe call operands
    - Support leftward-pointing pipe calls

commit f248451
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Jul 17 16:09:12 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-alpha.0

commit 00a76ef
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Jul 17 15:52:31 2017 -0400

    Fix for left-associativity and subscripts of pipeCalls

commit 6d4d300
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Jul 17 00:43:08 2017 -0400

    `pipeCall` tests

commit 4e579c7
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Mon Jul 17 00:28:40 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-2

commit 17f5a44
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 23:19:22 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-1

commit 0bcd865
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 23:07:36 2017 -0400

    @oigroup/babylon-lightscript@2.3.0-0

commit 6975053
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 23:03:53 2017 -0400

    Parsing for pipe operator

    commit b10a5e48d1552ff389de314762e863197bc0da7e
    Author: William C. Johnson <wcjohnson@oigroup.net>
    Date:   Sun Jul 16 22:55:34 2017 -0400

        Fix associativity

    commit 0fac7c226b9cd9cda94a5956a45a067a145e2976
    Author: William C. Johnson <wcjohnson@oigroup.net>
    Date:   Sun Jul 16 22:22:34 2017 -0400

        Parse pipe operator as subscript

    commit b21acb2f12941d9d7d4279320de4c55c7ee3b50f
    Author: William C. Johnson <wcjohnson@oigroup.net>
    Date:   Sun Jul 16 21:17:17 2017 -0400

        Basic pipeCall parsing

commit 70ee2c8
Author: William C. Johnson <wcjohnson@oigroup.net>
Date:   Sun Jul 16 20:46:52 2017 -0400

    Syntactic placeholders

    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 Sep 25, 2017
1 parent 833e5b9 commit 2376b75
Show file tree
Hide file tree
Showing 48 changed files with 2,043 additions and 18 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oigroup/babylon-lightscript",
"version": "2.2.1",
"version": "2.3.0-alpha.3",
"description": "A LightScript parser, based on babylon (a JavaScript parser)",
"author": "Alex Rattray <rattray.alex@gmail.com>",
"homepage": "http://lightscript.org/",
Expand Down Expand Up @@ -53,8 +53,8 @@
"clean": "rimraf lib",
"flow": "flow",
"prepublish": "cross-env BABEL_ENV=production npm run build",
"preversion": "npm run test",
"test": "npm run lint && npm run flow && npm run build -- -m && npm run test-only",
"preversion": "npm run lint && npm run flow && npm run test",
"test": "npm run build -- -m && npm run test-only",
"test-only": "ava",
"test-ci": "nyc npm run test-only",
"changelog": "git log `git describe --tags --abbrev=0`..HEAD --pretty=format:' * %s (%an)' | grep -v 'Merge pull request'",
Expand Down
5 changes: 4 additions & 1 deletion src/options.js
Original file line number Diff line number Diff line change
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
39 changes: 32 additions & 7 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,12 @@ pp.parseExprSubscripts = function (refShorthandDefaultPos) {
};

pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {

// pipeCall plugin hack: pass noPipes via state to avoid changing args
// to core parser function.
const noPipes = this.state.noPipeSubscripts;
this.state.noPipeSubscripts = false;

for (;;) {
if (this.hasPlugin("bangCall") && this.shouldUnwindBangSubscript()) {
return base;
Expand Down Expand Up @@ -412,7 +418,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 +437,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 @@ -468,6 +474,14 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
node.callee = base;
const next = this.parseBangCall(node, "CallExpression");
if (next) base = next; else return node;
} else if (
!noCalls &&
!noPipes &&
this.hasPlugin("pipeCall") &&
this.match(tt.pipeCall)
) {
const node = this.startNodeAt(startPos, startLoc);
base = this.parsePipeCall(node, base);
} else if (!(this.hasPlugin("lightscript") && this.isNonIndentedBreakFrom(startPos)) && this.eat(tt.bracketL)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
Expand Down Expand Up @@ -651,7 +665,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 +675,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 @@ -1075,10 +1089,10 @@ pp.parseObj = function (isPattern, refShorthandDefaultPos) {
this.hasPlugin("enhancedComprehension") &&
(this.match(tt._for) || this.match(tt._case))
) {
if (isPattern) {
this.unexpected(null, "Comprehensions are illegal in patterns.");
}
if (this.lookahead().type !== tt.colon) {
if (isPattern) {
this.unexpected(null, "Comprehensions are illegal in patterns.");
}
node.properties.push(this.parseSomeComprehension());
hasComprehension = true;
continue;
Expand Down Expand Up @@ -1134,6 +1148,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 +1277,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 +1481,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
4 changes: 4 additions & 0 deletions src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,8 @@ pp.parseBlockBody = function (node, allowDirectives, topLevel, end) {
let oldStrict;
let octalPosition;

this.state.nestedBlockLevel++;

let isEnd;
if (this.hasPlugin("lightscript") && typeof end === "number") {
isEnd = () => this.state.indentLevel <= end || this.match(tt.eof);
Expand Down Expand Up @@ -642,6 +644,8 @@ pp.parseBlockBody = function (node, allowDirectives, topLevel, end) {
node.body.push(stmt);
}

this.state.nestedBlockLevel--;

if (oldStrict === false) {
this.setStrict(false);
}
Expand Down
7 changes: 6 additions & 1 deletion src/plugins/bangCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export default function(parser) {
// Read args
let first = true;
const oldBangUnwindLevel = this.state.bangUnwindLevel;
const oldBangBlockLevel = this.state.bangBlockLevel;
this.state.bangBlockLevel = this.state.nestedBlockLevel;
this.state.bangUnwindLevel = bangIndentLevel + 1;

while (true) {
Expand Down Expand Up @@ -81,6 +83,7 @@ export default function(parser) {
}

this.state.bangUnwindLevel = oldBangUnwindLevel;
this.state.bangBlockLevel = oldBangBlockLevel;

node = this.finishNode(node, nodeType);

Expand All @@ -93,6 +96,8 @@ export default function(parser) {

// Subscripts to a bang call must appear at the arg indent level
pp.shouldUnwindBangSubscript = function() {
return this.isLineBreak() && (this.state.indentLevel <= this.state.bangUnwindLevel);
return this.isLineBreak() &&
(this.state.bangBlockLevel == this.state.nestedBlockLevel) &&
(this.state.indentLevel <= this.state.bangUnwindLevel);
};
}
20 changes: 15 additions & 5 deletions src/plugins/lightscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ pp.rethrowObjParseError = function(objParseResult, blockParseError) {
pp.parseInlineWhiteBlock = function(node) {
if (this.state.type.startsExpr) return this.parseMaybeAssign();
// oneline statement case
this.state.nestedBlockLevel++;
node.body = [this.parseStatement(true)];
this.state.nestedBlockLevel--;
node.directives = [];
this.addExtra(node, "curly", false);
return this.finishNode(node, "BlockStatement");
Expand All @@ -198,15 +200,20 @@ pp.parseMultilineWhiteBlock = function(node, indentLevel) {
if (this.match(tt.braceL) && this.hasPlugin("objectBlockAmbiguity_preferObject")) {
objParseResult = this.tryParseObjectWhiteBlock(node, indentLevel);
if (objParseResult[0]) return objParseResult[0];
}

try {
try {
this.parseBlockBody(node, false, false, indentLevel);
if (!node.body.length) {
this.unexpected(node.start, "Expected an Indent or Statement");
}
} catch (err) {
this.rethrowObjParseError(objParseResult, err);
}
} else {
this.parseBlockBody(node, false, false, indentLevel);
if (!node.body.length) {
this.unexpected(node.start, "Expected an Indent or Statement");
}
} catch (err) {
this.rethrowObjParseError(objParseResult, err);
}

this.addExtra(node, "curly", false);
Expand All @@ -230,7 +237,10 @@ pp.parseWhiteBlock = function (isExpression?) {
if (objParseResult[0]) return objParseResult[0];
}
try {
return this.parseStatement(false);
this.state.nestedBlockLevel++;
const stmt = this.parseStatement(false);
this.state.nestedBlockLevel--;
return stmt;
} catch (err) {
this.rethrowObjParseError(objParseResult, err);
}
Expand Down
50 changes: 50 additions & 0 deletions src/plugins/pipeCall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Parser from "../parser";
import { types as tt, TokenType } from "../tokenizer/types";
const pp = Parser.prototype;

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

tt.pipeCall = new TokenType("|>");

pp.parsePipeCall = function(node, left) {
if (this.state.value === "|>") {
return this.parseRightPointingPipeCall(node, left);
} else {
return this.parseLeftPointingPipeCall(node, left);
}
};

pp.parseRightPointingPipeCall = function(node, left) {
this.next();

node.left = left;

// To get left-associative parsing for pipe calls, we can't let the RHS
// of a pipe call subscript into another pipe call.
// Thus we use a state flag to prevent deep parsing of pipe calls
// Hackish but avoids changing the core args of parseSubscripts
if (this.match(tt.parenL) || this.match(tt.name)) {
this.state.potentialArrowAt = this.state.start;
}
const right = this.parseExprAtom();
this.state.noPipeSubscripts = true;
node.right = this.parseSubscripts(right, this.state.start, this.state.startLoc);

return this.finishNode(node, "PipeCallExpression");
};

pp.parseLeftPointingPipeCall = function(node, left) {
this.next();

node.left = left;
if (this.match(tt.parenL) || this.match(tt.name)) {
this.state.potentialArrowAt = this.state.start;
}
node.right = this.parseSubscripts(this.parseExprAtom(), this.state.start, this.state.startLoc);
node.reversed = true;

return this.finishNode(node, "PipeCallExpression");
};
}
51 changes: 51 additions & 0 deletions src/plugins/syntacticPlaceholder.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
9 changes: 9 additions & 0 deletions src/registerPlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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 pipeCallPlugin from "./plugins/pipeCall";
import { matchCoreSyntax, match } from "./plugins/match";

function noncePlugin() {}
Expand Down Expand Up @@ -79,4 +81,11 @@ 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);

// |> infix operator for piped function calls
registerPlugin("pipeCall", pipeCallPlugin);
}
9 changes: 9 additions & 0 deletions src/tokenizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ export default class Tokenizer {
if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2);
if (next === 61) return this.finishOp(tt.assign, 2);
if (code === 124 && next === 125 && this.hasPlugin("flow")) return this.finishOp(tt.braceBarR, 2);
if (code === 124 && next === 62 && this.hasPlugin("pipeCall")) {
this.state.pos += 2;
return this.finishToken(tt.pipeCall, "|>");
}
return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1);
}

Expand Down Expand Up @@ -433,6 +437,11 @@ export default class Tokenizer {
size = 2;
}

if (code === 60 && next === 124 && this.hasPlugin("pipeCall")) {
this.state.pos += 2;
return this.finishToken(tt.pipeCall, "<|");
}

return this.finishOp(tt.relational, size);
}

Expand Down

0 comments on commit 2376b75

Please sign in to comment.