implements PLN-0093

This commit is contained in:
bQUARKz 2026-07-15 06:57:19 +01:00
parent 917b0e1b9a
commit 2b25364252
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 93 additions and 4 deletions

View File

@ -1,7 +1,7 @@
{"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":40,"PLN":103,"LSN":55,"CLSN":1}} {"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":40,"PLN":103,"LSN":55,"CLSN":1}}
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]} {"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]}
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} {"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]} {"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]}
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-07","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0034-frontend-semantic-host-projection-flexibility/LSN-0048-frontend-owned-semantic-vocabularies-with-declarative-host-projection.md","status":"done","created_at":"2026-05-07","updated_at":"2026-05-07"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-07","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0034-frontend-semantic-host-projection-flexibility/LSN-0048-frontend-owned-semantic-vocabularies-with-declarative-host-projection.md","status":"done","created_at":"2026-05-07","updated_at":"2026-05-07"}]}
{"type":"discussion","id":"DSC-0033","status":"done","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-08","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0050","file":"discussion/lessons/DSC-0033-frontend-visual-theme-spec-and-css-retirement/LSN-0050-frontend-owned-visual-themes-with-structured-contract-and-host-adapters.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} {"type":"discussion","id":"DSC-0033","status":"done","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-08","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0050","file":"discussion/lessons/DSC-0033-frontend-visual-theme-spec-and-css-retirement/LSN-0050-frontend-owned-visual-themes-with-structured-contract-and-host-adapters.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0093 id: PLN-0093
ticket: pbs-symbol-documentation-and-hover-markdown ticket: pbs-symbol-documentation-and-hover-markdown
title: Implement PBS lexer support for documentation text blocks title: Implement PBS lexer support for documentation text blocks
status: open status: done
created: 2026-07-15 created: 2026-07-15
ref_decisions: [DEC-0039] ref_decisions: [DEC-0039]
tags: [compiler, compiler-pbs, studio, lsp, vscode, editor, hover, documentation, markdown] tags: [compiler, compiler-pbs, studio, lsp, vscode, editor, hover, documentation, markdown]

View File

@ -50,7 +50,7 @@ public enum PbsSemanticKind {
public static PbsSemanticKind forToken(final PbsToken token) { public static PbsSemanticKind forToken(final PbsToken token) {
return switch (token.kind()) { return switch (token.kind()) {
case COMMENT -> COMMENT; case COMMENT -> COMMENT;
case STRING_LITERAL -> STRING; case STRING_LITERAL, DOC_TEXT_BLOCK -> STRING;
case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> NUMBER; case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> NUMBER;
case TRUE, FALSE, NONE, SOME, OK, ERR -> LITERAL; case TRUE, FALSE, NONE, SOME, OK, ERR -> LITERAL;
case VOID, OPTIONAL, RESULT -> BUILTIN_TYPE; case VOID, OPTIONAL, RESULT -> BUILTIN_TYPE;

View File

@ -3,5 +3,6 @@ package p.studio.compiler.pbs.lexer;
public enum LexErrors { public enum LexErrors {
E_LEX_INVALID_CHAR, E_LEX_INVALID_CHAR,
E_LEX_UNTERMINATED_STRING, E_LEX_UNTERMINATED_STRING,
E_LEX_UNTERMINATED_DOC_TEXT_BLOCK,
E_LEX_INVALID_STRING_ESCAPE, E_LEX_INVALID_STRING_ESCAPE,
} }

View File

@ -17,6 +17,7 @@ public final class PbsLexer {
IDENTIFIER, IDENTIFIER,
NUMBER, NUMBER,
STRING, STRING,
DOC_TEXT_BLOCK,
LINE_COMMENT LINE_COMMENT
} }
@ -52,6 +53,7 @@ public final class PbsLexer {
case IDENTIFIER -> scanIdentifierState(); case IDENTIFIER -> scanIdentifierState();
case NUMBER -> scanNumberState(); case NUMBER -> scanNumberState();
case STRING -> scanStringState(); case STRING -> scanStringState();
case DOC_TEXT_BLOCK -> scanDocTextBlockState();
case LINE_COMMENT -> scanLineCommentState(); case LINE_COMMENT -> scanLineCommentState();
} }
} }
@ -123,7 +125,15 @@ public final class PbsLexer {
} }
addToken(match('=') ? PbsTokenKind.SLASH_EQUAL : PbsTokenKind.SLASH); addToken(match('=') ? PbsTokenKind.SLASH_EQUAL : PbsTokenKind.SLASH);
} }
case '"' -> state = LexerState.STRING; case '"' -> {
if (peek() == '"' && peekNext() == '"') {
advance();
advance();
state = LexerState.DOC_TEXT_BLOCK;
return;
}
state = LexerState.STRING;
}
default -> { default -> {
if (isDigit(c)) { if (isDigit(c)) {
state = LexerState.NUMBER; state = LexerState.NUMBER;
@ -206,6 +216,23 @@ public final class PbsLexer {
state = LexerState.DEFAULT; state = LexerState.DEFAULT;
} }
private void scanDocTextBlockState() {
while (!isAtEnd()) {
if (peek() == '"' && peekNext() == '"' && peekAfterNext() == '"') {
advance();
advance();
advance();
addToken(PbsTokenKind.DOC_TEXT_BLOCK);
state = LexerState.DEFAULT;
return;
}
advance();
}
report(LexErrors.E_LEX_UNTERMINATED_DOC_TEXT_BLOCK, "Unterminated documentation text block");
state = LexerState.DEFAULT;
}
private void scanLineCommentState() { private void scanLineCommentState() {
while (!isAtEnd() && peek() != '\n') { while (!isAtEnd() && peek() != '\n') {
advance(); advance();
@ -250,6 +277,17 @@ public final class PbsLexer {
return source.codePointAt(nextIndex); return source.codePointAt(nextIndex);
} }
private int peekAfterNext() {
if (isAtEnd()) return '\0';
final int first = source.codePointAt(current);
final int secondIndex = current + Character.charCount(first);
if (secondIndex >= source.length()) return '\0';
final int second = source.codePointAt(secondIndex);
final int thirdIndex = secondIndex + Character.charCount(second);
if (thirdIndex >= source.length()) return '\0';
return source.codePointAt(thirdIndex);
}
private boolean isAtEnd() { private boolean isAtEnd() {
return current >= source.length(); return current >= source.length();
} }

View File

@ -17,6 +17,7 @@ public enum PbsTokenKind {
FLOAT_LITERAL, FLOAT_LITERAL,
BOUNDED_LITERAL, BOUNDED_LITERAL,
STRING_LITERAL, STRING_LITERAL,
DOC_TEXT_BLOCK,
COMMENT, COMMENT,
// Import keywords. // Import keywords.

View File

@ -113,6 +113,55 @@ class PbsLexerTest {
diagnostics.stream().findFirst().orElseThrow().getCode()); diagnostics.stream().findFirst().orElseThrow().getCode());
} }
@Test
void shouldLexDocumentationTextBlock() {
final var source = """
[Doc(markdown = \"""
Draws a sprite.
- `x`: screen x
- `y`: screen y
\""")]
""";
final var diagnostics = DiagnosticSink.empty();
final var tokens = PbsLexer.lex(source, new FileId(0), diagnostics);
final var docTextBlock = tokens.stream()
.filter(t -> t.kind() == PbsTokenKind.DOC_TEXT_BLOCK)
.findFirst()
.orElseThrow();
assertTrue(docTextBlock.lexeme().startsWith("\"\"\""));
assertTrue(docTextBlock.lexeme().endsWith("\"\"\""));
assertTrue(docTextBlock.lexeme().contains("Draws a sprite."));
assertTrue(docTextBlock.lexeme().contains("- `x`: screen x"));
assertTrue(diagnostics.isEmpty(), "Documentation text block should lex without diagnostics");
}
@Test
void shouldAllowSingleAndDoubleQuotesInsideDocumentationTextBlock() {
final var source = "[Doc(markdown = \"\"\"Uses \"quoted\" text and 'single' quotes.\"\"\")]";
final var diagnostics = DiagnosticSink.empty();
final var tokens = PbsLexer.lex(source, new FileId(0), diagnostics);
final var kinds = tokens.stream().map(PbsToken::kind).toList();
assertTrue(kinds.contains(PbsTokenKind.DOC_TEXT_BLOCK));
assertTrue(diagnostics.isEmpty(), "Quotes inside documentation text block should not need escaping");
}
@Test
void shouldReportUnterminatedDocumentationTextBlock() {
final var source = "[Doc(markdown = \"\"\"unterminated)]";
final var diagnostics = DiagnosticSink.empty();
PbsLexer.lex(source, new FileId(0), diagnostics);
assertTrue(diagnostics.hasErrors(), "Lexer should report unterminated documentation text blocks");
assertEquals(LexErrors.E_LEX_UNTERMINATED_DOC_TEXT_BLOCK.name(),
diagnostics.stream().findFirst().orElseThrow().getCode());
}
@Test @Test
void shouldReportInvalidStringEscape() { void shouldReportInvalidStringEscape() {
final var source = "\"bad\\q\""; final var source = "\"bad\\q\"";