syndicate-js/src/parser.js

226 lines
7.6 KiB
JavaScript

//---------------------------------------------------------------------------
// @syndicate-lang/syntax, a translator of Syndicate extensions to JS.
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//---------------------------------------------------------------------------
import { _original_Parser, tokTypes as tt } from "@babel/parser";
export default class SyndicateParser extends _original_Parser {
// Overrides ExpressionParser.parseMaybeAssign
parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos) {
let previousError = null;
if (this.isContextual("activate")) {
let result = this.withBacktracking(
() => {
this.next();
const node = this.startNode();
node.moduleExpr = this.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos);
return this.finishNode(node, "ActivationExpression");
},
(err) => {
previousError = err;
return null;
});
if (result) return result;
}
try {
return super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos);
} catch (err) {
if (err instanceof SyntaxError && previousError && previousError.pos >= err.pos) {
throw previousError;
} else {
throw err;
}
}
}
// Overrides StatementParser.parseStatementContent
parseStatementContent(declaration, topLevel) {
let previousError = null;
switch (this.state.type) {
case tt.bitShift:
if (this.hasPlugin("syndicate") && (this.state.value === "<<")) {
let result = this.withBacktracking(
() => {
this.next();
const node = this.startNode();
node.body = this.parseExpression();
this.semicolon();
return this.finishNode(node, "MessageSendStatement");
},
(err) => { previousError = err; return null; });
if (result) return result;
}
break;
case tt.name:
if (this.hasPlugin("syndicate")) {
let result = this.withBacktracking(
() => {
if (this.isContextual("field")) {
this.next();
const node = this.startNode();
node.member = this.parseExprSubscripts();
if (node.member.type !== "MemberExpression") {
this.raise(node.start, "Field declaration requires MemberExpression");
}
if (this.eat(tt.eq)) {
node.init = this.parseExpression();
}
this.semicolon();
return this.finishNode(node, "FieldDeclarationStatement");
}
if (this.isContextual("spawn")) {
this.next();
const node = this.startNode();
if (this.isContextual("named")) {
this.next();
node.name = this.parseExpression();
}
node.body = this.parseStatement();
return this.finishNode(node, "SpawnStatement");
}
if (this.isContextual("assert")) {
this.next();
const node = this.startNode();
node.template = this.parseExpression();
if (this.eatContextual("when")) {
this.expect(tt.parenL);
node.test = this.parseExpression();
this.expect(tt.parenR);
}
this.semicolon();
return this.finishNode(node, "AssertionEndpointStatement");
}
if (this.isContextual("dataflow")) {
this.next();
const node = this.startNode();
node.body = this.parseStatement();
return this.finishNode(node, "DataflowStatement");
}
if (this.isContextual("stop")) {
this.next();
return this.parseEventHandlerEndpoint(true, false);
}
if (this.isContextual("on")) {
return this.parseEventHandlerEndpoint(false, true);
}
if (this.isContextual("assertion") || this.isContextual("message")) {
const node = this.startNode();
node.expectedUse = this.state.value;
this.next();
this.eatContextual("type");
if (!this.match(tt.name)) { this.unexpected(null, tt.name); }
node.id = this.parseIdentifier();
this.parseFunctionParams(node); // eww
node.formals = node.params;
delete node.params; // eww
if (this.eat(tt.eq)) {
if (!this.match(tt.string)) { this.unexpected(null, tt.string); }
node.wireName = this.parseLiteral(this.state.value, "StringLiteral");
}
this.semicolon();
return this.finishNode(node, "SyndicateTypeDefinition");
}
return null;
},
(err) => {
previousError = err;
return null;
});
if (result) return result;
}
}
try {
return super.parseStatementContent(declaration, topLevel);
} catch (err) {
if (err instanceof SyntaxError && previousError && previousError.pos >= err.pos) {
throw previousError;
} else {
throw err;
}
}
}
withBacktracking(alt1, alt2) {
const state = this.state.clone();
try {
return alt1();
} catch (err) {
if (err instanceof SyntaxError) {
this.state = state;
return alt2(err);
} else {
throw err;
}
}
}
parseEventHandlerEndpoint(terminal, pseudoEventsAllowed) {
this.expectContextual("on");
const node = this.startNode();
if (this.match(tt.parenL)) {
node.terminal = terminal;
node.triggerType = "dataflow";
node.pattern = this.parseExpression();
node.body = this.parseStatement();
return this.finishNode(node, "EventHandlerEndpoint");
}
if (!this.match(tt.name)) {
this.unexpected(
null,
"Expected endpoint trigger type ("
+ (pseudoEventsAllowed ? "start/stop/" : "")
+ "asserted/retracted/message/(...))");
}
switch (this.state.value) {
case "start":
case "stop":
node.triggerType = this.state.value;
this.next();
node.body = this.parseStatement();
return this.finishNode(node, "PseudoEventHandler");
case "asserted":
case "retracted":
case "message":
node.triggerType = this.state.value;
this.next();
node.terminal = terminal;
node.pattern = this.parseExpression();
node.body = this.parseStatement();
return this.finishNode(node, "EventHandlerEndpoint");
default:
this.unexpected(null, "Unknown event handler trigger type");
}
}
}