implements PR006.2
This commit is contained in:
parent
0cf2e3e099
commit
42392f3d02
@ -8,10 +8,12 @@ Este indice organiza uma sequencia de PRs atomicas para levar `frontends/pbs` ao
|
||||
4. `PR-004-pbs-parser-statements-and-control-flow.md`
|
||||
5. `PR-005-pbs-parser-expressions-optional-result-apply.md`
|
||||
6. `PR-006-pbs-barrel-and-module-visibility.md`
|
||||
7. `PR-007-pbs-static-semantics-declaration-validation.md`
|
||||
8. `PR-008-pbs-static-semantics-call-resolution-and-flow.md`
|
||||
9. `PR-009-pbs-diagnostics-contract-v1.md`
|
||||
10. `PR-010-pbs-irbackend-lowering-contract.md`
|
||||
11. `PR-011-pbs-gate-u-conformance-fixtures.md`
|
||||
7. `PR-006.2-pbs-parser-ast-syntax-hardening.md`
|
||||
8. `PR-006.3-pbs-syntax-completeness-and-module-hygiene.md`
|
||||
9. `PR-007-pbs-static-semantics-declaration-validation.md`
|
||||
10. `PR-008-pbs-static-semantics-call-resolution-and-flow.md`
|
||||
11. `PR-009-pbs-diagnostics-contract-v1.md`
|
||||
12. `PR-010-pbs-irbackend-lowering-contract.md`
|
||||
13. `PR-011-pbs-gate-u-conformance-fixtures.md`
|
||||
|
||||
Cada documento e auto contido e inclui: `Briefing`, `Target`, `Method`, `Acceptance Criteria` e `Tests`.
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
# PR-006.3 - PBS Syntax Completeness and Module Hygiene
|
||||
|
||||
## Briefing
|
||||
Depois do hardening sintatico principal, ainda restam lacunas de completude do contrato de sintaxe/modulo que afetam determinismo diagnostico e aderencia fina ao spec.
|
||||
Este PR fecha essas lacunas com foco em regras formais e higiene de modulo.
|
||||
|
||||
## Target
|
||||
- Specs:
|
||||
- `docs/pbs/specs/3. Core Syntax Specification.md` (secoes 5.1, 6.1.1, 8, 12)
|
||||
- `docs/pbs/specs/12. Diagnostics Specification.md` (phase = syntax/linking)
|
||||
- Codigo:
|
||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java`
|
||||
- `.../pbs/parser/PbsExprParser.java`
|
||||
- `.../pbs/lexer/PbsLexer.java`
|
||||
- `.../pbs/linking/PbsModuleVisibilityValidator.java`
|
||||
|
||||
## Method
|
||||
1. Atributos (`AttrList`) em `.pbs`:
|
||||
- introduzir parse minimo de atributo no frontend;
|
||||
- em modulo ordinario, rejeitar com diagnostico especifico e recuperacao estavel.
|
||||
2. Regras de modulo:
|
||||
- validar erro quando modulo possui `mod.barrel` mas zero arquivos `.pbs`.
|
||||
3. Ajustes de forma sintatica:
|
||||
- aceitar trailing comma em `StructFieldList`;
|
||||
- aplicar limites de aridade: tupla tipo (1..6) e tupla literal (2..6).
|
||||
4. Lexer/string:
|
||||
- diagnosticar escape de string invalido de forma deterministica (sem aceitar silenciosamente).
|
||||
|
||||
## Acceptance Criteria
|
||||
- Uso de atributos em modulo ordinario gera erro deterministico com span primario no atributo.
|
||||
- Modulo sem `.pbs` e com `mod.barrel` nao passa silenciosamente.
|
||||
- `declare struct S(a: int,);` passa no parser.
|
||||
- Tupla tipo com mais de 6 campos falha deterministicamente.
|
||||
- Tupla literal com mais de 6 itens falha deterministicamente.
|
||||
- Escape invalido em string gera erro lexico dedicado.
|
||||
|
||||
## Tests
|
||||
- Novo teste de parser para atributos em `.pbs` com recuperacao e codigo estavel.
|
||||
- Novo teste de linking para modulo sem `.pbs`.
|
||||
- Testes de parser para trailing comma em struct fields.
|
||||
- Testes de parser para limites de aridade de tupla tipo/tupla literal.
|
||||
- Teste de lexer para escape invalido em string.
|
||||
@ -84,7 +84,6 @@ public final class PbsAst {
|
||||
ReturnKind returnKind,
|
||||
TypeRef returnType,
|
||||
TypeRef resultErrorType,
|
||||
Expression elseFallback,
|
||||
Block body,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
@ -92,6 +91,8 @@ public final class PbsAst {
|
||||
public record StructDecl(
|
||||
String name,
|
||||
ReadOnlyList<StructField> fields,
|
||||
ReadOnlyList<FunctionDecl> methods,
|
||||
ReadOnlyList<CtorDecl> ctors,
|
||||
boolean hasBody,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
@ -140,6 +141,7 @@ public final class PbsAst {
|
||||
String contractName,
|
||||
String ownerName,
|
||||
String binderName,
|
||||
ReadOnlyList<FunctionDecl> methods,
|
||||
Span span) implements TopDecl {
|
||||
}
|
||||
|
||||
@ -154,6 +156,13 @@ public final class PbsAst {
|
||||
Span span) {
|
||||
}
|
||||
|
||||
public record CtorDecl(
|
||||
String name,
|
||||
ReadOnlyList<Parameter> parameters,
|
||||
Block body,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
public record TypeRef(
|
||||
TypeRefKind kind,
|
||||
String name,
|
||||
@ -241,6 +250,7 @@ public final class PbsAst {
|
||||
|
||||
public record Block(
|
||||
ReadOnlyList<Statement> statements,
|
||||
Expression tailExpression,
|
||||
Span span) {
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,9 @@ public enum ParseErrors {
|
||||
E_PARSE_INVALID_ASSIGN_TARGET,
|
||||
E_PARSE_INVALID_FOR_FORM,
|
||||
E_PARSE_LOOP_CONTROL_OUTSIDE_LOOP,
|
||||
E_PARSE_INVALID_ENUM_FORM,
|
||||
E_PARSE_DUPLICATE_ENUM_CASE_LABEL,
|
||||
E_PARSE_DUPLICATE_ENUM_CASE_ID,
|
||||
E_PARSE_INVALID_SWITCH_FORM,
|
||||
E_PARSE_INVALID_HANDLE_FORM,
|
||||
E_PARSE_INVALID_TUPLE_LITERAL,
|
||||
|
||||
@ -14,17 +14,25 @@ import java.util.ArrayList;
|
||||
* Dedicated expression parser for PBS.
|
||||
*/
|
||||
final class PbsExprParser {
|
||||
@FunctionalInterface
|
||||
interface BlockParserDelegate {
|
||||
PbsAst.Block parse(String message);
|
||||
}
|
||||
|
||||
private final PbsTokenCursor cursor;
|
||||
private final FileId fileId;
|
||||
private final DiagnosticSink diagnostics;
|
||||
private final BlockParserDelegate blockParserDelegate;
|
||||
|
||||
PbsExprParser(
|
||||
final PbsTokenCursor cursor,
|
||||
final FileId fileId,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final DiagnosticSink diagnostics,
|
||||
final BlockParserDelegate blockParserDelegate) {
|
||||
this.cursor = cursor;
|
||||
this.fileId = fileId;
|
||||
this.diagnostics = diagnostics;
|
||||
this.blockParserDelegate = blockParserDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -592,26 +600,7 @@ final class PbsExprParser {
|
||||
}
|
||||
|
||||
private PbsAst.Block parseSurfaceBlock(final String message) {
|
||||
final var open = consume(PbsTokenKind.LEFT_BRACE, message);
|
||||
return parseSurfaceBlockFromOpen(open);
|
||||
}
|
||||
|
||||
private PbsAst.Block parseSurfaceBlockFromOpen(final PbsToken open) {
|
||||
var depth = 1;
|
||||
var end = open.end();
|
||||
while (!cursor.isAtEnd() && depth > 0) {
|
||||
final var token = cursor.advance();
|
||||
end = token.end();
|
||||
if (token.kind() == PbsTokenKind.LEFT_BRACE) {
|
||||
depth++;
|
||||
} else if (token.kind() == PbsTokenKind.RIGHT_BRACE) {
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
if (depth > 0) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected '}' to close block expression");
|
||||
}
|
||||
return new PbsAst.Block(ReadOnlyList.empty(), span(open.start(), end));
|
||||
return blockParserDelegate.parse(message);
|
||||
}
|
||||
|
||||
private PbsToken consume(final PbsTokenKind kind, final String message) {
|
||||
|
||||
@ -9,6 +9,7 @@ import p.studio.compiler.source.identifiers.FileId;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* High-level manual parser for PBS source files.
|
||||
@ -29,7 +30,7 @@ public final class PbsParser {
|
||||
final FileId fileId,
|
||||
final DiagnosticSink diagnostics) {
|
||||
this.cursor = new PbsTokenCursor(tokens);
|
||||
this.exprParser = new PbsExprParser(cursor, fileId, diagnostics);
|
||||
this.exprParser = new PbsExprParser(cursor, fileId, diagnostics, this::parseExpressionSurfaceBlock);
|
||||
this.fileId = fileId;
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
@ -205,6 +206,8 @@ public final class PbsParser {
|
||||
final var fields = parseStructFields();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after struct fields");
|
||||
|
||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||
final var ctors = new ArrayList<PbsAst.CtorDecl>();
|
||||
final boolean hasBody;
|
||||
long end;
|
||||
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
||||
@ -212,7 +215,10 @@ public final class PbsParser {
|
||||
end = cursor.previous().end();
|
||||
} else if (cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
||||
hasBody = true;
|
||||
end = parseStructBodyAndConsumeRightBrace(cursor.previous());
|
||||
final var bodyParse = parseStructBodyAndConsumeRightBrace(cursor.previous());
|
||||
methods.addAll(bodyParse.methods().asList());
|
||||
ctors.addAll(bodyParse.ctors().asList());
|
||||
end = bodyParse.end();
|
||||
} else {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Struct declaration must end with ';' or contain a '{...}' body");
|
||||
@ -220,7 +226,13 @@ public final class PbsParser {
|
||||
end = consumeDeclarationTerminator();
|
||||
}
|
||||
|
||||
return new PbsAst.StructDecl(name.lexeme(), ReadOnlyList.wrap(fields), hasBody, span(declareToken.start(), end));
|
||||
return new PbsAst.StructDecl(
|
||||
name.lexeme(),
|
||||
ReadOnlyList.wrap(fields),
|
||||
ReadOnlyList.wrap(methods),
|
||||
ReadOnlyList.wrap(ctors),
|
||||
hasBody,
|
||||
span(declareToken.start(), end));
|
||||
}
|
||||
|
||||
private ArrayList<PbsAst.StructField> parseStructFields() {
|
||||
@ -258,14 +270,16 @@ public final class PbsParser {
|
||||
return fields;
|
||||
}
|
||||
|
||||
private long parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) {
|
||||
private StructBodyParse parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) {
|
||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||
final var ctors = new ArrayList<PbsAst.CtorDecl>();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (cursor.match(PbsTokenKind.FN)) {
|
||||
parseMethodDeclarationInBody(cursor.previous());
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
continue;
|
||||
}
|
||||
if (cursor.match(PbsTokenKind.CTOR)) {
|
||||
parseCtorDeclarationInBody(cursor.previous());
|
||||
ctors.add(parseCtorDeclarationInBody(cursor.previous()));
|
||||
continue;
|
||||
}
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
@ -273,15 +287,20 @@ public final class PbsParser {
|
||||
cursor.advance();
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end struct body");
|
||||
return rightBrace.end();
|
||||
return new StructBodyParse(ReadOnlyList.wrap(methods), ReadOnlyList.wrap(ctors), rightBrace.end());
|
||||
}
|
||||
|
||||
private void parseCtorDeclarationInBody(final PbsToken ctorToken) {
|
||||
consume(PbsTokenKind.IDENTIFIER, "Expected constructor name");
|
||||
private PbsAst.CtorDecl parseCtorDeclarationInBody(final PbsToken ctorToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constructor name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name");
|
||||
parseParametersUntilRightParen();
|
||||
final var parameters = parseParametersUntilRightParen();
|
||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after constructor parameters");
|
||||
consumeBlockForMember("constructor");
|
||||
final var body = parseBlock();
|
||||
return new PbsAst.CtorDecl(
|
||||
name.lexeme(),
|
||||
ReadOnlyList.wrap(parameters),
|
||||
body,
|
||||
span(ctorToken.start(), body.span().getEnd()));
|
||||
}
|
||||
|
||||
private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) {
|
||||
@ -312,7 +331,7 @@ public final class PbsParser {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
methods.add(parseFunctionLike(cursor.previous(), false));
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
}
|
||||
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()));
|
||||
@ -336,6 +355,10 @@ public final class PbsParser {
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
|
||||
|
||||
final var cases = new ArrayList<PbsAst.EnumCase>();
|
||||
final var caseLabels = new HashSet<String>();
|
||||
final var explicitCaseIds = new HashSet<Long>();
|
||||
var hasExplicitCases = false;
|
||||
var hasImplicitCases = false;
|
||||
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||
do {
|
||||
final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label");
|
||||
@ -343,11 +366,32 @@ public final class PbsParser {
|
||||
if (cursor.match(PbsTokenKind.EQUAL)) {
|
||||
final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case");
|
||||
explicitValue = parseLongOrNull(intToken.lexeme());
|
||||
hasExplicitCases = true;
|
||||
if (explicitValue == null) {
|
||||
report(intToken, ParseErrors.E_PARSE_INVALID_ENUM_FORM,
|
||||
"Invalid explicit enum identifier");
|
||||
} else if (!explicitCaseIds.add(explicitValue)) {
|
||||
report(intToken, ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID,
|
||||
"Duplicate explicit enum identifier '%s'".formatted(explicitValue));
|
||||
}
|
||||
} else {
|
||||
hasImplicitCases = true;
|
||||
}
|
||||
|
||||
if (!caseLabels.add(caseName.lexeme())) {
|
||||
report(caseName, ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL,
|
||||
"Duplicate enum case label '%s'".formatted(caseName.lexeme()));
|
||||
}
|
||||
|
||||
cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end())));
|
||||
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
|
||||
}
|
||||
|
||||
if (hasExplicitCases && hasImplicitCases) {
|
||||
report(name, ParseErrors.E_PARSE_INVALID_ENUM_FORM,
|
||||
"Enum declarations cannot mix implicit and explicit case identifiers");
|
||||
}
|
||||
|
||||
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after enum cases");
|
||||
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after enum declaration");
|
||||
return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), Math.max(rightParen.end(), semicolon.end())));
|
||||
@ -357,10 +401,10 @@ public final class PbsParser {
|
||||
* Parses a top-level function declaration.
|
||||
*/
|
||||
private PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) {
|
||||
return parseFunctionLike(fnToken, true);
|
||||
return parseFunctionLike(fnToken);
|
||||
}
|
||||
|
||||
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken, final boolean allowElseFallback) {
|
||||
private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) {
|
||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||
final var parameters = parseParametersUntilRightParen();
|
||||
@ -368,11 +412,6 @@ public final class PbsParser {
|
||||
|
||||
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||
|
||||
PbsAst.Expression elseFallback = null;
|
||||
if (allowElseFallback && cursor.match(PbsTokenKind.ELSE)) {
|
||||
elseFallback = exprParser.parseExpression();
|
||||
}
|
||||
|
||||
final var body = parseBlock();
|
||||
return new PbsAst.FunctionDecl(
|
||||
name.lexeme(),
|
||||
@ -380,7 +419,6 @@ public final class PbsParser {
|
||||
returnSpec.kind,
|
||||
returnSpec.returnType,
|
||||
returnSpec.resultErrorType,
|
||||
elseFallback,
|
||||
body,
|
||||
span(fnToken.start(), body.span().getEnd()));
|
||||
}
|
||||
@ -407,10 +445,6 @@ public final class PbsParser {
|
||||
span(fnToken.start(), end));
|
||||
}
|
||||
|
||||
private void parseMethodDeclarationInBody(final PbsToken fnToken) {
|
||||
parseFunctionLike(fnToken, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `declare callback` declaration.
|
||||
*/
|
||||
@ -463,9 +497,20 @@ public final class PbsParser {
|
||||
consume(PbsTokenKind.USING, "Expected 'using' in 'implements' declaration");
|
||||
final var binderName = consume(PbsTokenKind.IDENTIFIER, "Expected binder name after 'using'");
|
||||
|
||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||
long end;
|
||||
if (cursor.check(PbsTokenKind.LEFT_BRACE)) {
|
||||
end = consumeBalancedBraces(cursor.advance());
|
||||
cursor.advance();
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
if (!cursor.match(PbsTokenKind.FN)) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Implements body accepts only function declarations");
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
methods.add(parseFunctionLike(cursor.previous()));
|
||||
}
|
||||
end = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end implements body").end();
|
||||
} else {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN,
|
||||
"Expected '{' to start 'implements' body");
|
||||
@ -476,6 +521,7 @@ public final class PbsParser {
|
||||
contractName.lexeme(),
|
||||
ownerName.lexeme(),
|
||||
binderName.lexeme(),
|
||||
ReadOnlyList.wrap(methods),
|
||||
span(implementsToken.start(), end));
|
||||
}
|
||||
|
||||
@ -501,13 +547,13 @@ public final class PbsParser {
|
||||
|
||||
private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) {
|
||||
if (cursor.match(PbsTokenKind.ARROW)) {
|
||||
return parseReturnSpecFromMarker(cursor.previous());
|
||||
return parseReturnSpecFromMarker();
|
||||
}
|
||||
|
||||
if (cursor.match(PbsTokenKind.COLON)) {
|
||||
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION,
|
||||
"Return annotations must use '->' syntax");
|
||||
return parseReturnSpecFromMarker(cursor.previous());
|
||||
return parseReturnSpecFromMarker();
|
||||
}
|
||||
|
||||
final var inferredSpan = span(anchorToken.end(), anchorToken.end());
|
||||
@ -517,7 +563,7 @@ public final class PbsParser {
|
||||
null);
|
||||
}
|
||||
|
||||
private ParsedReturnSpec parseReturnSpecFromMarker(final PbsToken markerToken) {
|
||||
private ParsedReturnSpec parseReturnSpecFromMarker() {
|
||||
if (cursor.match(PbsTokenKind.RESULT)) {
|
||||
final var resultToken = cursor.previous();
|
||||
consume(PbsTokenKind.LESS, "Expected '<' after 'result'");
|
||||
@ -624,13 +670,56 @@ public final class PbsParser {
|
||||
* Parses a brace-delimited block.
|
||||
*/
|
||||
private PbsAst.Block parseBlock() {
|
||||
final var leftBrace = consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start block");
|
||||
return parseBlock("Expected '{' to start block", true);
|
||||
}
|
||||
|
||||
private PbsAst.Block parseExpressionSurfaceBlock(final String message) {
|
||||
return parseBlock(message, true);
|
||||
}
|
||||
|
||||
private PbsAst.Block parseBlock(final String message, final boolean allowTailExpression) {
|
||||
final var leftBrace = consume(PbsTokenKind.LEFT_BRACE, message);
|
||||
final var statements = new ArrayList<PbsAst.Statement>();
|
||||
PbsAst.Expression tailExpression = null;
|
||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||
statements.add(parseStatement());
|
||||
if (startsStructuredStatement() || isAssignmentStatementStart()) {
|
||||
statements.add(parseStatement());
|
||||
continue;
|
||||
}
|
||||
|
||||
final var expression = exprParser.parseExpression();
|
||||
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
||||
statements.add(new PbsAst.ExpressionStatement(
|
||||
expression,
|
||||
span(expression.span().getStart(), cursor.previous().end())));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (allowTailExpression && cursor.check(PbsTokenKind.RIGHT_BRACE)) {
|
||||
tailExpression = expression;
|
||||
break;
|
||||
}
|
||||
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ';' after expression");
|
||||
if (!cursor.isAtEnd() && !cursor.check(PbsTokenKind.RIGHT_BRACE)) {
|
||||
cursor.advance();
|
||||
}
|
||||
}
|
||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end block");
|
||||
return new PbsAst.Block(ReadOnlyList.wrap(statements), span(leftBrace.start(), rightBrace.end()));
|
||||
return new PbsAst.Block(
|
||||
ReadOnlyList.wrap(statements),
|
||||
tailExpression,
|
||||
span(leftBrace.start(), rightBrace.end()));
|
||||
}
|
||||
|
||||
private boolean startsStructuredStatement() {
|
||||
return cursor.check(PbsTokenKind.LET)
|
||||
|| cursor.check(PbsTokenKind.IF)
|
||||
|| cursor.check(PbsTokenKind.FOR)
|
||||
|| cursor.check(PbsTokenKind.WHILE)
|
||||
|| cursor.check(PbsTokenKind.BREAK)
|
||||
|| cursor.check(PbsTokenKind.CONTINUE)
|
||||
|| cursor.check(PbsTokenKind.RETURN);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -878,16 +967,6 @@ public final class PbsParser {
|
||||
};
|
||||
}
|
||||
|
||||
private void consumeBlockForMember(final String memberKind) {
|
||||
if (!cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||
"Expected '{' to start " + memberKind + " body");
|
||||
consumeDeclarationTerminator();
|
||||
return;
|
||||
}
|
||||
consumeBalancedBraces(cursor.previous());
|
||||
}
|
||||
|
||||
private long consumeDeclarationTerminator() {
|
||||
var end = cursor.previous().end();
|
||||
var parenDepth = 0;
|
||||
@ -989,4 +1068,10 @@ public final class PbsParser {
|
||||
PbsAst.TypeRef returnType,
|
||||
PbsAst.TypeRef resultErrorType) {
|
||||
}
|
||||
|
||||
private record StructBodyParse(
|
||||
ReadOnlyList<PbsAst.FunctionDecl> methods,
|
||||
ReadOnlyList<PbsAst.CtorDecl> ctors,
|
||||
long end) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import p.studio.compiler.source.identifiers.FileId;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PbsExprParserTest {
|
||||
@ -60,14 +61,20 @@ class PbsExprParserTest {
|
||||
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms should parse cleanly");
|
||||
|
||||
final var body = ast.functions().getFirst().body().statements();
|
||||
assertInstanceOf(PbsAst.IfExpr.class,
|
||||
final var ifExpr = assertInstanceOf(PbsAst.IfExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(0)).initializer());
|
||||
assertInstanceOf(PbsAst.SwitchExpr.class,
|
||||
assertInstanceOf(PbsAst.IntLiteralExpr.class, ifExpr.thenBlock().tailExpression());
|
||||
final var elseBlock = assertInstanceOf(PbsAst.BlockExpr.class, ifExpr.elseExpression()).block();
|
||||
assertInstanceOf(PbsAst.IntLiteralExpr.class, elseBlock.tailExpression());
|
||||
|
||||
final var switchExpr = assertInstanceOf(PbsAst.SwitchExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(1)).initializer());
|
||||
assertInstanceOf(PbsAst.IntLiteralExpr.class, switchExpr.arms().getFirst().block().tailExpression());
|
||||
|
||||
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
||||
assertEquals(2, handle.arms().size());
|
||||
assertInstanceOf(PbsAst.IntLiteralExpr.class, handle.arms().get(1).block().tailExpression());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -88,14 +95,28 @@ class PbsExprParserTest {
|
||||
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms with return blocks should parse cleanly");
|
||||
|
||||
final var body = ast.functions().getFirst().body().statements();
|
||||
assertInstanceOf(PbsAst.IfExpr.class,
|
||||
final var ifExpr = assertInstanceOf(PbsAst.IfExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(0)).initializer());
|
||||
assertInstanceOf(PbsAst.SwitchExpr.class,
|
||||
assertEquals(1, ifExpr.thenBlock().statements().size());
|
||||
assertInstanceOf(PbsAst.ReturnStatement.class, ifExpr.thenBlock().statements().getFirst());
|
||||
assertNull(ifExpr.thenBlock().tailExpression());
|
||||
final var elseBlock = assertInstanceOf(PbsAst.BlockExpr.class, ifExpr.elseExpression()).block();
|
||||
assertEquals(1, elseBlock.statements().size());
|
||||
assertInstanceOf(PbsAst.ReturnStatement.class, elseBlock.statements().getFirst());
|
||||
assertNull(elseBlock.tailExpression());
|
||||
|
||||
final var switchExpr = assertInstanceOf(PbsAst.SwitchExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(1)).initializer());
|
||||
assertEquals(1, switchExpr.arms().getFirst().block().statements().size());
|
||||
assertInstanceOf(PbsAst.ReturnStatement.class, switchExpr.arms().getFirst().block().statements().getFirst());
|
||||
assertNull(switchExpr.arms().getFirst().block().tailExpression());
|
||||
|
||||
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
||||
assertEquals(2, handle.arms().size());
|
||||
assertEquals(1, handle.arms().get(1).block().statements().size());
|
||||
assertInstanceOf(PbsAst.ReturnStatement.class, handle.arms().get(1).block().statements().getFirst());
|
||||
assertNull(handle.arms().get(1).block().tailExpression());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -97,6 +97,8 @@ class PbsParserTest {
|
||||
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
||||
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));
|
||||
assertEquals(1, contractDecl.signatures().size());
|
||||
@ -115,7 +117,8 @@ class PbsParserTest {
|
||||
assertEquals("Err", callbackDecl.resultErrorType().name());
|
||||
|
||||
assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(7));
|
||||
assertInstanceOf(PbsAst.ImplementsDecl.class, ast.topDecls().get(8));
|
||||
final var implementsDecl = assertInstanceOf(PbsAst.ImplementsDecl.class, ast.topDecls().get(8));
|
||||
assertEquals(1, implementsDecl.methods().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -164,6 +167,37 @@ class PbsParserTest {
|
||||
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectFunctionElseFallbackSurface() {
|
||||
final var source = """
|
||||
fn run() -> int else 1 {
|
||||
return 1;
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.hasErrors(), "Function-level else fallback syntax is not valid PBS core syntax");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportEnumMixedAndDuplicateCases() {
|
||||
final var source = """
|
||||
declare enum Mixed(A, B = 1);
|
||||
declare enum Duplicated(Idle = 0, Idle = 1, Run = 1);
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_INVALID_ENUM_FORM.name())));
|
||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL.name())));
|
||||
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecoverWithInvalidDeclarationNode() {
|
||||
final var source = """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user