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`
|
4. `PR-004-pbs-parser-statements-and-control-flow.md`
|
||||||
5. `PR-005-pbs-parser-expressions-optional-result-apply.md`
|
5. `PR-005-pbs-parser-expressions-optional-result-apply.md`
|
||||||
6. `PR-006-pbs-barrel-and-module-visibility.md`
|
6. `PR-006-pbs-barrel-and-module-visibility.md`
|
||||||
7. `PR-007-pbs-static-semantics-declaration-validation.md`
|
7. `PR-006.2-pbs-parser-ast-syntax-hardening.md`
|
||||||
8. `PR-008-pbs-static-semantics-call-resolution-and-flow.md`
|
8. `PR-006.3-pbs-syntax-completeness-and-module-hygiene.md`
|
||||||
9. `PR-009-pbs-diagnostics-contract-v1.md`
|
9. `PR-007-pbs-static-semantics-declaration-validation.md`
|
||||||
10. `PR-010-pbs-irbackend-lowering-contract.md`
|
10. `PR-008-pbs-static-semantics-call-resolution-and-flow.md`
|
||||||
11. `PR-011-pbs-gate-u-conformance-fixtures.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`.
|
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,
|
ReturnKind returnKind,
|
||||||
TypeRef returnType,
|
TypeRef returnType,
|
||||||
TypeRef resultErrorType,
|
TypeRef resultErrorType,
|
||||||
Expression elseFallback,
|
|
||||||
Block body,
|
Block body,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
@ -92,6 +91,8 @@ public final class PbsAst {
|
|||||||
public record StructDecl(
|
public record StructDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<StructField> fields,
|
ReadOnlyList<StructField> fields,
|
||||||
|
ReadOnlyList<FunctionDecl> methods,
|
||||||
|
ReadOnlyList<CtorDecl> ctors,
|
||||||
boolean hasBody,
|
boolean hasBody,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
@ -140,6 +141,7 @@ public final class PbsAst {
|
|||||||
String contractName,
|
String contractName,
|
||||||
String ownerName,
|
String ownerName,
|
||||||
String binderName,
|
String binderName,
|
||||||
|
ReadOnlyList<FunctionDecl> methods,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +156,13 @@ public final class PbsAst {
|
|||||||
Span span) {
|
Span span) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record CtorDecl(
|
||||||
|
String name,
|
||||||
|
ReadOnlyList<Parameter> parameters,
|
||||||
|
Block body,
|
||||||
|
Span span) {
|
||||||
|
}
|
||||||
|
|
||||||
public record TypeRef(
|
public record TypeRef(
|
||||||
TypeRefKind kind,
|
TypeRefKind kind,
|
||||||
String name,
|
String name,
|
||||||
@ -241,6 +250,7 @@ public final class PbsAst {
|
|||||||
|
|
||||||
public record Block(
|
public record Block(
|
||||||
ReadOnlyList<Statement> statements,
|
ReadOnlyList<Statement> statements,
|
||||||
|
Expression tailExpression,
|
||||||
Span span) {
|
Span span) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,9 @@ public enum ParseErrors {
|
|||||||
E_PARSE_INVALID_ASSIGN_TARGET,
|
E_PARSE_INVALID_ASSIGN_TARGET,
|
||||||
E_PARSE_INVALID_FOR_FORM,
|
E_PARSE_INVALID_FOR_FORM,
|
||||||
E_PARSE_LOOP_CONTROL_OUTSIDE_LOOP,
|
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_SWITCH_FORM,
|
||||||
E_PARSE_INVALID_HANDLE_FORM,
|
E_PARSE_INVALID_HANDLE_FORM,
|
||||||
E_PARSE_INVALID_TUPLE_LITERAL,
|
E_PARSE_INVALID_TUPLE_LITERAL,
|
||||||
|
|||||||
@ -14,17 +14,25 @@ import java.util.ArrayList;
|
|||||||
* Dedicated expression parser for PBS.
|
* Dedicated expression parser for PBS.
|
||||||
*/
|
*/
|
||||||
final class PbsExprParser {
|
final class PbsExprParser {
|
||||||
|
@FunctionalInterface
|
||||||
|
interface BlockParserDelegate {
|
||||||
|
PbsAst.Block parse(String message);
|
||||||
|
}
|
||||||
|
|
||||||
private final PbsTokenCursor cursor;
|
private final PbsTokenCursor cursor;
|
||||||
private final FileId fileId;
|
private final FileId fileId;
|
||||||
private final DiagnosticSink diagnostics;
|
private final DiagnosticSink diagnostics;
|
||||||
|
private final BlockParserDelegate blockParserDelegate;
|
||||||
|
|
||||||
PbsExprParser(
|
PbsExprParser(
|
||||||
final PbsTokenCursor cursor,
|
final PbsTokenCursor cursor,
|
||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics,
|
||||||
|
final BlockParserDelegate blockParserDelegate) {
|
||||||
this.cursor = cursor;
|
this.cursor = cursor;
|
||||||
this.fileId = fileId;
|
this.fileId = fileId;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
|
this.blockParserDelegate = blockParserDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -592,26 +600,7 @@ final class PbsExprParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.Block parseSurfaceBlock(final String message) {
|
private PbsAst.Block parseSurfaceBlock(final String message) {
|
||||||
final var open = consume(PbsTokenKind.LEFT_BRACE, message);
|
return blockParserDelegate.parse(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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsToken consume(final PbsTokenKind kind, final String 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 p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High-level manual parser for PBS source files.
|
* High-level manual parser for PBS source files.
|
||||||
@ -29,7 +30,7 @@ public final class PbsParser {
|
|||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
this.cursor = new PbsTokenCursor(tokens);
|
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.fileId = fileId;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
}
|
}
|
||||||
@ -205,6 +206,8 @@ public final class PbsParser {
|
|||||||
final var fields = parseStructFields();
|
final var fields = parseStructFields();
|
||||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after struct fields");
|
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;
|
final boolean hasBody;
|
||||||
long end;
|
long end;
|
||||||
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
||||||
@ -212,7 +215,10 @@ public final class PbsParser {
|
|||||||
end = cursor.previous().end();
|
end = cursor.previous().end();
|
||||||
} else if (cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
} else if (cursor.match(PbsTokenKind.LEFT_BRACE)) {
|
||||||
hasBody = true;
|
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 {
|
} else {
|
||||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||||
"Struct declaration must end with ';' or contain a '{...}' body");
|
"Struct declaration must end with ';' or contain a '{...}' body");
|
||||||
@ -220,7 +226,13 @@ public final class PbsParser {
|
|||||||
end = consumeDeclarationTerminator();
|
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() {
|
private ArrayList<PbsAst.StructField> parseStructFields() {
|
||||||
@ -258,14 +270,16 @@ public final class PbsParser {
|
|||||||
return fields;
|
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()) {
|
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||||
if (cursor.match(PbsTokenKind.FN)) {
|
if (cursor.match(PbsTokenKind.FN)) {
|
||||||
parseMethodDeclarationInBody(cursor.previous());
|
methods.add(parseFunctionLike(cursor.previous()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.CTOR)) {
|
if (cursor.match(PbsTokenKind.CTOR)) {
|
||||||
parseCtorDeclarationInBody(cursor.previous());
|
ctors.add(parseCtorDeclarationInBody(cursor.previous()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||||
@ -273,15 +287,20 @@ public final class PbsParser {
|
|||||||
cursor.advance();
|
cursor.advance();
|
||||||
}
|
}
|
||||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end struct body");
|
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) {
|
private PbsAst.CtorDecl parseCtorDeclarationInBody(final PbsToken ctorToken) {
|
||||||
consume(PbsTokenKind.IDENTIFIER, "Expected constructor name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constructor name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name");
|
||||||
parseParametersUntilRightParen();
|
final var parameters = parseParametersUntilRightParen();
|
||||||
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after constructor parameters");
|
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) {
|
private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) {
|
||||||
@ -312,7 +331,7 @@ public final class PbsParser {
|
|||||||
cursor.advance();
|
cursor.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
methods.add(parseFunctionLike(cursor.previous(), false));
|
methods.add(parseFunctionLike(cursor.previous()));
|
||||||
}
|
}
|
||||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end service body");
|
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()));
|
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");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
|
||||||
|
|
||||||
final var cases = new ArrayList<PbsAst.EnumCase>();
|
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)) {
|
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||||
do {
|
do {
|
||||||
final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label");
|
final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label");
|
||||||
@ -343,11 +366,32 @@ public final class PbsParser {
|
|||||||
if (cursor.match(PbsTokenKind.EQUAL)) {
|
if (cursor.match(PbsTokenKind.EQUAL)) {
|
||||||
final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case");
|
final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case");
|
||||||
explicitValue = parseLongOrNull(intToken.lexeme());
|
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())));
|
cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end())));
|
||||||
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
|
} 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 rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after enum cases");
|
||||||
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after enum declaration");
|
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())));
|
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.
|
* Parses a top-level function declaration.
|
||||||
*/
|
*/
|
||||||
private PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) {
|
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");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||||
final var parameters = parseParametersUntilRightParen();
|
final var parameters = parseParametersUntilRightParen();
|
||||||
@ -368,11 +412,6 @@ public final class PbsParser {
|
|||||||
|
|
||||||
final var returnSpec = parseCallableReturnSpec(rightParen);
|
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||||
|
|
||||||
PbsAst.Expression elseFallback = null;
|
|
||||||
if (allowElseFallback && cursor.match(PbsTokenKind.ELSE)) {
|
|
||||||
elseFallback = exprParser.parseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
final var body = parseBlock();
|
final var body = parseBlock();
|
||||||
return new PbsAst.FunctionDecl(
|
return new PbsAst.FunctionDecl(
|
||||||
name.lexeme(),
|
name.lexeme(),
|
||||||
@ -380,7 +419,6 @@ public final class PbsParser {
|
|||||||
returnSpec.kind,
|
returnSpec.kind,
|
||||||
returnSpec.returnType,
|
returnSpec.returnType,
|
||||||
returnSpec.resultErrorType,
|
returnSpec.resultErrorType,
|
||||||
elseFallback,
|
|
||||||
body,
|
body,
|
||||||
span(fnToken.start(), body.span().getEnd()));
|
span(fnToken.start(), body.span().getEnd()));
|
||||||
}
|
}
|
||||||
@ -407,10 +445,6 @@ public final class PbsParser {
|
|||||||
span(fnToken.start(), end));
|
span(fnToken.start(), end));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseMethodDeclarationInBody(final PbsToken fnToken) {
|
|
||||||
parseFunctionLike(fnToken, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses `declare callback` declaration.
|
* Parses `declare callback` declaration.
|
||||||
*/
|
*/
|
||||||
@ -463,9 +497,20 @@ public final class PbsParser {
|
|||||||
consume(PbsTokenKind.USING, "Expected 'using' in 'implements' declaration");
|
consume(PbsTokenKind.USING, "Expected 'using' in 'implements' declaration");
|
||||||
final var binderName = consume(PbsTokenKind.IDENTIFIER, "Expected binder name after 'using'");
|
final var binderName = consume(PbsTokenKind.IDENTIFIER, "Expected binder name after 'using'");
|
||||||
|
|
||||||
|
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||||
long end;
|
long end;
|
||||||
if (cursor.check(PbsTokenKind.LEFT_BRACE)) {
|
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 {
|
} else {
|
||||||
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN,
|
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN,
|
||||||
"Expected '{' to start 'implements' body");
|
"Expected '{' to start 'implements' body");
|
||||||
@ -476,6 +521,7 @@ public final class PbsParser {
|
|||||||
contractName.lexeme(),
|
contractName.lexeme(),
|
||||||
ownerName.lexeme(),
|
ownerName.lexeme(),
|
||||||
binderName.lexeme(),
|
binderName.lexeme(),
|
||||||
|
ReadOnlyList.wrap(methods),
|
||||||
span(implementsToken.start(), end));
|
span(implementsToken.start(), end));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,13 +547,13 @@ public final class PbsParser {
|
|||||||
|
|
||||||
private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) {
|
private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) {
|
||||||
if (cursor.match(PbsTokenKind.ARROW)) {
|
if (cursor.match(PbsTokenKind.ARROW)) {
|
||||||
return parseReturnSpecFromMarker(cursor.previous());
|
return parseReturnSpecFromMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor.match(PbsTokenKind.COLON)) {
|
if (cursor.match(PbsTokenKind.COLON)) {
|
||||||
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION,
|
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION,
|
||||||
"Return annotations must use '->' syntax");
|
"Return annotations must use '->' syntax");
|
||||||
return parseReturnSpecFromMarker(cursor.previous());
|
return parseReturnSpecFromMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
final var inferredSpan = span(anchorToken.end(), anchorToken.end());
|
final var inferredSpan = span(anchorToken.end(), anchorToken.end());
|
||||||
@ -517,7 +563,7 @@ public final class PbsParser {
|
|||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParsedReturnSpec parseReturnSpecFromMarker(final PbsToken markerToken) {
|
private ParsedReturnSpec parseReturnSpecFromMarker() {
|
||||||
if (cursor.match(PbsTokenKind.RESULT)) {
|
if (cursor.match(PbsTokenKind.RESULT)) {
|
||||||
final var resultToken = cursor.previous();
|
final var resultToken = cursor.previous();
|
||||||
consume(PbsTokenKind.LESS, "Expected '<' after 'result'");
|
consume(PbsTokenKind.LESS, "Expected '<' after 'result'");
|
||||||
@ -624,13 +670,56 @@ public final class PbsParser {
|
|||||||
* Parses a brace-delimited block.
|
* Parses a brace-delimited block.
|
||||||
*/
|
*/
|
||||||
private PbsAst.Block parseBlock() {
|
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>();
|
final var statements = new ArrayList<PbsAst.Statement>();
|
||||||
|
PbsAst.Expression tailExpression = null;
|
||||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
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");
|
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() {
|
private long consumeDeclarationTerminator() {
|
||||||
var end = cursor.previous().end();
|
var end = cursor.previous().end();
|
||||||
var parenDepth = 0;
|
var parenDepth = 0;
|
||||||
@ -989,4 +1068,10 @@ public final class PbsParser {
|
|||||||
PbsAst.TypeRef returnType,
|
PbsAst.TypeRef returnType,
|
||||||
PbsAst.TypeRef resultErrorType) {
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class PbsExprParserTest {
|
class PbsExprParserTest {
|
||||||
@ -60,14 +61,20 @@ class PbsExprParserTest {
|
|||||||
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms should parse cleanly");
|
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms should parse cleanly");
|
||||||
|
|
||||||
final var body = ast.functions().getFirst().body().statements();
|
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.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.LetStatement.class, body.get(1)).initializer());
|
||||||
|
assertInstanceOf(PbsAst.IntLiteralExpr.class, switchExpr.arms().getFirst().block().tailExpression());
|
||||||
|
|
||||||
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
||||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
||||||
assertEquals(2, handle.arms().size());
|
assertEquals(2, handle.arms().size());
|
||||||
|
assertInstanceOf(PbsAst.IntLiteralExpr.class, handle.arms().get(1).block().tailExpression());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -88,14 +95,28 @@ class PbsExprParserTest {
|
|||||||
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms with return blocks should parse cleanly");
|
assertTrue(diagnostics.isEmpty(), "Expression control-flow forms with return blocks should parse cleanly");
|
||||||
|
|
||||||
final var body = ast.functions().getFirst().body().statements();
|
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.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());
|
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,
|
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
||||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
||||||
assertEquals(2, handle.arms().size());
|
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
|
@Test
|
||||||
|
|||||||
@ -97,6 +97,8 @@ class PbsParserTest {
|
|||||||
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().get(1));
|
||||||
assertEquals(2, structDecl.fields().size());
|
assertEquals(2, structDecl.fields().size());
|
||||||
assertTrue(structDecl.hasBody());
|
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(2));
|
||||||
assertEquals(1, contractDecl.signatures().size());
|
assertEquals(1, contractDecl.signatures().size());
|
||||||
@ -115,7 +117,8 @@ class PbsParserTest {
|
|||||||
assertEquals("Err", callbackDecl.resultErrorType().name());
|
assertEquals("Err", callbackDecl.resultErrorType().name());
|
||||||
|
|
||||||
assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(7));
|
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
|
@Test
|
||||||
@ -164,6 +167,37 @@ class PbsParserTest {
|
|||||||
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(2));
|
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
|
@Test
|
||||||
void shouldRecoverWithInvalidDeclarationNode() {
|
void shouldRecoverWithInvalidDeclarationNode() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user