implements PR-19.3 parser and ast globals lifecycle markers
This commit is contained in:
parent
b190227b53
commit
738eea71ee
@ -113,18 +113,26 @@ public final class PbsAst {
|
||||
ErrorDecl,
|
||||
EnumDecl,
|
||||
CallbackDecl,
|
||||
GlobalDecl,
|
||||
ConstDecl,
|
||||
ImplementsDecl,
|
||||
InvalidDecl {
|
||||
Span span();
|
||||
}
|
||||
|
||||
public enum LifecycleMarker {
|
||||
NONE,
|
||||
INIT,
|
||||
FRAME
|
||||
}
|
||||
|
||||
public record FunctionDecl(
|
||||
String name,
|
||||
ReadOnlyList<Parameter> parameters,
|
||||
ReturnKind returnKind,
|
||||
TypeRef returnType,
|
||||
TypeRef resultErrorType,
|
||||
LifecycleMarker lifecycleMarker,
|
||||
Block body,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
@ -185,6 +193,13 @@ public final class PbsAst {
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record GlobalDecl(
|
||||
String name,
|
||||
TypeRef explicitType,
|
||||
Expression initializer,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
public record ConstDecl(
|
||||
String name,
|
||||
TypeRef explicitType,
|
||||
@ -637,6 +652,7 @@ public final class PbsAst {
|
||||
BarrelErrorItem,
|
||||
BarrelEnumItem,
|
||||
BarrelServiceItem,
|
||||
BarrelGlobalItem,
|
||||
BarrelConstItem,
|
||||
BarrelCallbackItem {
|
||||
Span span();
|
||||
@ -693,6 +709,12 @@ public final class PbsAst {
|
||||
Span span) implements BarrelItem {
|
||||
}
|
||||
|
||||
public record BarrelGlobalItem(
|
||||
Visibility visibility,
|
||||
String name,
|
||||
Span span) implements BarrelItem {
|
||||
}
|
||||
|
||||
public record BarrelConstItem(
|
||||
Visibility visibility,
|
||||
String name,
|
||||
|
||||
@ -316,6 +316,7 @@ public final class PbsLexer {
|
||||
map.put("ctor", PbsTokenKind.CTOR);
|
||||
map.put("let", PbsTokenKind.LET);
|
||||
map.put("const", PbsTokenKind.CONST);
|
||||
map.put("global", PbsTokenKind.GLOBAL);
|
||||
map.put("declare", PbsTokenKind.DECLARE);
|
||||
map.put("struct", PbsTokenKind.STRUCT);
|
||||
map.put("contract", PbsTokenKind.CONTRACT);
|
||||
|
||||
@ -39,6 +39,7 @@ public enum PbsTokenKind {
|
||||
DECLARE,
|
||||
LET,
|
||||
CONST,
|
||||
GLOBAL,
|
||||
STRUCT,
|
||||
CONTRACT,
|
||||
ERROR,
|
||||
|
||||
@ -274,6 +274,8 @@ public final class PbsModuleVisibilityValidator {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.ENUM, enumDecl.name(), enumDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.SERVICE, serviceDecl.name(), serviceDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.GLOBAL, globalDecl.name(), globalDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONST, constDecl.name(), constDecl.span(), nameTable);
|
||||
} else if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||
@ -356,6 +358,9 @@ public final class PbsModuleVisibilityValidator {
|
||||
if (item instanceof PbsAst.BarrelServiceItem) {
|
||||
return NonFunctionKind.SERVICE;
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelGlobalItem) {
|
||||
return NonFunctionKind.GLOBAL;
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelConstItem) {
|
||||
return NonFunctionKind.CONST;
|
||||
}
|
||||
@ -384,6 +389,9 @@ public final class PbsModuleVisibilityValidator {
|
||||
if (item instanceof PbsAst.BarrelServiceItem serviceItem) {
|
||||
return serviceItem.name();
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelGlobalItem globalItem) {
|
||||
return globalItem.name();
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelConstItem constItem) {
|
||||
return constItem.name();
|
||||
}
|
||||
@ -412,6 +420,9 @@ public final class PbsModuleVisibilityValidator {
|
||||
if (item instanceof PbsAst.BarrelServiceItem serviceItem) {
|
||||
return serviceItem.visibility();
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelGlobalItem globalItem) {
|
||||
return globalItem.visibility();
|
||||
}
|
||||
if (item instanceof PbsAst.BarrelConstItem constItem) {
|
||||
return constItem.visibility();
|
||||
}
|
||||
@ -469,6 +480,7 @@ public final class PbsModuleVisibilityValidator {
|
||||
ERROR,
|
||||
ENUM,
|
||||
SERVICE,
|
||||
GLOBAL,
|
||||
CONST,
|
||||
CALLBACK
|
||||
}
|
||||
|
||||
@ -98,6 +98,9 @@ public final class PbsBarrelParser {
|
||||
if (cursor.match(PbsTokenKind.SERVICE)) {
|
||||
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.SERVICE);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.GLOBAL)) {
|
||||
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.GLOBAL);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CONST)) {
|
||||
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.CONST);
|
||||
}
|
||||
@ -126,6 +129,7 @@ public final class PbsBarrelParser {
|
||||
case ERROR -> new PbsAst.BarrelErrorItem(visibility, name.lexeme(), itemSpan);
|
||||
case ENUM -> new PbsAst.BarrelEnumItem(visibility, name.lexeme(), itemSpan);
|
||||
case SERVICE -> new PbsAst.BarrelServiceItem(visibility, name.lexeme(), itemSpan);
|
||||
case GLOBAL -> new PbsAst.BarrelGlobalItem(visibility, name.lexeme(), itemSpan);
|
||||
case CONST -> new PbsAst.BarrelConstItem(visibility, name.lexeme(), itemSpan);
|
||||
case CALLBACK -> new PbsAst.BarrelCallbackItem(visibility, name.lexeme(), itemSpan);
|
||||
default -> null;
|
||||
|
||||
@ -131,6 +131,10 @@ final class PbsDeclarationParser {
|
||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "callback declarations");
|
||||
return parseCallbackDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.GLOBAL)) {
|
||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "global declarations");
|
||||
return parseGlobalDeclaration(declareToken);
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CONST)) {
|
||||
return parseConstDeclaration(declareToken, pendingAttributes);
|
||||
}
|
||||
@ -166,8 +170,10 @@ final class PbsDeclarationParser {
|
||||
return new PbsAst.InvalidDecl("invalid declare form", span(declareToken.start(), token.end()));
|
||||
}
|
||||
|
||||
PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) {
|
||||
return parseFunctionLike(fnToken);
|
||||
PbsAst.FunctionDecl parseFunction(
|
||||
final PbsToken fnToken,
|
||||
final ReadOnlyList<PbsAst.Attribute> pendingAttributes) {
|
||||
return parseFunctionLike(fnToken, pendingAttributes);
|
||||
}
|
||||
|
||||
PbsAst.ImplementsDecl parseImplementsDeclaration(final PbsToken implementsToken) {
|
||||
@ -188,7 +194,7 @@ final class PbsDeclarationParser {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
|
||||
}
|
||||
end = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end implements body").end();
|
||||
} else {
|
||||
@ -377,7 +383,7 @@ final class PbsDeclarationParser {
|
||||
final var ctors = new ArrayList<PbsAst.CtorDecl>();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (cursor.match(PbsTokenKind.FN)) {
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
|
||||
continue;
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CTOR)) {
|
||||
@ -441,7 +447,7 @@ final class PbsDeclarationParser {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end service body");
|
||||
return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), span(declareToken.start(), rightBrace.end()));
|
||||
@ -519,7 +525,10 @@ final class PbsDeclarationParser {
|
||||
return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), Math.max(rightParen.end(), semicolon.end())));
|
||||
}
|
||||
|
||||
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) {
|
||||
private PbsAst.FunctionDecl parseFunctionLike(
|
||||
final PbsToken fnToken,
|
||||
final ReadOnlyList<PbsAst.Attribute> pendingAttributes) {
|
||||
final var lifecycleMarker = validateAndResolveLifecycleMarker(pendingAttributes);
|
||||
final var name = consumeCallableName();
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||
final var parameters = typeParser.parseParametersUntilRightParen();
|
||||
@ -534,6 +543,7 @@ final class PbsDeclarationParser {
|
||||
returnSpec.kind(),
|
||||
returnSpec.returnType(),
|
||||
returnSpec.resultErrorType(),
|
||||
lifecycleMarker,
|
||||
body,
|
||||
span(fnToken.start(), body.span().getEnd()));
|
||||
}
|
||||
@ -614,6 +624,20 @@ final class PbsDeclarationParser {
|
||||
span(declareToken.start(), semicolon.end()));
|
||||
}
|
||||
|
||||
private PbsAst.GlobalDecl parseGlobalDeclaration(final PbsToken declareToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected global name");
|
||||
consume(PbsTokenKind.COLON, "Expected ':' after global name");
|
||||
final var explicitType = typeParser.parseTypeRef();
|
||||
consume(PbsTokenKind.EQUAL, "Expected '=' after global type annotation");
|
||||
final var initializer = exprParser.parseExpression();
|
||||
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after global declaration");
|
||||
return new PbsAst.GlobalDecl(
|
||||
name.lexeme(),
|
||||
explicitType,
|
||||
initializer,
|
||||
span(declareToken.start(), semicolon.end()));
|
||||
}
|
||||
|
||||
private PbsAst.Block parseBlock() {
|
||||
return blockParserDelegate.parse();
|
||||
}
|
||||
@ -646,6 +670,43 @@ final class PbsDeclarationParser {
|
||||
return context.parseMode() == PbsParser.ParseMode.ORDINARY;
|
||||
}
|
||||
|
||||
private PbsAst.LifecycleMarker validateAndResolveLifecycleMarker(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||
if (attributes.isEmpty()) {
|
||||
return PbsAst.LifecycleMarker.NONE;
|
||||
}
|
||||
|
||||
if (!isOrdinaryMode()) {
|
||||
reportAttributesNotAllowed(attributes, "Attributes are not allowed before top-level functions");
|
||||
return PbsAst.LifecycleMarker.NONE;
|
||||
}
|
||||
|
||||
PbsAst.LifecycleMarker marker = PbsAst.LifecycleMarker.NONE;
|
||||
for (final var attribute : attributes) {
|
||||
final PbsAst.LifecycleMarker nextMarker;
|
||||
if ("Init".equals(attribute.name())) {
|
||||
nextMarker = PbsAst.LifecycleMarker.INIT;
|
||||
} else if ("Frame".equals(attribute.name())) {
|
||||
nextMarker = PbsAst.LifecycleMarker.FRAME;
|
||||
} else {
|
||||
reportAttributesNotAllowed(attributes, "Only marker attributes [Init] and [Frame] are allowed before top-level functions");
|
||||
return PbsAst.LifecycleMarker.NONE;
|
||||
}
|
||||
|
||||
if (!attribute.arguments().isEmpty()) {
|
||||
reportAttributesNotAllowed(attributes, "Lifecycle markers do not accept arguments");
|
||||
return PbsAst.LifecycleMarker.NONE;
|
||||
}
|
||||
|
||||
if (marker != PbsAst.LifecycleMarker.NONE && marker != nextMarker) {
|
||||
reportAttributesNotAllowed(attributes, "Top-level functions cannot combine [Init] and [Frame]");
|
||||
return PbsAst.LifecycleMarker.NONE;
|
||||
}
|
||||
marker = nextMarker;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
private PbsToken consume(final PbsTokenKind kind, final String message) {
|
||||
if (cursor.check(kind)) {
|
||||
return cursor.advance();
|
||||
|
||||
@ -30,12 +30,6 @@ final class PbsTopLevelParser {
|
||||
var pendingAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
||||
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
||||
pendingAttributes = attributeParser.parseAttributeList();
|
||||
if (isOrdinaryMode()) {
|
||||
reportAttributesNotAllowed(
|
||||
pendingAttributes,
|
||||
"Attributes are not allowed in ordinary .pbs source modules");
|
||||
pendingAttributes = ReadOnlyList.empty();
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.IMPORT)) {
|
||||
@ -45,8 +39,7 @@ final class PbsTopLevelParser {
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.FN)) {
|
||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "top-level functions");
|
||||
topDecls.add(declarationParser.parseFunction(cursor.previous()));
|
||||
topDecls.add(declarationParser.parseFunction(cursor.previous(), pendingAttributes));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ class PbsBarrelParserTest {
|
||||
pub fn sum(a: int, b: int) -> int;
|
||||
mod fn run(state: optional int) -> result<Err> (value: int, meta: float);
|
||||
pub struct Vec2;
|
||||
mod global Palette;
|
||||
pub callback Tick;
|
||||
""";
|
||||
|
||||
@ -25,7 +26,7 @@ class PbsBarrelParserTest {
|
||||
final var barrel = PbsBarrelParser.parse(tokens, fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Valid barrel should parse without diagnostics");
|
||||
assertEquals(4, barrel.items().size());
|
||||
assertEquals(5, barrel.items().size());
|
||||
|
||||
final var sum = assertInstanceOf(PbsAst.BarrelFunctionItem.class, barrel.items().get(0));
|
||||
assertEquals("sum", sum.name());
|
||||
@ -39,7 +40,8 @@ class PbsBarrelParserTest {
|
||||
assertEquals(PbsAst.TypeRefKind.NAMED_TUPLE, run.returnType().kind());
|
||||
|
||||
assertInstanceOf(PbsAst.BarrelStructItem.class, barrel.items().get(2));
|
||||
assertInstanceOf(PbsAst.BarrelCallbackItem.class, barrel.items().get(3));
|
||||
assertInstanceOf(PbsAst.BarrelGlobalItem.class, barrel.items().get(3));
|
||||
assertInstanceOf(PbsAst.BarrelCallbackItem.class, barrel.items().get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -75,12 +75,17 @@ class PbsParserTest {
|
||||
void shouldParseDeclarationFamiliesWithShape() {
|
||||
final var source = """
|
||||
fn f() -> int { return 1; }
|
||||
[Init]
|
||||
fn init() -> void { return; }
|
||||
[Frame]
|
||||
fn frame() -> void { return; }
|
||||
declare struct S(pub mut x: int, y: optional int) { fn run() -> void { return; } ctor make(x: int) { return; } }
|
||||
declare contract C { fn run(x: int) -> int; }
|
||||
declare service Game { fn tick(x: int) -> int { return x; } }
|
||||
declare error Err { Fail; Crash; }
|
||||
declare enum Mode(Idle = 0, Run = 1);
|
||||
declare callback TickCb(x: int) -> result<Err> int;
|
||||
declare global STATE: int = 10;
|
||||
declare const LIMIT: int = 10;
|
||||
implements C for S using s { fn run(x: int) -> int { return x; } }
|
||||
""";
|
||||
@ -91,32 +96,40 @@ class PbsParserTest {
|
||||
final PbsAst.File ast = PbsParser.parse(tokens, fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Parser should represent declaration families with expected shapes");
|
||||
assertEquals(9, ast.topDecls().size());
|
||||
assertEquals(12, ast.topDecls().size());
|
||||
|
||||
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
||||
final var initDecl = assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(1));
|
||||
assertEquals(PbsAst.LifecycleMarker.INIT, initDecl.lifecycleMarker());
|
||||
|
||||
final var frameDecl = assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(2));
|
||||
assertEquals(PbsAst.LifecycleMarker.FRAME, frameDecl.lifecycleMarker());
|
||||
|
||||
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(3));
|
||||
assertEquals(2, structDecl.fields().size());
|
||||
assertTrue(structDecl.hasBody());
|
||||
assertEquals(1, structDecl.methods().size());
|
||||
assertEquals(1, structDecl.ctors().size());
|
||||
|
||||
final var contractDecl = assertInstanceOf(PbsAst.ContractDecl.class, ast.topDecls().get(2));
|
||||
final var contractDecl = assertInstanceOf(PbsAst.ContractDecl.class, ast.topDecls().get(4));
|
||||
assertEquals(1, contractDecl.signatures().size());
|
||||
|
||||
final var serviceDecl = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().get(3));
|
||||
final var serviceDecl = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().get(5));
|
||||
assertEquals(1, serviceDecl.methods().size());
|
||||
|
||||
final var errorDecl = assertInstanceOf(PbsAst.ErrorDecl.class, ast.topDecls().get(4));
|
||||
final var errorDecl = assertInstanceOf(PbsAst.ErrorDecl.class, ast.topDecls().get(6));
|
||||
assertEquals(2, errorDecl.cases().size());
|
||||
|
||||
final var enumDecl = assertInstanceOf(PbsAst.EnumDecl.class, ast.topDecls().get(5));
|
||||
final var enumDecl = assertInstanceOf(PbsAst.EnumDecl.class, ast.topDecls().get(7));
|
||||
assertEquals(2, enumDecl.cases().size());
|
||||
|
||||
final var callbackDecl = assertInstanceOf(PbsAst.CallbackDecl.class, ast.topDecls().get(6));
|
||||
final var callbackDecl = assertInstanceOf(PbsAst.CallbackDecl.class, ast.topDecls().get(8));
|
||||
assertEquals(PbsAst.ReturnKind.RESULT, callbackDecl.returnKind());
|
||||
assertEquals("Err", callbackDecl.resultErrorType().name());
|
||||
|
||||
assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(7));
|
||||
final var implementsDecl = assertInstanceOf(PbsAst.ImplementsDecl.class, ast.topDecls().get(8));
|
||||
final var globalDecl = assertInstanceOf(PbsAst.GlobalDecl.class, ast.topDecls().get(9));
|
||||
assertEquals("STATE", globalDecl.name());
|
||||
assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(10));
|
||||
final var implementsDecl = assertInstanceOf(PbsAst.ImplementsDecl.class, ast.topDecls().get(11));
|
||||
assertEquals(1, implementsDecl.methods().size());
|
||||
}
|
||||
|
||||
@ -139,6 +152,26 @@ class PbsParserTest {
|
||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseLifecycleMarkersOnlyOnTopLevelFunctions() {
|
||||
final var source = """
|
||||
[Init]
|
||||
fn init() -> void { return; }
|
||||
[Frame]
|
||||
fn frame() -> void { return; }
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
final PbsAst.File ast = PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Lifecycle markers should be accepted on top-level functions");
|
||||
final var initDecl = assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(0));
|
||||
final var frameDecl = assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(1));
|
||||
assertEquals(PbsAst.LifecycleMarker.INIT, initDecl.lifecycleMarker());
|
||||
assertEquals(PbsAst.LifecycleMarker.FRAME, frameDecl.lifecycleMarker());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseServiceAndMemberNamesUsingErrorKeyword() {
|
||||
final var source = """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user