Implement optional prefix | and &, fix trailing comma bug (#446)

This commit is contained in:
Brian Terlson 2021-04-14 09:57:08 -07:00 коммит произвёл GitHub
Родитель 7eb3b821a7
Коммит bd3934b7fb
3 изменённых файлов: 78 добавлений и 59 удалений

Просмотреть файл

@ -254,11 +254,11 @@ Expression :
UnionExpressionOrHigher :
IntersectionExpressionOrHigher
UnionExpressionOrHigher `|` IntersectionExpressionOrHigher
`|`? UnionExpressionOrHigher `|` IntersectionExpressionOrHigher
IntersectionExpressionOrHigher :
ArrayExpressionOrHigher
IntersectionExpressionOrHigher `&` ArrayExpressionOrHigher
`&`? IntersectionExpressionOrHigher `&` ArrayExpressionOrHigher
ArrayExpressionOrHigher :
PrimaryExpression

Просмотреть файл

@ -24,11 +24,53 @@ export function parse(code: string | Types.SourceFile) {
inScopeNamespaces: [],
};
script.statements = parseADLScriptList();
script.end = scanner.position;
return script;
}
function parseADLScriptList(): Types.Statement[] {
const stmts: Types.Statement[] = [];
let seenBlocklessNs = false;
let seenDecl = false;
while (!scanner.eof()) {
const item = parseADLScriptItem();
const decorators = parseDecoratorList();
const tok = token();
let item: Types.Statement;
switch (tok) {
case Token.ImportKeyword:
if (decorators.length > 0) {
error("Cannot decorate an import statement");
}
item = parseImportStatement();
break;
case Token.ModelKeyword:
item = parseModelStatement(decorators);
break;
case Token.NamespaceKeyword:
item = parseNamespaceStatement(decorators);
break;
case Token.OpKeyword:
item = parseOperationStatement(decorators);
break;
case Token.UsingKeyword:
if (decorators.length > 0) {
error("Cannot decorate using statements");
}
item = parseUsingStatement();
break;
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
}
// no need to put empty statement nodes in the tree for now
// since we aren't trying to emit ADL
parseExpected(Token.Semicolon);
continue;
default:
throw error(`Expected statement, but found ${Token[tok]}`);
}
if (isBlocklessNamespace(item)) {
if (seenBlocklessNs) {
throw error("Cannot use multiple blockless namespaces");
@ -44,71 +86,41 @@ export function parse(code: string | Types.SourceFile) {
} else {
seenDecl = true;
}
script.statements.push(item);
stmts.push(item);
}
script.end = scanner.position;
return script;
return stmts;
}
function parseADLScriptItem(): Types.Statement {
while (true) {
const decorators = parseDecoratorList();
const tok = token();
function parseStatementList(): Types.Statement[] {
const stmts: Types.Statement[] = [];
switch (tok) {
case Token.ImportKeyword:
if (decorators.length > 0) {
error("Cannot decorate an import statement");
}
return parseImportStatement();
case Token.ModelKeyword:
return parseModelStatement(decorators);
case Token.NamespaceKeyword:
return parseNamespaceStatement(decorators);
case Token.OpKeyword:
return parseOperationStatement(decorators);
case Token.UsingKeyword:
if (decorators.length > 0) {
error("Cannot decorate using statements");
}
return parseUsingStatement();
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
}
// no need to put empty statement nodes in the tree for now
// since we aren't trying to emit ADL
parseExpected(Token.Semicolon);
continue;
}
throw error(`Expected statement, but found ${Token[tok]}`);
}
}
function parseStatement(): Types.Statement {
while (true) {
while (token() !== Token.CloseBrace) {
const decorators = parseDecoratorList();
const tok = token();
switch (tok) {
case Token.ModelKeyword:
return parseModelStatement(decorators);
stmts.push(parseModelStatement(decorators));
break;
case Token.NamespaceKeyword:
const ns = parseNamespaceStatement(decorators);
if (!Array.isArray(ns.statements)) {
throw error(`Blockless namespace can only be top-level`);
}
return ns;
stmts.push(ns);
break;
case Token.OpKeyword:
return parseOperationStatement(decorators);
stmts.push(parseOperationStatement(decorators));
break;
case Token.UsingKeyword:
if (decorators.length > 0) {
error("Cannot decorate using statements");
}
return parseUsingStatement();
stmts.push(parseUsingStatement());
break;
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
@ -117,10 +129,12 @@ export function parse(code: string | Types.SourceFile) {
// since we aren't trying to emit ADL
parseExpected(Token.Semicolon);
continue;
default:
throw error(`Expected statement, but found ${Token[tok]}`);
}
throw error(`Expected statement, but found ${Token[tok]}`);
}
return stmts;
}
function parseDecoratorList() {
@ -152,11 +166,7 @@ export function parse(code: string | Types.SourceFile) {
let statements: Types.Statement[] | undefined;
if (nextTok === Token.OpenBrace) {
statements = [];
while (token() !== Token.CloseBrace) {
statements.push(parseStatement());
}
statements = parseStatementList();
parseExpected(Token.CloseBrace);
}
@ -401,6 +411,7 @@ export function parse(code: string | Types.SourceFile) {
function parseUnionExpressionOrHigher(): Types.Expression {
const pos = tokenPos();
parseOptional(Token.Bar);
let node: Types.Expression = parseIntersectionExpressionOrHigher();
if (token() !== Token.Bar) {
@ -427,6 +438,7 @@ export function parse(code: string | Types.SourceFile) {
function parseIntersectionExpressionOrHigher(): Types.Expression {
const pos = tokenPos();
parseOptional(Token.Ampersand);
let node: Types.Expression = parseArrayExpressionOrHigher();
if (token() !== Token.Ampersand) {

Просмотреть файл

@ -16,7 +16,6 @@ describe("syntax", () => {
describe("model statements", () => {
parseEach([
"model Car { };",
`@foo()
model Car { };`,
@ -119,7 +118,7 @@ describe("syntax", () => {
});
describe("union expressions", () => {
parseEach(["model A { foo: B | C }", "model A { foo: B | C & D }"]);
parseEach(["model A { foo: B | C }", "model A { foo: B | C & D }", "model A { foo: | B | C }"]);
});
describe("template instantiations", () => {
@ -127,7 +126,7 @@ describe("syntax", () => {
});
describe("intersection expressions", () => {
parseEach(["model A { foo: B & C }"]);
parseEach(["model A { foo: B & C }", "model A { foo: & B & C }"]);
});
describe("parenthesized expressions", () => {
@ -195,12 +194,20 @@ describe("syntax", () => {
`,
]);
});
describe("empty statements", () => {
parseEach([`;;;;`, `namespace Foo { model Car { }; };`, `model Car { };;;;`]);
});
});
function parseEach(cases: string[]) {
for (const code of cases) {
it("parses `" + shorten(code) + "`", () => {
dumpAST(parse(code));
try {
dumpAST(parse(code));
} catch (e) {
throw e.diagnostics;
}
});
}
}