Skip to content
14 changes: 13 additions & 1 deletion src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,14 @@ pp.parseExprOps = function (noIn, refShorthandDefaultPos) {
pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) {
// correct ASI failures.
if (this.hasPlugin("lightscript") && this.isLineBreak()) {

// if it's a newline followed by a unary +/-, bail so it can be parsed separately.
if (this.match(tt.plusMin) && !this.isNextCharWhitespace()) {
return left;
}
// for match/case
if (this.match(tt.bitwiseOR)) {
return left;
}
}

if (this.hasPlugin("lightscript") && this.isBitwiseOp()) {
Expand Down Expand Up @@ -716,6 +719,12 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
return this.parseIfExpression(node);
}

case tt._match:
if (this.hasPlugin("lightscript")) {
node = this.startNode();
return this.parseMatchExpression(node);
}

case tt.arrow:
if (this.hasPlugin("lightscript")) {
node = this.startNode();
Expand Down Expand Up @@ -1151,6 +1160,9 @@ pp.parsePropertyName = function (prop) {
// Initialize empty function node.

pp.initFunction = function (node, isAsync) {
if (this.hasPlugin("lightscript") && this.state.inMatchCaseTest) {
this.unexpected(node.start, "Cannot match on functions.");
}
node.id = null;
node.generator = false;
node.expression = false;
Expand Down
3 changes: 3 additions & 0 deletions src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ pp.parseStatement = function (declaration, topLevel) {
}
return starttype === tt._import ? this.parseImport(node) : this.parseExport(node);

case tt._match:
if (this.hasPlugin("lightscript")) return this.parseMatchStatement(node);

case tt.name:
if (this.state.value === "async") {
// peek ahead and see if next token is a function
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/jsx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ export default function(instance) {
return function(code) {
if (this.state.inPropertyName) return inner.call(this, code);

// don't allow jsx inside match case tests
if (this.hasPlugin("lightscript") && this.state.inMatchCaseTest) {
return inner.call(this, code);
}

const context = this.curContext();

if (this.hasPlugin("lightscript") && code === 60) {
Expand Down
112 changes: 110 additions & 2 deletions src/plugins/lightscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,10 @@ pp.expectParenFreeBlockStart = function (node) {
// if true: blah
// if true { blah }
// if (true) blah
// match (foo) as bar:
if (node && node.extra && node.extra.hasParens) {
this.expect(tt.parenR);
} else if (!(this.match(tt.colon) || this.match(tt.braceL))) {
} else if (!(this.match(tt.colon) || this.match(tt.braceL) || this.isContextual("as"))) {
this.unexpected(null, "Paren-free test expressions must be followed by braces or a colon.");
}
};
Expand Down Expand Up @@ -550,6 +551,112 @@ pp.isBitwiseOp = function () {
);
};

pp.parseMatchExpression = function (node) {
return this.parseMatch(node, true);
};

pp.parseMatchStatement = function (node) {
return this.parseMatch(node, false);
};

pp.parseMatch = function (node, isExpression) {
if (this.state.inMatchCaseTest) this.unexpected();
this.expect(tt._match);

node.discriminant = this.parseParenExpression();
if (this.eatContextual("as")) {
node.alias = this.parseIdentifier();
}

const isColon = this.match(tt.colon);
let isEnd;
if (isColon) {
const indentLevel = this.state.indentLevel;
this.next();
isEnd = () => this.state.indentLevel <= indentLevel || this.match(tt.eof);
} else {
this.expect(tt.braceL);
isEnd = () => this.eat(tt.braceR);
}

node.cases = [];
const caseIndentLevel = this.state.indentLevel;
let hasUsedElse = false;
while (!isEnd()) {
if (hasUsedElse) {
this.unexpected(null, "`else` must be last case.");
}
if (isColon && this.state.indentLevel !== caseIndentLevel) {
this.unexpected(null, "Mismatched indent.");
}

const matchCase = this.parseMatchCase(isExpression);
if (matchCase.test && matchCase.test.type === "MatchElse") {
hasUsedElse = true;
}
node.cases.push(matchCase);
}

if (!node.cases.length) {
this.unexpected(null, tt.bitwiseOR);
}

return this.finishNode(node, isExpression ? "MatchExpression" : "MatchStatement");
};

pp.parseMatchCase = function (isExpression) {
const node = this.startNode();

this.parseMatchCaseTest(node);

if (isExpression) {
// disallow return/continue/break, etc. c/p doExpression
const oldInFunction = this.state.inFunction;
const oldLabels = this.state.labels;
this.state.labels = [];
this.state.inFunction = false;

node.consequent = this.parseBlock(false);

this.state.inFunction = oldInFunction;
this.state.labels = oldLabels;
} else {
node.consequent = this.parseBlock(false);
}

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

pp.parseMatchCaseTest = function (node) {
// can't be nested so no need to read/restore old value
this.state.inMatchCaseTest = true;

this.expect(tt.bitwiseOR);
if (this.isLineBreak()) this.unexpected(this.state.lastTokEnd, "Illegal newline.");

if (this.match(tt._else)) {
const elseNode = this.startNode();
this.next();
node.test = this.finishNode(elseNode, "MatchElse");
} else if (this.eat(tt._with)) {
this.parseMatchCaseBinding(node);
} else {
node.test = this.parseExprOps();
}

if (this.eat(tt._with)) {
this.parseMatchCaseBinding(node);
}

this.state.inMatchCaseTest = false;
};

pp.parseMatchCaseBinding = function (node) {
if (node.binding) this.unexpected(this.state.lastTokStart, "Cannot destructure twice.");
if (!(this.match(tt.braceL) || this.match(tt.bracketL))) this.unexpected();
node.binding = this.parseBindingAtom();
};


export default function (instance) {

Expand All @@ -568,7 +675,8 @@ export default function (instance) {
// first, try paren-free style
try {
const val = this.parseExpression();
if (this.match(tt.braceL) || this.match(tt.colon)) {
// "as" for `match (foo) as bar:`, bit dirty to allow for all but not a problem
if (this.match(tt.braceL) || this.match(tt.colon) || this.isContextual("as")) {
if (val.extra && val.extra.parenthesized) {
delete val.extra.parenthesized;
delete val.extra.parenStart;
Expand Down
2 changes: 2 additions & 0 deletions src/tokenizer/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export default class State {
this.pos = this.lineStart = 0;
this.curLine = options.startLine;

// for lightscript
this.indentLevel = 0;
this.inMatchCaseTest = false;

this.type = tt.eof;
this.value = null;
Expand Down
1 change: 1 addition & 0 deletions src/tokenizer/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const keywords = {
"or": types.logicalOR,
"and": types.logicalAND,
"not": new KeywordTokenType("not", { beforeExpr, prefix, startsExpr }),
"match": new KeywordTokenType("match", { beforeExpr, startsExpr }),

"break": new KeywordTokenType("break"),
"case": new KeywordTokenType("case", { beforeExpr }),
Expand Down
2 changes: 1 addition & 1 deletion src/util/identifier.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions test/fixtures/lightscript/commaless/obj-pattern/actual.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
a
b: {
c
d
d = 1
e
}
e
} = f
f
...g
} = h
Loading