Compare commits
10 Commits
917b0e1b9a
...
725c28b3f5
| Author | SHA1 | Date | |
|---|---|---|---|
| 725c28b3f5 | |||
| d8db99eb81 | |||
| 63215ac02f | |||
| e7647165bc | |||
| f32a25eb59 | |||
| d680504c31 | |||
| aeea8ca900 | |||
| 05dd37a30a | |||
| f700e400d5 | |||
| 2b25364252 |
@ -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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","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"}]}
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0094
|
id: PLN-0094
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Extend PBS attribute parser and AST for Doc markdown payloads
|
title: Extend PBS attribute parser and AST for Doc markdown payloads
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0095
|
id: PLN-0095
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Implement documentation text block normalization
|
title: Implement documentation text block normalization
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0096
|
id: PLN-0096
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Validate Doc attribute semantics and diagnostics
|
title: Validate Doc attribute semantics and diagnostics
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0097
|
id: PLN-0097
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Attach Doc metadata to PBS semantic symbols
|
title: Attach Doc metadata to PBS semantic symbols
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0098
|
id: PLN-0098
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Expose Doc metadata through PBS editorial and LSP surfaces
|
title: Expose Doc metadata through PBS editorial and LSP surfaces
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0099
|
id: PLN-0099
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Render Doc markdown in hover composition
|
title: Render Doc markdown in hover composition
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0100
|
id: PLN-0100
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Author Doc metadata for stdlib SDK and interface declarations
|
title: Author Doc metadata for stdlib SDK and interface declarations
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0101
|
id: PLN-0101
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Protect runtime artifacts from Doc metadata lowering
|
title: Protect runtime artifacts from Doc metadata lowering
|
||||||
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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0102
|
id: PLN-0102
|
||||||
ticket: pbs-symbol-documentation-and-hover-markdown
|
ticket: pbs-symbol-documentation-and-hover-markdown
|
||||||
title: Add end-to-end Doc documentation conformance coverage
|
title: Add end-to-end Doc documentation conformance coverage
|
||||||
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]
|
||||||
@ -54,6 +54,17 @@ Validate that syntax, normalization, semantics, metadata attachment, LSP exposur
|
|||||||
- Runtime/lowering golden tests.
|
- Runtime/lowering golden tests.
|
||||||
- Manual VSCode checklist execution before final housekeeping.
|
- Manual VSCode checklist execution before final housekeeping.
|
||||||
|
|
||||||
|
## Manual VSCode Validation Checklist
|
||||||
|
|
||||||
|
This plan intentionally does not automate VSCode UI rendering. Before final housekeeping, validate manually that:
|
||||||
|
|
||||||
|
- hover renders a `Doc` paragraph below the symbol signature and kind;
|
||||||
|
- Markdown lists render from `- item` documentation lines;
|
||||||
|
- code spans such as `` `color` `` render as inline code;
|
||||||
|
- normalized indentation from `[Doc(markdown = """...""")]` does not leak leading source indentation into hover;
|
||||||
|
- undocumented symbols still show the existing hover fallback without an empty documentation block;
|
||||||
|
- completion documentation matches the same Markdown payload used by hover.
|
||||||
|
|
||||||
## Affected Artifacts
|
## Affected Artifacts
|
||||||
|
|
||||||
- PBS frontend tests and fixtures
|
- PBS frontend tests and fixtures
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class PbsDocumentationTextBlockNormalizer {
|
||||||
|
private static final String DELIMITER = "\"\"\"";
|
||||||
|
|
||||||
|
private PbsDocumentationTextBlockNormalizer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizeLexeme(final String lexeme) {
|
||||||
|
if (lexeme == null || !lexeme.startsWith(DELIMITER) || !lexeme.endsWith(DELIMITER) || lexeme.length() < 6) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return normalizeContent(lexeme.substring(DELIMITER.length(), lexeme.length() - DELIMITER.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizeContent(final String rawContent) {
|
||||||
|
final var lines = splitLines(rawContent == null ? "" : rawContent.replace("\r\n", "\n").replace('\r', '\n'));
|
||||||
|
removeIncidentalBoundaryLines(lines);
|
||||||
|
final int commonIndent = commonIndent(lines);
|
||||||
|
if (commonIndent > 0) {
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
final var line = lines.get(i);
|
||||||
|
if (line.isBlank()) {
|
||||||
|
lines.set(i, "");
|
||||||
|
} else {
|
||||||
|
lines.set(i, line.substring(Math.min(commonIndent, leadingWhitespace(line))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.join("\n", lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> splitLines(final String text) {
|
||||||
|
final var lines = new ArrayList<String>();
|
||||||
|
final var parts = text.split("\n", -1);
|
||||||
|
for (final var part : parts) {
|
||||||
|
lines.add(part);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeIncidentalBoundaryLines(final List<String> lines) {
|
||||||
|
if (!lines.isEmpty() && lines.getFirst().isBlank()) {
|
||||||
|
lines.removeFirst();
|
||||||
|
}
|
||||||
|
if (!lines.isEmpty() && lines.getLast().isBlank()) {
|
||||||
|
lines.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int commonIndent(final List<String> lines) {
|
||||||
|
var common = Integer.MAX_VALUE;
|
||||||
|
for (final var line : lines) {
|
||||||
|
if (line.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
common = Math.min(common, leadingWhitespace(line));
|
||||||
|
}
|
||||||
|
return common == Integer.MAX_VALUE ? 0 : common;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int leadingWhitespace(final String line) {
|
||||||
|
var count = 0;
|
||||||
|
while (count < line.length() && Character.isWhitespace(line.charAt(count))) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
@ -78,6 +78,7 @@ public final class PbsAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed interface AttributeValue permits AttributeStringValue,
|
public sealed interface AttributeValue permits AttributeStringValue,
|
||||||
|
AttributeDocTextBlockValue,
|
||||||
AttributeIntValue,
|
AttributeIntValue,
|
||||||
AttributeBoolValue,
|
AttributeBoolValue,
|
||||||
AttributeErrorValue {
|
AttributeErrorValue {
|
||||||
@ -89,6 +90,11 @@ public final class PbsAst {
|
|||||||
Span span) implements AttributeValue {
|
Span span) implements AttributeValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record AttributeDocTextBlockValue(
|
||||||
|
String value,
|
||||||
|
Span span) implements AttributeValue {
|
||||||
|
}
|
||||||
|
|
||||||
public record AttributeIntValue(
|
public record AttributeIntValue(
|
||||||
long value,
|
long value,
|
||||||
Span span) implements AttributeValue {
|
Span span) implements AttributeValue {
|
||||||
@ -132,6 +138,7 @@ public final class PbsAst {
|
|||||||
ReturnKind returnKind,
|
ReturnKind returnKind,
|
||||||
TypeRef returnType,
|
TypeRef returnType,
|
||||||
TypeRef resultErrorType,
|
TypeRef resultErrorType,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
LifecycleMarker lifecycleMarker,
|
LifecycleMarker lifecycleMarker,
|
||||||
Block body,
|
Block body,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
@ -143,18 +150,21 @@ public final class PbsAst {
|
|||||||
ReadOnlyList<FunctionDecl> methods,
|
ReadOnlyList<FunctionDecl> methods,
|
||||||
ReadOnlyList<CtorDecl> ctors,
|
ReadOnlyList<CtorDecl> ctors,
|
||||||
boolean hasBody,
|
boolean hasBody,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ContractDecl(
|
public record ContractDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<FunctionSignature> signatures,
|
ReadOnlyList<FunctionSignature> signatures,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HostDecl(
|
public record HostDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<FunctionSignature> signatures,
|
ReadOnlyList<FunctionSignature> signatures,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,18 +179,21 @@ public final class PbsAst {
|
|||||||
public record ServiceDecl(
|
public record ServiceDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<FunctionDecl> methods,
|
ReadOnlyList<FunctionDecl> methods,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ErrorDecl(
|
public record ErrorDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<String> cases,
|
ReadOnlyList<String> cases,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record EnumDecl(
|
public record EnumDecl(
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<EnumCase> cases,
|
ReadOnlyList<EnumCase> cases,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +203,7 @@ public final class PbsAst {
|
|||||||
ReturnKind returnKind,
|
ReturnKind returnKind,
|
||||||
TypeRef returnType,
|
TypeRef returnType,
|
||||||
TypeRef resultErrorType,
|
TypeRef resultErrorType,
|
||||||
|
ReadOnlyList<Attribute> attributes,
|
||||||
Span span) implements TopDecl {
|
Span span) implements TopDecl {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -84,6 +84,10 @@ final class PbsAttributeParser {
|
|||||||
final var token = cursor.previous();
|
final var token = cursor.previous();
|
||||||
return new PbsAst.AttributeStringValue(token.lexeme(), context.span(token.start(), token.end()));
|
return new PbsAst.AttributeStringValue(token.lexeme(), context.span(token.start(), token.end()));
|
||||||
}
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.DOC_TEXT_BLOCK)) {
|
||||||
|
final var token = cursor.previous();
|
||||||
|
return new PbsAst.AttributeDocTextBlockValue(token.lexeme(), context.span(token.start(), token.end()));
|
||||||
|
}
|
||||||
if (cursor.match(PbsTokenKind.INT_LITERAL)) {
|
if (cursor.match(PbsTokenKind.INT_LITERAL)) {
|
||||||
final var token = cursor.previous();
|
final var token = cursor.previous();
|
||||||
final var parsedLong = parseLongOrNull(token.lexeme());
|
final var parsedLong = parseLongOrNull(token.lexeme());
|
||||||
|
|||||||
@ -108,28 +108,28 @@ final class PbsDeclarationParser {
|
|||||||
final PbsToken declareToken,
|
final PbsToken declareToken,
|
||||||
final ReadOnlyList<PbsAst.Attribute> pendingAttributes) {
|
final ReadOnlyList<PbsAst.Attribute> pendingAttributes) {
|
||||||
if (cursor.match(PbsTokenKind.STRUCT)) {
|
if (cursor.match(PbsTokenKind.STRUCT)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "struct declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "struct declarations");
|
||||||
return parseStructDeclaration(declareToken);
|
return parseStructDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.CONTRACT)) {
|
if (cursor.match(PbsTokenKind.CONTRACT)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "contract declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "contract declarations");
|
||||||
return parseContractDeclaration(declareToken);
|
return parseContractDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.SERVICE)) {
|
if (cursor.match(PbsTokenKind.SERVICE)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "service declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "service declarations");
|
||||||
return parseServiceDeclaration(declareToken);
|
return parseServiceDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.ERROR)) {
|
if (cursor.match(PbsTokenKind.ERROR)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "error declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "error declarations");
|
||||||
return parseErrorDeclaration(declareToken);
|
return parseErrorDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.ENUM)) {
|
if (cursor.match(PbsTokenKind.ENUM)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "enum declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "enum declarations");
|
||||||
return parseEnumDeclaration(declareToken);
|
return parseEnumDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.CALLBACK)) {
|
if (cursor.match(PbsTokenKind.CALLBACK)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "callback declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "callback declarations");
|
||||||
return parseCallbackDeclaration(declareToken);
|
return parseCallbackDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.GLOBAL)) {
|
if (cursor.match(PbsTokenKind.GLOBAL)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "global declarations");
|
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "global declarations");
|
||||||
@ -139,14 +139,14 @@ final class PbsDeclarationParser {
|
|||||||
return parseConstDeclaration(declareToken, pendingAttributes);
|
return parseConstDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.HOST)) {
|
if (cursor.match(PbsTokenKind.HOST)) {
|
||||||
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "host declarations");
|
rejectUnsupportedDeclarationAttributes(pendingAttributes, "host declarations");
|
||||||
if (isNotInterfaceMode()) {
|
if (isNotInterfaceMode()) {
|
||||||
final var end = declarationTerminatorDelegate.consume();
|
final var end = declarationTerminatorDelegate.consume();
|
||||||
report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION,
|
report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION,
|
||||||
"'declare host' is reserved and not supported in ordinary source modules");
|
"'declare host' is reserved and not supported in ordinary source modules");
|
||||||
return new PbsAst.InvalidDecl("reserved declare host", span(declareToken.start(), end));
|
return new PbsAst.InvalidDecl("reserved declare host", span(declareToken.start(), end));
|
||||||
}
|
}
|
||||||
return parseHostDeclaration(declareToken);
|
return parseHostDeclaration(declareToken, pendingAttributes);
|
||||||
}
|
}
|
||||||
if (cursor.match(PbsTokenKind.BUILTIN)) {
|
if (cursor.match(PbsTokenKind.BUILTIN)) {
|
||||||
consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration");
|
consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration");
|
||||||
@ -211,14 +211,16 @@ final class PbsDeclarationParser {
|
|||||||
span(implementsToken.start(), end));
|
span(implementsToken.start(), end));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.HostDecl parseHostDeclaration(final PbsToken declareToken) {
|
private PbsAst.HostDecl parseHostDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected host declaration name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected host declaration name");
|
||||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start host declaration body");
|
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start host declaration body");
|
||||||
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
|
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
|
||||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||||
var attributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
var signatureAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
||||||
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
||||||
attributes = attributeParser.parseAttributeList();
|
signatureAttributes = attributeParser.parseAttributeList();
|
||||||
}
|
}
|
||||||
if (!cursor.match(PbsTokenKind.FN)) {
|
if (!cursor.match(PbsTokenKind.FN)) {
|
||||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||||
@ -226,12 +228,13 @@ final class PbsDeclarationParser {
|
|||||||
cursor.advance();
|
cursor.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
signatures.add(parseFunctionSignature(cursor.previous(), attributes));
|
signatures.add(parseFunctionSignature(cursor.previous(), signatureAttributes));
|
||||||
}
|
}
|
||||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end host declaration body");
|
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end host declaration body");
|
||||||
return new PbsAst.HostDecl(
|
return new PbsAst.HostDecl(
|
||||||
name.lexeme(),
|
name.lexeme(),
|
||||||
ReadOnlyList.wrap(signatures),
|
ReadOnlyList.wrap(signatures),
|
||||||
|
attributes,
|
||||||
span(declareToken.start(), rightBrace.end()));
|
span(declareToken.start(), rightBrace.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +311,9 @@ final class PbsDeclarationParser {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) {
|
private PbsAst.StructDecl parseStructDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration");
|
||||||
final var fields = parseStructFields();
|
final var fields = parseStructFields();
|
||||||
@ -340,6 +345,7 @@ final class PbsDeclarationParser {
|
|||||||
ReadOnlyList.wrap(methods),
|
ReadOnlyList.wrap(methods),
|
||||||
ReadOnlyList.wrap(ctors),
|
ReadOnlyList.wrap(ctors),
|
||||||
hasBody,
|
hasBody,
|
||||||
|
attributes,
|
||||||
span(declareToken.start(), end));
|
span(declareToken.start(), end));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,10 +388,17 @@ final class PbsDeclarationParser {
|
|||||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||||
final var ctors = new ArrayList<PbsAst.CtorDecl>();
|
final var ctors = new ArrayList<PbsAst.CtorDecl>();
|
||||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||||
|
var attributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
||||||
|
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
||||||
|
attributes = attributeParser.parseAttributeList();
|
||||||
|
}
|
||||||
if (cursor.match(PbsTokenKind.FN)) {
|
if (cursor.match(PbsTokenKind.FN)) {
|
||||||
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
|
methods.add(parseFunctionLike(cursor.previous(), attributes));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!attributes.isEmpty()) {
|
||||||
|
reportAttributesNotAllowed(attributes, "Attributes are valid only on struct methods in struct bodies");
|
||||||
|
}
|
||||||
if (cursor.match(PbsTokenKind.CTOR)) {
|
if (cursor.match(PbsTokenKind.CTOR)) {
|
||||||
ctors.add(parseCtorDeclarationInBody(cursor.previous()));
|
ctors.add(parseCtorDeclarationInBody(cursor.previous()));
|
||||||
continue;
|
continue;
|
||||||
@ -411,17 +424,19 @@ final class PbsDeclarationParser {
|
|||||||
span(ctorToken.start(), body.span().getEnd()));
|
span(ctorToken.start(), body.span().getEnd()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) {
|
private PbsAst.ContractDecl parseContractDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name");
|
||||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body");
|
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body");
|
||||||
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
|
final var signatures = new ArrayList<PbsAst.FunctionSignature>();
|
||||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||||
var attributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
var signatureAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
||||||
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
||||||
attributes = attributeParser.parseAttributeList();
|
signatureAttributes = attributeParser.parseAttributeList();
|
||||||
if (isOrdinaryMode()) {
|
if (isOrdinaryMode()) {
|
||||||
reportAttributesNotAllowed(attributes, "Attributes are not allowed in ordinary .pbs source modules");
|
reportAttributesNotAllowed(signatureAttributes, "Attributes are not allowed in ordinary .pbs source modules");
|
||||||
attributes = ReadOnlyList.empty();
|
signatureAttributes = ReadOnlyList.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!cursor.match(PbsTokenKind.FN)) {
|
if (!cursor.match(PbsTokenKind.FN)) {
|
||||||
@ -430,30 +445,41 @@ final class PbsDeclarationParser {
|
|||||||
cursor.advance();
|
cursor.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
signatures.add(parseFunctionSignature(cursor.previous(), attributes));
|
signatures.add(parseFunctionSignature(cursor.previous(), signatureAttributes));
|
||||||
}
|
}
|
||||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end contract body");
|
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end contract body");
|
||||||
return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), span(declareToken.start(), rightBrace.end()));
|
return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), attributes, span(declareToken.start(), rightBrace.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.ServiceDecl parseServiceDeclaration(final PbsToken declareToken) {
|
private PbsAst.ServiceDecl parseServiceDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name");
|
||||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body");
|
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body");
|
||||||
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
final var methods = new ArrayList<PbsAst.FunctionDecl>();
|
||||||
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
|
||||||
|
var methodAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
|
||||||
|
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
|
||||||
|
methodAttributes = attributeParser.parseAttributeList();
|
||||||
|
}
|
||||||
if (!cursor.match(PbsTokenKind.FN)) {
|
if (!cursor.match(PbsTokenKind.FN)) {
|
||||||
|
if (!methodAttributes.isEmpty()) {
|
||||||
|
reportAttributesNotAllowed(methodAttributes, "Attributes are valid only on service methods in service bodies");
|
||||||
|
}
|
||||||
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE,
|
||||||
"Service body accepts only method declarations");
|
"Service body accepts only method declarations");
|
||||||
cursor.advance();
|
cursor.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty()));
|
methods.add(parseFunctionLike(cursor.previous(), methodAttributes));
|
||||||
}
|
}
|
||||||
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), attributes, span(declareToken.start(), rightBrace.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.ErrorDecl parseErrorDeclaration(final PbsToken declareToken) {
|
private PbsAst.ErrorDecl parseErrorDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name");
|
||||||
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body");
|
consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body");
|
||||||
final var cases = new ArrayList<String>();
|
final var cases = new ArrayList<String>();
|
||||||
@ -463,10 +489,12 @@ final class PbsDeclarationParser {
|
|||||||
consume(PbsTokenKind.SEMICOLON, "Expected ';' after error case label");
|
consume(PbsTokenKind.SEMICOLON, "Expected ';' after error case label");
|
||||||
}
|
}
|
||||||
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end error body");
|
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end error body");
|
||||||
return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), rightBrace.end()));
|
return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), attributes, span(declareToken.start(), rightBrace.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.EnumDecl parseEnumDeclaration(final PbsToken declareToken) {
|
private PbsAst.EnumDecl parseEnumDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name");
|
||||||
|
|
||||||
@ -522,7 +550,7 @@ final class PbsDeclarationParser {
|
|||||||
|
|
||||||
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), attributes, span(declareToken.start(), Math.max(rightParen.end(), semicolon.end())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.FunctionDecl parseFunctionLike(
|
private PbsAst.FunctionDecl parseFunctionLike(
|
||||||
@ -543,6 +571,7 @@ final class PbsDeclarationParser {
|
|||||||
returnSpec.kind(),
|
returnSpec.kind(),
|
||||||
returnSpec.returnType(),
|
returnSpec.returnType(),
|
||||||
returnSpec.resultErrorType(),
|
returnSpec.resultErrorType(),
|
||||||
|
pendingAttributes,
|
||||||
lifecycleMarker,
|
lifecycleMarker,
|
||||||
body,
|
body,
|
||||||
span(fnToken.start(), body.span().getEnd()));
|
span(fnToken.start(), body.span().getEnd()));
|
||||||
@ -585,7 +614,9 @@ final class PbsDeclarationParser {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.CallbackDecl parseCallbackDeclaration(final PbsToken declareToken) {
|
private PbsAst.CallbackDecl parseCallbackDeclaration(
|
||||||
|
final PbsToken declareToken,
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name");
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name");
|
||||||
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name");
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name");
|
||||||
final var parameters = typeParser.parseParametersUntilRightParen();
|
final var parameters = typeParser.parseParametersUntilRightParen();
|
||||||
@ -600,6 +631,7 @@ final class PbsDeclarationParser {
|
|||||||
returnSpec.kind(),
|
returnSpec.kind(),
|
||||||
returnSpec.returnType(),
|
returnSpec.returnType(),
|
||||||
returnSpec.resultErrorType(),
|
returnSpec.resultErrorType(),
|
||||||
|
attributes,
|
||||||
span(declareToken.start(), semicolon.end()));
|
span(declareToken.start(), semicolon.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,6 +683,20 @@ final class PbsDeclarationParser {
|
|||||||
reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface);
|
reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void rejectUnsupportedDeclarationAttributes(
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
|
final String targetSurface) {
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if ("Doc".equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(),
|
||||||
|
ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(),
|
||||||
|
"Only [Doc] is allowed before " + targetSurface,
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void reportAttributesNotAllowed(
|
private void reportAttributesNotAllowed(
|
||||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
final String message) {
|
final String message) {
|
||||||
@ -676,14 +722,16 @@ final class PbsDeclarationParser {
|
|||||||
return PbsAst.LifecycleMarker.NONE;
|
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;
|
PbsAst.LifecycleMarker marker = PbsAst.LifecycleMarker.NONE;
|
||||||
for (final var attribute : attributes) {
|
for (final var attribute : attributes) {
|
||||||
final PbsAst.LifecycleMarker nextMarker;
|
final PbsAst.LifecycleMarker nextMarker;
|
||||||
|
if ("Doc".equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isOrdinaryMode()) {
|
||||||
|
reportAttributesNotAllowed(attributes, "Only [Doc] is allowed before interface executable methods");
|
||||||
|
return PbsAst.LifecycleMarker.NONE;
|
||||||
|
}
|
||||||
if ("Init".equals(attribute.name())) {
|
if ("Init".equals(attribute.name())) {
|
||||||
nextMarker = PbsAst.LifecycleMarker.INIT;
|
nextMarker = PbsAst.LifecycleMarker.INIT;
|
||||||
} else if ("Frame".equals(attribute.name())) {
|
} else if ("Frame".equals(attribute.name())) {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
|
import p.studio.compiler.pbs.PbsDocumentationTextBlockNormalizer;
|
||||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.source.Span;
|
import p.studio.compiler.source.Span;
|
||||||
@ -19,6 +20,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
|
private static final String ATTR_INIT_ALLOWED = "InitAllowed";
|
||||||
private static final String ATTR_ASSET_LOWERING = "AssetLowering";
|
private static final String ATTR_ASSET_LOWERING = "AssetLowering";
|
||||||
|
private static final String ATTR_DOC = "Doc";
|
||||||
|
|
||||||
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||||
ATTR_HOST,
|
ATTR_HOST,
|
||||||
@ -27,7 +29,8 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
ATTR_BUILTIN_CONST,
|
ATTR_BUILTIN_CONST,
|
||||||
ATTR_INTRINSIC_CALL,
|
ATTR_INTRINSIC_CALL,
|
||||||
ATTR_INIT_ALLOWED,
|
ATTR_INIT_ALLOWED,
|
||||||
ATTR_ASSET_LOWERING);
|
ATTR_ASSET_LOWERING,
|
||||||
|
ATTR_DOC);
|
||||||
|
|
||||||
private final NameTable nameTable;
|
private final NameTable nameTable;
|
||||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||||
@ -60,6 +63,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
|
|
||||||
for (final var topDecl : ast.topDecls()) {
|
for (final var topDecl : ast.topDecls()) {
|
||||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
validateDocAttributes(functionDecl.attributes(), "function declarations", diagnostics);
|
||||||
if (interfaceModule) {
|
if (interfaceModule) {
|
||||||
reportInterfaceNonDeclarativeDecl(
|
reportInterfaceNonDeclarativeDecl(
|
||||||
functionDecl.span(),
|
functionDecl.span(),
|
||||||
@ -82,6 +86,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
validateDocAttributes(structDecl.attributes(), "struct declarations", diagnostics);
|
||||||
binder.registerType(structDecl.name(), structDecl.span(), "struct");
|
binder.registerType(structDecl.name(), structDecl.span(), "struct");
|
||||||
if (interfaceModule && (structDecl.hasBody() || !structDecl.methods().isEmpty() || !structDecl.ctors().isEmpty())) {
|
if (interfaceModule && (structDecl.hasBody() || !structDecl.methods().isEmpty() || !structDecl.ctors().isEmpty())) {
|
||||||
reportInterfaceNonDeclarativeDecl(
|
reportInterfaceNonDeclarativeDecl(
|
||||||
@ -94,6 +99,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
validateDocAttributes(serviceDecl.attributes(), "service declarations", diagnostics);
|
||||||
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
||||||
binder.registerVisibleTopLevelSurface(serviceDecl.name(), serviceDecl.span(), "service");
|
binder.registerVisibleTopLevelSurface(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
@ -102,10 +108,12 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
validateDocAttributes(contractDecl.attributes(), "contract declarations", diagnostics);
|
||||||
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
|
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
|
||||||
if (interfaceModule) {
|
if (interfaceModule) {
|
||||||
for (final var signature : contractDecl.signatures()) {
|
for (final var signature : contractDecl.signatures()) {
|
||||||
validateReservedAttributeTarget(signature.attributes(), "contract signature", diagnostics);
|
validateDocAttributes(signature.attributes(), "contract signatures", diagnostics);
|
||||||
|
validateReservedAttributeTargetExceptDoc(signature.attributes(), "contract signature", diagnostics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
validateContractDeclaration(contractDecl, binder, rules);
|
validateContractDeclaration(contractDecl, binder, rules);
|
||||||
@ -113,6 +121,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
validateDocAttributes(hostDecl.attributes(), "host declarations", diagnostics);
|
||||||
binder.registerHostOwner(hostDecl.name(), hostDecl.span(), "host owner");
|
binder.registerHostOwner(hostDecl.name(), hostDecl.span(), "host owner");
|
||||||
validateHostDeclaration(hostDecl, binder, rules, interfaceModule, diagnostics);
|
validateHostDeclaration(hostDecl, binder, rules, interfaceModule, diagnostics);
|
||||||
continue;
|
continue;
|
||||||
@ -125,18 +134,21 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
validateDocAttributes(errorDecl.attributes(), "error declarations", diagnostics);
|
||||||
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
|
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
|
||||||
rules.validateErrorDeclaration(errorDecl);
|
rules.validateErrorDeclaration(errorDecl);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
validateDocAttributes(enumDecl.attributes(), "enum declarations", diagnostics);
|
||||||
binder.registerType(enumDecl.name(), enumDecl.span(), "enum");
|
binder.registerType(enumDecl.name(), enumDecl.span(), "enum");
|
||||||
rules.validateEnumDeclaration(enumDecl);
|
rules.validateEnumDeclaration(enumDecl);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
validateDocAttributes(callbackDecl.attributes(), "callback declarations", diagnostics);
|
||||||
binder.registerType(callbackDecl.name(), callbackDecl.span(), "callback");
|
binder.registerType(callbackDecl.name(), callbackDecl.span(), "callback");
|
||||||
rules.validateCallbackDeclaration(callbackDecl);
|
rules.validateCallbackDeclaration(callbackDecl);
|
||||||
continue;
|
continue;
|
||||||
@ -187,6 +199,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
false);
|
false);
|
||||||
final var methodScope = PbsCallableScope.structMethods(ownerNameId);
|
final var methodScope = PbsCallableScope.structMethods(ownerNameId);
|
||||||
for (final var method : structDecl.methods()) {
|
for (final var method : structDecl.methods()) {
|
||||||
|
validateDocAttributes(method.attributes(), "struct methods", diagnostics);
|
||||||
validateCallable(
|
validateCallable(
|
||||||
methodScope,
|
methodScope,
|
||||||
method.name(),
|
method.name(),
|
||||||
@ -226,6 +239,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
final var methodScope = PbsCallableScope.serviceMethods(nameTable.register(serviceDecl.name()));
|
final var methodScope = PbsCallableScope.serviceMethods(nameTable.register(serviceDecl.name()));
|
||||||
for (final var method : serviceDecl.methods()) {
|
for (final var method : serviceDecl.methods()) {
|
||||||
|
validateDocAttributes(method.attributes(), "service methods", diagnostics);
|
||||||
validateCallable(
|
validateCallable(
|
||||||
methodScope,
|
methodScope,
|
||||||
method.name(),
|
method.name(),
|
||||||
@ -494,12 +508,13 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
final PbsAst.ConstDecl constDecl,
|
final PbsAst.ConstDecl constDecl,
|
||||||
final boolean interfaceModule,
|
final boolean interfaceModule,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
validateDocAttributes(constDecl.attributes(), "const declarations", diagnostics);
|
||||||
final var builtinConstAttributes = attributesNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
|
final var builtinConstAttributes = attributesNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
|
||||||
for (final var attribute : constDecl.attributes()) {
|
for (final var attribute : constDecl.attributes()) {
|
||||||
if (!isReservedAttribute(attribute.name())) {
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ATTR_BUILTIN_CONST.equals(attribute.name())) {
|
if (ATTR_BUILTIN_CONST.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
@ -540,6 +555,7 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private void validateHostSignatureAttributes(
|
private void validateHostSignatureAttributes(
|
||||||
final PbsAst.FunctionSignature signature,
|
final PbsAst.FunctionSignature signature,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
validateDocAttributes(signature.attributes(), "host signatures", diagnostics);
|
||||||
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
|
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
|
||||||
final var initAllowedAttributes = attributesNamed(signature.attributes(), ATTR_INIT_ALLOWED);
|
final var initAllowedAttributes = attributesNamed(signature.attributes(), ATTR_INIT_ALLOWED);
|
||||||
final var assetLoweringAttributes = attributesNamed(signature.attributes(), ATTR_ASSET_LOWERING);
|
final var assetLoweringAttributes = attributesNamed(signature.attributes(), ATTR_ASSET_LOWERING);
|
||||||
@ -550,7 +566,8 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
if (ATTR_HOST.equals(attribute.name())
|
if (ATTR_HOST.equals(attribute.name())
|
||||||
|| ATTR_CAPABILITY.equals(attribute.name())
|
|| ATTR_CAPABILITY.equals(attribute.name())
|
||||||
|| ATTR_INIT_ALLOWED.equals(attribute.name())
|
|| ATTR_INIT_ALLOWED.equals(attribute.name())
|
||||||
|| ATTR_ASSET_LOWERING.equals(attribute.name())) {
|
|| ATTR_ASSET_LOWERING.equals(attribute.name())
|
||||||
|
|| ATTR_DOC.equals(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
@ -597,12 +614,13 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private void validateBuiltinTypeAttribute(
|
private void validateBuiltinTypeAttribute(
|
||||||
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
validateDocAttributes(builtinTypeDecl.attributes(), "builtin type declarations", diagnostics);
|
||||||
final var builtinTypeAttributes = attributesNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
final var builtinTypeAttributes = attributesNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
||||||
for (final var attribute : builtinTypeDecl.attributes()) {
|
for (final var attribute : builtinTypeDecl.attributes()) {
|
||||||
if (!isReservedAttribute(attribute.name())) {
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ATTR_BUILTIN_TYPE.equals(attribute.name())) {
|
if (ATTR_BUILTIN_TYPE.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
@ -645,12 +663,13 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private void validateIntrinsicCallAttributes(
|
private void validateIntrinsicCallAttributes(
|
||||||
final PbsAst.FunctionSignature signature,
|
final PbsAst.FunctionSignature signature,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
validateDocAttributes(signature.attributes(), "builtin method signatures", diagnostics);
|
||||||
final var intrinsicAttributes = attributesNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
|
final var intrinsicAttributes = attributesNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
|
||||||
for (final var attribute : signature.attributes()) {
|
for (final var attribute : signature.attributes()) {
|
||||||
if (!isReservedAttribute(attribute.name())) {
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ATTR_INTRINSIC_CALL.equals(attribute.name())) {
|
if (ATTR_INTRINSIC_CALL.equals(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
@ -770,6 +789,47 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateDocAttributes(
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
|
final String targetSurface,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var docAttributes = attributesNamed(attributes, ATTR_DOC);
|
||||||
|
if (docAttributes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (docAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < docAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(docAttributes.get(i), ATTR_DOC, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateDocAttributeShape(docAttributes.getFirst(), targetSurface, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDocAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final String targetSurface,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("markdown"), Set.of(), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var value = args.get("markdown");
|
||||||
|
if (!(value instanceof PbsAst.AttributeDocTextBlockValue docValue)) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Doc.markdown on %s must be a documentation text block".formatted(targetSurface),
|
||||||
|
attribute.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme(docValue.value());
|
||||||
|
if (normalized.isBlank()) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Doc.markdown on %s must not be empty".formatted(targetSurface),
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||||
final PbsAst.Attribute attribute,
|
final PbsAst.Attribute attribute,
|
||||||
final Set<String> requiredNames,
|
final Set<String> requiredNames,
|
||||||
@ -911,6 +971,21 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateReservedAttributeTargetExceptDoc(
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
|
final String targetSurface,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if (!isReservedAttribute(attribute.name()) || ATTR_DOC.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on %s".formatted(attribute.name(), targetSurface),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void reportInvalidReservedAttributeTarget(
|
private void reportInvalidReservedAttributeTarget(
|
||||||
final PbsAst.Attribute attribute,
|
final PbsAst.Attribute attribute,
|
||||||
final String message,
|
final String message,
|
||||||
|
|||||||
@ -6,12 +6,22 @@ public record PbsEditorialCompletionCandidate(
|
|||||||
String label,
|
String label,
|
||||||
PbsEditorialSymbolKind kind,
|
PbsEditorialSymbolKind kind,
|
||||||
String detail,
|
String detail,
|
||||||
String origin) {
|
String origin,
|
||||||
|
String documentation) {
|
||||||
|
public PbsEditorialCompletionCandidate(
|
||||||
|
final String label,
|
||||||
|
final PbsEditorialSymbolKind kind,
|
||||||
|
final String detail,
|
||||||
|
final String origin) {
|
||||||
|
this(label, kind, detail, origin, "");
|
||||||
|
}
|
||||||
|
|
||||||
public PbsEditorialCompletionCandidate {
|
public PbsEditorialCompletionCandidate {
|
||||||
label = requireText(label, "label");
|
label = requireText(label, "label");
|
||||||
kind = Objects.requireNonNull(kind, "kind");
|
kind = Objects.requireNonNull(kind, "kind");
|
||||||
detail = normalize(detail);
|
detail = normalize(detail);
|
||||||
origin = normalize(origin);
|
origin = normalize(origin);
|
||||||
|
documentation = normalize(documentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String requireText(
|
private static String requireText(
|
||||||
|
|||||||
@ -482,10 +482,11 @@ public final class PbsEditorialSupportService {
|
|||||||
}
|
}
|
||||||
for (final var method : structInfo.methods().entrySet()) {
|
for (final var method : structInfo.methods().entrySet()) {
|
||||||
completions.put(method.getKey(), new PbsEditorialCompletionCandidate(
|
completions.put(method.getKey(), new PbsEditorialCompletionCandidate(
|
||||||
method.getKey(),
|
method.getKey(),
|
||||||
PbsEditorialSymbolKind.METHOD,
|
PbsEditorialSymbolKind.METHOD,
|
||||||
formatCallableDetails(method.getValue()),
|
formatCallableDetails(method.getValue()),
|
||||||
receiverType.name()));
|
receiverType.name(),
|
||||||
|
documentationFromCallables(method.getValue())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (receiverType.kind() == Kind.SERVICE || receiverType.kind() == Kind.CONTRACT) {
|
} else if (receiverType.kind() == Kind.SERVICE || receiverType.kind() == Kind.CONTRACT) {
|
||||||
@ -494,10 +495,11 @@ public final class PbsEditorialSupportService {
|
|||||||
: Optional.ofNullable(model.contracts.get(receiverType.name())).map(ContractInfo::methods).orElse(Map.of());
|
: Optional.ofNullable(model.contracts.get(receiverType.name())).map(ContractInfo::methods).orElse(Map.of());
|
||||||
for (final var method : methods.entrySet()) {
|
for (final var method : methods.entrySet()) {
|
||||||
completions.put(method.getKey(), new PbsEditorialCompletionCandidate(
|
completions.put(method.getKey(), new PbsEditorialCompletionCandidate(
|
||||||
method.getKey(),
|
method.getKey(),
|
||||||
PbsEditorialSymbolKind.METHOD,
|
PbsEditorialSymbolKind.METHOD,
|
||||||
formatCallableDetails(method.getValue()),
|
formatCallableDetails(method.getValue()),
|
||||||
receiverType.name()));
|
receiverType.name(),
|
||||||
|
documentationFromCallables(method.getValue())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return List.copyOf(completions.values());
|
return List.copyOf(completions.values());
|
||||||
@ -587,7 +589,7 @@ public final class PbsEditorialSupportService {
|
|||||||
formatCallableDetails(methods),
|
formatCallableDetails(methods),
|
||||||
receiverType.name(),
|
receiverType.name(),
|
||||||
signaturesFromCallables(methods),
|
signaturesFromCallables(methods),
|
||||||
""));
|
documentationFromCallables(methods)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (receiverType.kind() == Kind.SERVICE || receiverType.kind() == Kind.CONTRACT) {
|
if (receiverType.kind() == Kind.SERVICE || receiverType.kind() == Kind.CONTRACT) {
|
||||||
@ -602,7 +604,7 @@ public final class PbsEditorialSupportService {
|
|||||||
formatCallableDetails(signatures),
|
formatCallableDetails(signatures),
|
||||||
receiverType.name(),
|
receiverType.name(),
|
||||||
signaturesFromCallables(signatures),
|
signaturesFromCallables(signatures),
|
||||||
""));
|
documentationFromCallables(signatures)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@ -663,7 +665,7 @@ public final class PbsEditorialSupportService {
|
|||||||
detail,
|
detail,
|
||||||
topDeclSymbol.origin(),
|
topDeclSymbol.origin(),
|
||||||
signatures,
|
signatures,
|
||||||
"");
|
documentationFromTopDecl(topDeclSymbol.decl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsEditorialResolvedSymbol constructorSymbolFromTopDecl(
|
private PbsEditorialResolvedSymbol constructorSymbolFromTopDecl(
|
||||||
@ -678,7 +680,68 @@ public final class PbsEditorialSupportService {
|
|||||||
detail,
|
detail,
|
||||||
topDeclSymbol.origin(),
|
topDeclSymbol.origin(),
|
||||||
signatures,
|
signatures,
|
||||||
"");
|
documentationFromTopDecl(topDeclSymbol.decl()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String documentationFromCallables(final List<CallableSymbol> callables) {
|
||||||
|
for (final var callable : callables) {
|
||||||
|
if (!callable.documentation().isBlank()) {
|
||||||
|
return callable.documentation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String documentationFromTopDecl(final PbsAst.TopDecl topDecl) {
|
||||||
|
return documentationFromAttributes(attributesOf(topDecl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReadOnlyList<PbsAst.Attribute> attributesOf(final PbsAst.TopDecl topDecl) {
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
return functionDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
return structDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
return builtinTypeDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
return serviceDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
return hostDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
return contractDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
return callbackDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
return enumDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
return errorDecl.attributes();
|
||||||
|
}
|
||||||
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
|
return constDecl.attributes();
|
||||||
|
}
|
||||||
|
return ReadOnlyList.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String documentationFromAttributes(final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if (!"Doc".equals(attribute.name()) || attribute.arguments().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var argument = attribute.arguments().getFirst();
|
||||||
|
if (!"markdown".equals(argument.name()) || !(argument.value() instanceof PbsAst.AttributeDocTextBlockValue docValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return p.studio.compiler.pbs.PbsDocumentationTextBlockNormalizer.normalizeLexeme(docValue.value());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsEditorialCompletionCandidate completionFromTopDecl(final TopDeclSymbol topDeclSymbol) {
|
private PbsEditorialCompletionCandidate completionFromTopDecl(final TopDeclSymbol topDeclSymbol) {
|
||||||
@ -690,7 +753,8 @@ public final class PbsEditorialSupportService {
|
|||||||
topDeclSymbol.localName(),
|
topDeclSymbol.localName(),
|
||||||
symbolKind(topDeclSymbol.decl()),
|
symbolKind(topDeclSymbol.decl()),
|
||||||
detail,
|
detail,
|
||||||
topDeclSymbol.origin());
|
topDeclSymbol.origin(),
|
||||||
|
documentationFromTopDecl(topDeclSymbol.decl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PbsEditorialSignature> signaturesForTopDecl(final PbsAst.TopDecl topDecl) {
|
private List<PbsEditorialSignature> signaturesForTopDecl(final PbsAst.TopDecl topDecl) {
|
||||||
@ -1155,6 +1219,7 @@ public final class PbsEditorialSupportService {
|
|||||||
functionDecl.returnKind(),
|
functionDecl.returnKind(),
|
||||||
functionDecl.returnType(),
|
functionDecl.returnType(),
|
||||||
functionDecl.resultErrorType(),
|
functionDecl.resultErrorType(),
|
||||||
|
functionDecl.attributes(),
|
||||||
functionDecl.lifecycleMarker(),
|
functionDecl.lifecycleMarker(),
|
||||||
functionDecl.body(),
|
functionDecl.body(),
|
||||||
functionDecl.span());
|
functionDecl.span());
|
||||||
@ -1166,6 +1231,7 @@ public final class PbsEditorialSupportService {
|
|||||||
structDecl.methods(),
|
structDecl.methods(),
|
||||||
structDecl.ctors(),
|
structDecl.ctors(),
|
||||||
structDecl.hasBody(),
|
structDecl.hasBody(),
|
||||||
|
structDecl.attributes(),
|
||||||
structDecl.span());
|
structDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
@ -1177,13 +1243,13 @@ public final class PbsEditorialSupportService {
|
|||||||
builtinTypeDecl.span());
|
builtinTypeDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
return new PbsAst.ServiceDecl(newName, serviceDecl.methods(), serviceDecl.span());
|
return new PbsAst.ServiceDecl(newName, serviceDecl.methods(), serviceDecl.attributes(), serviceDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
return new PbsAst.HostDecl(newName, hostDecl.signatures(), hostDecl.span());
|
return new PbsAst.HostDecl(newName, hostDecl.signatures(), hostDecl.attributes(), hostDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
return new PbsAst.ContractDecl(newName, contractDecl.signatures(), contractDecl.span());
|
return new PbsAst.ContractDecl(newName, contractDecl.signatures(), contractDecl.attributes(), contractDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
return new PbsAst.CallbackDecl(
|
return new PbsAst.CallbackDecl(
|
||||||
@ -1192,13 +1258,14 @@ public final class PbsEditorialSupportService {
|
|||||||
callbackDecl.returnKind(),
|
callbackDecl.returnKind(),
|
||||||
callbackDecl.returnType(),
|
callbackDecl.returnType(),
|
||||||
callbackDecl.resultErrorType(),
|
callbackDecl.resultErrorType(),
|
||||||
|
callbackDecl.attributes(),
|
||||||
callbackDecl.span());
|
callbackDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
return new PbsAst.EnumDecl(newName, enumDecl.cases(), enumDecl.span());
|
return new PbsAst.EnumDecl(newName, enumDecl.cases(), enumDecl.attributes(), enumDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
return new PbsAst.ErrorDecl(newName, errorDecl.cases(), errorDecl.span());
|
return new PbsAst.ErrorDecl(newName, errorDecl.cases(), errorDecl.attributes(), errorDecl.span());
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
||||||
return new PbsAst.GlobalDecl(newName, globalDecl.explicitType(), globalDecl.initializer(), globalDecl.span());
|
return new PbsAst.GlobalDecl(newName, globalDecl.explicitType(), globalDecl.initializer(), globalDecl.span());
|
||||||
|
|||||||
@ -146,7 +146,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
List<CallableParameter> parameters,
|
List<CallableParameter> parameters,
|
||||||
List<TypeView> inputTypes,
|
List<TypeView> inputTypes,
|
||||||
TypeView outputType,
|
TypeView outputType,
|
||||||
Span span) {
|
Span span,
|
||||||
|
String documentation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
record CallableParameter(
|
record CallableParameter(
|
||||||
@ -282,7 +283,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
method.returnKind(),
|
method.returnKind(),
|
||||||
method.returnType(),
|
method.returnType(),
|
||||||
method.resultErrorType(),
|
method.resultErrorType(),
|
||||||
method.span()));
|
method.span(),
|
||||||
|
documentationFromAttributes(method.attributes())));
|
||||||
}
|
}
|
||||||
structs.put(structDecl.name(), new StructInfo(fields, methods));
|
structs.put(structDecl.name(), new StructInfo(fields, methods));
|
||||||
return;
|
return;
|
||||||
@ -306,7 +308,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
signature.returnKind(),
|
signature.returnKind(),
|
||||||
signature.returnType(),
|
signature.returnType(),
|
||||||
signature.resultErrorType(),
|
signature.resultErrorType(),
|
||||||
signature.span()));
|
signature.span(),
|
||||||
|
documentationFromAttributes(signature.attributes())));
|
||||||
}
|
}
|
||||||
structs.put(builtinTypeDecl.name(), new StructInfo(fields, methods));
|
structs.put(builtinTypeDecl.name(), new StructInfo(fields, methods));
|
||||||
return;
|
return;
|
||||||
@ -321,7 +324,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
method.returnKind(),
|
method.returnKind(),
|
||||||
method.returnType(),
|
method.returnType(),
|
||||||
method.resultErrorType(),
|
method.resultErrorType(),
|
||||||
method.span()));
|
method.span(),
|
||||||
|
documentationFromAttributes(method.attributes())));
|
||||||
}
|
}
|
||||||
services.put(serviceDecl.name(), new ServiceInfo(methods));
|
services.put(serviceDecl.name(), new ServiceInfo(methods));
|
||||||
serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name()));
|
serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name()));
|
||||||
@ -337,7 +341,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
signature.returnKind(),
|
signature.returnKind(),
|
||||||
signature.returnType(),
|
signature.returnType(),
|
||||||
signature.resultErrorType(),
|
signature.resultErrorType(),
|
||||||
signature.span()));
|
signature.span(),
|
||||||
|
documentationFromAttributes(signature.attributes())));
|
||||||
}
|
}
|
||||||
// Host owners are value singletons with callable members, same access shape as services.
|
// Host owners are value singletons with callable members, same access shape as services.
|
||||||
services.put(hostDecl.name(), new ServiceInfo(methods));
|
services.put(hostDecl.name(), new ServiceInfo(methods));
|
||||||
@ -354,7 +359,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
signature.returnKind(),
|
signature.returnKind(),
|
||||||
signature.returnType(),
|
signature.returnType(),
|
||||||
signature.resultErrorType(),
|
signature.resultErrorType(),
|
||||||
signature.span()));
|
signature.span(),
|
||||||
|
documentationFromAttributes(signature.attributes())));
|
||||||
}
|
}
|
||||||
contracts.put(contractDecl.name(), new ContractInfo(methods));
|
contracts.put(contractDecl.name(), new ContractInfo(methods));
|
||||||
return;
|
return;
|
||||||
@ -367,12 +373,14 @@ final class PbsFlowSemanticSupport {
|
|||||||
functionDecl.returnKind(),
|
functionDecl.returnKind(),
|
||||||
functionDecl.returnType(),
|
functionDecl.returnType(),
|
||||||
functionDecl.resultErrorType(),
|
functionDecl.resultErrorType(),
|
||||||
functionDecl.span()));
|
functionDecl.span(),
|
||||||
|
documentationFromAttributes(functionDecl.attributes())));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.CallbackDecl(
|
if (topDecl instanceof PbsAst.CallbackDecl(
|
||||||
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
|
String name, ReadOnlyList<PbsAst.Parameter> parameters, PbsAst.ReturnKind returnKind,
|
||||||
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span
|
PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType,
|
||||||
|
ReadOnlyList<PbsAst.Attribute> ignoredAttributes, Span span
|
||||||
)) {
|
)) {
|
||||||
final var symbol = callableFrom(
|
final var symbol = callableFrom(
|
||||||
name,
|
name,
|
||||||
@ -380,7 +388,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
returnKind,
|
returnKind,
|
||||||
returnType,
|
returnType,
|
||||||
resultErrorType,
|
resultErrorType,
|
||||||
span);
|
span,
|
||||||
|
"");
|
||||||
callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType()));
|
callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -477,7 +486,8 @@ final class PbsFlowSemanticSupport {
|
|||||||
final PbsAst.ReturnKind returnKind,
|
final PbsAst.ReturnKind returnKind,
|
||||||
final PbsAst.TypeRef returnType,
|
final PbsAst.TypeRef returnType,
|
||||||
final PbsAst.TypeRef resultErrorType,
|
final PbsAst.TypeRef resultErrorType,
|
||||||
final Span span) {
|
final Span span,
|
||||||
|
final String documentation) {
|
||||||
final var input = new ArrayList<TypeView>(parameters.size());
|
final var input = new ArrayList<TypeView>(parameters.size());
|
||||||
final var semanticParameters = new ArrayList<CallableParameter>(parameters.size());
|
final var semanticParameters = new ArrayList<CallableParameter>(parameters.size());
|
||||||
for (int index = 0; index < parameters.size(); index += 1) {
|
for (int index = 0; index < parameters.size(); index += 1) {
|
||||||
@ -490,7 +500,21 @@ final class PbsFlowSemanticSupport {
|
|||||||
type,
|
type,
|
||||||
parameter.span()));
|
parameter.span()));
|
||||||
}
|
}
|
||||||
return new CallableSymbol(name, semanticParameters, input, callableReturn(returnKind, returnType, resultErrorType), span);
|
return new CallableSymbol(name, semanticParameters, input, callableReturn(returnKind, returnType, resultErrorType), span, documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String documentationFromAttributes(final ReadOnlyList<PbsAst.Attribute> attributes) {
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if (!"Doc".equals(attribute.name()) || attribute.arguments().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var argument = attribute.arguments().getFirst();
|
||||||
|
if (!"markdown".equals(argument.name()) || !(argument.value() instanceof PbsAst.AttributeDocTextBlockValue docValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return p.studio.compiler.pbs.PbsDocumentationTextBlockNormalizer.normalizeLexeme(docValue.value());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeView callableReturn(
|
private TypeView callableReturn(
|
||||||
|
|||||||
@ -1,61 +1,136 @@
|
|||||||
|
[Doc(markdown = """
|
||||||
|
Represents a packed RGBA color.
|
||||||
|
""")]
|
||||||
[BuiltinType(name = "color", version = 1)]
|
[BuiltinType(name = "color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub raw: int
|
pub raw: int
|
||||||
) {
|
) {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Builds a color from its packed integer representation.
|
||||||
|
|
||||||
|
- `raw`: packed color value.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "from_raw", version = 1)]
|
[IntrinsicCall(name = "from_raw", version = 1)]
|
||||||
fn from_raw(raw: int) -> Color;
|
fn from_raw(raw: int) -> Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Builds an opaque color from red, green, and blue channels.
|
||||||
|
|
||||||
|
- `r`: red channel.
|
||||||
|
- `g`: green channel.
|
||||||
|
- `b`: blue channel.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "rgb", version = 1)]
|
[IntrinsicCall(name = "rgb", version = 1)]
|
||||||
fn rgb(r: int, g: int, b: int) -> Color;
|
fn rgb(r: int, g: int, b: int) -> Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Builds a color from red, green, blue, and alpha channels.
|
||||||
|
|
||||||
|
- `r`: red channel.
|
||||||
|
- `g`: green channel.
|
||||||
|
- `b`: blue channel.
|
||||||
|
- `a`: alpha channel.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "rgba", version = 1)]
|
[IntrinsicCall(name = "rgba", version = 1)]
|
||||||
fn rgba(r: int, g: int, b: int, a: int) -> Color;
|
fn rgba(r: int, g: int, b: int, a: int) -> Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Parses a CSS-style RGBA color string.
|
||||||
|
|
||||||
|
- `value`: color text in HTML/CSS format.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "html_rgba", version = 1)]
|
[IntrinsicCall(name = "html_rgba", version = 1)]
|
||||||
fn html_rgba(value: str) -> Color;
|
fn html_rgba(value: str) -> Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Builds an opaque grayscale color.
|
||||||
|
|
||||||
|
- `value`: grayscale channel value.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "gray_scale", version = 1)]
|
[IntrinsicCall(name = "gray_scale", version = 1)]
|
||||||
fn gray_scale(value: int) -> Color;
|
fn gray_scale(value: int) -> Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the packed RGB portion of this color.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "hex", version = 1)]
|
[IntrinsicCall(name = "hex", version = 1)]
|
||||||
fn hex() -> int;
|
fn hex() -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the alpha channel of this color.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "alpha", version = 1)]
|
[IntrinsicCall(name = "alpha", version = 1)]
|
||||||
fn alpha() -> int;
|
fn alpha() -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque black.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "black", version = 1)]
|
[BuiltinConst(target = "color", name = "black", version = 1)]
|
||||||
declare const BLACK: Color;
|
declare const BLACK: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque white.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "white", version = 1)]
|
[BuiltinConst(target = "color", name = "white", version = 1)]
|
||||||
declare const WHITE: Color;
|
declare const WHITE: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque red.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "red", version = 1)]
|
[BuiltinConst(target = "color", name = "red", version = 1)]
|
||||||
declare const RED: Color;
|
declare const RED: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque green.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "green", version = 1)]
|
[BuiltinConst(target = "color", name = "green", version = 1)]
|
||||||
declare const GREEN: Color;
|
declare const GREEN: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque blue.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "blue", version = 1)]
|
[BuiltinConst(target = "color", name = "blue", version = 1)]
|
||||||
declare const BLUE: Color;
|
declare const BLUE: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque yellow.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "yellow", version = 1)]
|
[BuiltinConst(target = "color", name = "yellow", version = 1)]
|
||||||
declare const YELLOW: Color;
|
declare const YELLOW: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque orange.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "orange", version = 1)]
|
[BuiltinConst(target = "color", name = "orange", version = 1)]
|
||||||
declare const ORANGE: Color;
|
declare const ORANGE: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque indigo.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "indigo", version = 1)]
|
[BuiltinConst(target = "color", name = "indigo", version = 1)]
|
||||||
declare const INDIGO: Color;
|
declare const INDIGO: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque gray.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "gray", version = 1)]
|
[BuiltinConst(target = "color", name = "gray", version = 1)]
|
||||||
declare const GRAY: Color;
|
declare const GRAY: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque cyan.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "cyan", version = 1)]
|
[BuiltinConst(target = "color", name = "cyan", version = 1)]
|
||||||
declare const CYAN: Color;
|
declare const CYAN: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Opaque magenta.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "magenta", version = 1)]
|
[BuiltinConst(target = "color", name = "magenta", version = 1)]
|
||||||
declare const MAGENTA: Color;
|
declare const MAGENTA: Color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Fully transparent color.
|
||||||
|
""")]
|
||||||
[BuiltinConst(target = "color", name = "transparent", version = 1)]
|
[BuiltinConst(target = "color", name = "transparent", version = 1)]
|
||||||
declare const TRANSPARENT: Color;
|
declare const TRANSPARENT: Color;
|
||||||
|
|||||||
@ -1,35 +1,83 @@
|
|||||||
|
[Doc(markdown = """
|
||||||
|
Low-level asset loading host bindings.
|
||||||
|
""")]
|
||||||
declare host LowAssets {
|
declare host LowAssets {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Starts loading an addressable asset into a slot.
|
||||||
|
|
||||||
|
- `addressable`: asset reference.
|
||||||
|
- `slot`: destination slot.
|
||||||
|
""")]
|
||||||
[Host(module = "asset", name = "load", version = 1)]
|
[Host(module = "asset", name = "load", version = 1)]
|
||||||
[Capability(name = "asset")]
|
[Capability(name = "asset")]
|
||||||
[AssetLowering(param = 0)]
|
[AssetLowering(param = 0)]
|
||||||
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
|
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Reads the status of an asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
[Host(module = "asset", name = "status", version = 1)]
|
[Host(module = "asset", name = "status", version = 1)]
|
||||||
[Capability(name = "asset")]
|
[Capability(name = "asset")]
|
||||||
fn status(loading_handle: int) -> int;
|
fn status(loading_handle: int) -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Commits a completed asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
[Host(module = "asset", name = "commit", version = 1)]
|
[Host(module = "asset", name = "commit", version = 1)]
|
||||||
[Capability(name = "asset")]
|
[Capability(name = "asset")]
|
||||||
fn commit(loading_handle: int) -> int;
|
fn commit(loading_handle: int) -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Cancels an asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
[Host(module = "asset", name = "cancel", version = 1)]
|
[Host(module = "asset", name = "cancel", version = 1)]
|
||||||
[Capability(name = "asset")]
|
[Capability(name = "asset")]
|
||||||
fn cancel(loading_handle: int) -> int;
|
fn cancel(loading_handle: int) -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
High-level asset loading commands.
|
||||||
|
""")]
|
||||||
declare service Assets {
|
declare service Assets {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Starts loading an addressable asset into a slot.
|
||||||
|
|
||||||
|
- `addressable`: asset reference.
|
||||||
|
- `slot`: destination slot.
|
||||||
|
""")]
|
||||||
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int) {
|
fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int) {
|
||||||
return LowAssets.load(addressable, slot);
|
return LowAssets.load(addressable, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Reads the status of an asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
fn status(loading_handle: int) -> int {
|
fn status(loading_handle: int) -> int {
|
||||||
return LowAssets.status(loading_handle);
|
return LowAssets.status(loading_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Commits a completed asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
fn commit(loading_handle: int) -> int {
|
fn commit(loading_handle: int) -> int {
|
||||||
return LowAssets.commit(loading_handle);
|
return LowAssets.commit(loading_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Cancels an asset loading operation.
|
||||||
|
|
||||||
|
- `loading_handle`: handle returned by `load`.
|
||||||
|
""")]
|
||||||
fn cancel(loading_handle: int) -> int {
|
fn cancel(loading_handle: int) -> int {
|
||||||
return LowAssets.cancel(loading_handle);
|
return LowAssets.cancel(loading_handle);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,46 @@
|
|||||||
|
[Doc(markdown = """
|
||||||
|
Low-level scene composer host bindings.
|
||||||
|
""")]
|
||||||
declare host LowComposer {
|
declare host LowComposer {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Sets the scene camera position.
|
||||||
|
|
||||||
|
- `x`: camera x coordinate.
|
||||||
|
- `y`: camera y coordinate.
|
||||||
|
""")]
|
||||||
[Host(module = "composer", name = "set_camera", version = 1)]
|
[Host(module = "composer", name = "set_camera", version = 1)]
|
||||||
[Capability(name = "composer")]
|
[Capability(name = "composer")]
|
||||||
fn set_camera(x: int, y: int) -> void;
|
fn set_camera(x: int, y: int) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Binds a scene bank for composer emission.
|
||||||
|
|
||||||
|
- `scene_bank_id`: scene bank identifier.
|
||||||
|
""")]
|
||||||
[Host(module = "composer", name = "bind_scene", version = 1)]
|
[Host(module = "composer", name = "bind_scene", version = 1)]
|
||||||
[Capability(name = "composer")]
|
[Capability(name = "composer")]
|
||||||
fn bind_scene(scene_bank_id: int) -> int;
|
fn bind_scene(scene_bank_id: int) -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Unbinds the active scene bank.
|
||||||
|
""")]
|
||||||
[Host(module = "composer", name = "unbind_scene", version = 1)]
|
[Host(module = "composer", name = "unbind_scene", version = 1)]
|
||||||
[Capability(name = "composer")]
|
[Capability(name = "composer")]
|
||||||
fn unbind_scene() -> int;
|
fn unbind_scene() -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Emits a sprite command into the active scene.
|
||||||
|
|
||||||
|
- `glyph_id`: glyph identifier.
|
||||||
|
- `palette_id`: palette identifier.
|
||||||
|
- `x`: screen x coordinate.
|
||||||
|
- `y`: screen y coordinate.
|
||||||
|
- `layer`: render layer.
|
||||||
|
- `bank_id`: asset bank identifier.
|
||||||
|
- `flip_x`: mirror horizontally.
|
||||||
|
- `flip_y`: mirror vertically.
|
||||||
|
- `priority`: ordering priority.
|
||||||
|
""")]
|
||||||
[Host(module = "composer", name = "emit_sprite", version = 1)]
|
[Host(module = "composer", name = "emit_sprite", version = 1)]
|
||||||
[Capability(name = "composer")]
|
[Capability(name = "composer")]
|
||||||
fn emit_sprite(
|
fn emit_sprite(
|
||||||
@ -26,23 +56,53 @@ declare host LowComposer {
|
|||||||
) -> int;
|
) -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
High-level scene composition commands.
|
||||||
|
""")]
|
||||||
declare service Composer
|
declare service Composer
|
||||||
{
|
{
|
||||||
|
[Doc(markdown = """
|
||||||
|
Sets the scene camera position.
|
||||||
|
|
||||||
|
- `x`: camera x coordinate.
|
||||||
|
- `y`: camera y coordinate.
|
||||||
|
""")]
|
||||||
fn set_camera(x: int, y: int) -> void
|
fn set_camera(x: int, y: int) -> void
|
||||||
{
|
{
|
||||||
LowComposer.set_camera(x, y);
|
LowComposer.set_camera(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Binds a scene bank for composer emission.
|
||||||
|
|
||||||
|
- `scene_bank_id`: scene bank identifier.
|
||||||
|
""")]
|
||||||
fn bind_scene(scene_bank_id: int) -> int
|
fn bind_scene(scene_bank_id: int) -> int
|
||||||
{
|
{
|
||||||
return LowComposer.bind_scene(scene_bank_id);
|
return LowComposer.bind_scene(scene_bank_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Unbinds the active scene bank.
|
||||||
|
""")]
|
||||||
fn unbind_scene() -> int
|
fn unbind_scene() -> int
|
||||||
{
|
{
|
||||||
return LowComposer.unbind_scene();
|
return LowComposer.unbind_scene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Emits a sprite command into the active scene.
|
||||||
|
|
||||||
|
- `glyph_id`: glyph identifier.
|
||||||
|
- `palette_id`: palette identifier.
|
||||||
|
- `x`: screen x coordinate.
|
||||||
|
- `y`: screen y coordinate.
|
||||||
|
- `layer`: render layer.
|
||||||
|
- `bank_id`: asset bank identifier.
|
||||||
|
- `flip_x`: mirror horizontally.
|
||||||
|
- `flip_y`: mirror vertically.
|
||||||
|
- `priority`: ordering priority.
|
||||||
|
""")]
|
||||||
fn emit_sprite(
|
fn emit_sprite(
|
||||||
glyph_id: int,
|
glyph_id: int,
|
||||||
palette_id: int,
|
palette_id: int,
|
||||||
|
|||||||
@ -1,67 +1,189 @@
|
|||||||
import { Color } from @core:color;
|
import { Color } from @core:color;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Low-level 2D graphics host bindings.
|
||||||
|
""")]
|
||||||
declare host LowGfx {
|
declare host LowGfx {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Clears the render target.
|
||||||
|
|
||||||
|
- `color`: fill color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "clear", version = 1)]
|
[Host(module = "gfx2d", name = "clear", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn clear(color: Color) -> void;
|
fn clear(color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled rectangle.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: top coordinate.
|
||||||
|
- `w`: rectangle width.
|
||||||
|
- `h`: rectangle height.
|
||||||
|
- `color`: fill color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "fill_rect", version = 1)]
|
[Host(module = "gfx2d", name = "fill_rect", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void;
|
fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a line segment.
|
||||||
|
|
||||||
|
- `x1`: start x coordinate.
|
||||||
|
- `y1`: start y coordinate.
|
||||||
|
- `x2`: end x coordinate.
|
||||||
|
- `y2`: end y coordinate.
|
||||||
|
- `color`: line color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "draw_line", version = 1)]
|
[Host(module = "gfx2d", name = "draw_line", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void;
|
fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a circle outline.
|
||||||
|
|
||||||
|
- `x`: center x coordinate.
|
||||||
|
- `y`: center y coordinate.
|
||||||
|
- `r`: radius.
|
||||||
|
- `color`: outline color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "draw_circle", version = 1)]
|
[Host(module = "gfx2d", name = "draw_circle", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_circle(x: int, y: int, r: int, color: Color) -> void;
|
fn draw_circle(x: int, y: int, r: int, color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled circle.
|
||||||
|
|
||||||
|
- `x`: center x coordinate.
|
||||||
|
- `y`: center y coordinate.
|
||||||
|
- `r`: radius.
|
||||||
|
- `border_color`: outline color.
|
||||||
|
- `fill_color`: fill color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "draw_disc", version = 1)]
|
[Host(module = "gfx2d", name = "draw_disc", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void;
|
fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled rectangle with a border.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: top coordinate.
|
||||||
|
- `w`: rectangle width.
|
||||||
|
- `h`: rectangle height.
|
||||||
|
- `border_color`: border color.
|
||||||
|
- `fill_color`: fill color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "draw_square", version = 1)]
|
[Host(module = "gfx2d", name = "draw_square", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void;
|
fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws text at a screen position.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: baseline coordinate.
|
||||||
|
- `message`: text to draw.
|
||||||
|
- `color`: text color.
|
||||||
|
""")]
|
||||||
[Host(module = "gfx2d", name = "draw_text", version = 1)]
|
[Host(module = "gfx2d", name = "draw_text", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_text(x: int, y: int, message: str, color: Color) -> void;
|
fn draw_text(x: int, y: int, message: str, color: Color) -> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
High-level 2D drawing commands.
|
||||||
|
""")]
|
||||||
declare service Gfx
|
declare service Gfx
|
||||||
{
|
{
|
||||||
|
[Doc(markdown = """
|
||||||
|
Clears the render target.
|
||||||
|
|
||||||
|
- `color`: fill color.
|
||||||
|
""")]
|
||||||
fn clear(color: Color) -> void
|
fn clear(color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.clear(color);
|
LowGfx.clear(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled rectangle.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: top coordinate.
|
||||||
|
- `w`: rectangle width.
|
||||||
|
- `h`: rectangle height.
|
||||||
|
- `color`: fill color.
|
||||||
|
""")]
|
||||||
fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void
|
fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.fill_rect(x, y, w, h, color);
|
LowGfx.fill_rect(x, y, w, h, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a line segment.
|
||||||
|
|
||||||
|
- `x1`: start x coordinate.
|
||||||
|
- `y1`: start y coordinate.
|
||||||
|
- `x2`: end x coordinate.
|
||||||
|
- `y2`: end y coordinate.
|
||||||
|
- `color`: line color.
|
||||||
|
""")]
|
||||||
fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void
|
fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.draw_line(x1, y1, x2, y2, color);
|
LowGfx.draw_line(x1, y1, x2, y2, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a circle outline.
|
||||||
|
|
||||||
|
- `x`: center x coordinate.
|
||||||
|
- `y`: center y coordinate.
|
||||||
|
- `r`: radius.
|
||||||
|
- `color`: outline color.
|
||||||
|
""")]
|
||||||
fn draw_circle(x: int, y: int, r: int, color: Color) -> void
|
fn draw_circle(x: int, y: int, r: int, color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.draw_circle(x, y, r, color);
|
LowGfx.draw_circle(x, y, r, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled circle.
|
||||||
|
|
||||||
|
- `x`: center x coordinate.
|
||||||
|
- `y`: center y coordinate.
|
||||||
|
- `r`: radius.
|
||||||
|
- `border_color`: outline color.
|
||||||
|
- `fill_color`: fill color.
|
||||||
|
""")]
|
||||||
fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void
|
fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.draw_disc(x, y, r, border_color, fill_color);
|
LowGfx.draw_disc(x, y, r, border_color, fill_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws a filled rectangle with a border.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: top coordinate.
|
||||||
|
- `w`: rectangle width.
|
||||||
|
- `h`: rectangle height.
|
||||||
|
- `border_color`: border color.
|
||||||
|
- `fill_color`: fill color.
|
||||||
|
""")]
|
||||||
fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void
|
fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.draw_square(x, y, w, h, border_color, fill_color);
|
LowGfx.draw_square(x, y, w, h, border_color, fill_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Draws text at a screen position.
|
||||||
|
|
||||||
|
- `x`: left coordinate.
|
||||||
|
- `y`: baseline coordinate.
|
||||||
|
- `message`: text to draw.
|
||||||
|
- `color`: text color.
|
||||||
|
""")]
|
||||||
fn draw_text(x: int, y: int, message: str, color: Color) -> void
|
fn draw_text(x: int, y: int, message: str, color: Color) -> void
|
||||||
{
|
{
|
||||||
LowGfx.draw_text(x, y, message, color);
|
LowGfx.draw_text(x, y, message, color);
|
||||||
|
|||||||
@ -1,85 +1,169 @@
|
|||||||
|
[Doc(markdown = """
|
||||||
|
Root input device provider.
|
||||||
|
""")]
|
||||||
[BuiltinType(name = "input", version = 1)]
|
[BuiltinType(name = "input", version = 1)]
|
||||||
declare builtin type InputType() {
|
declare builtin type InputType() {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the gamepad input state.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "pad", version = 1)]
|
[IntrinsicCall(name = "pad", version = 1)]
|
||||||
fn pad() -> InputPad;
|
fn pad() -> InputPad;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the touch input state.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "touch", version = 1)]
|
[IntrinsicCall(name = "touch", version = 1)]
|
||||||
fn touch() -> InputTouch;
|
fn touch() -> InputTouch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Represents the current gamepad state.
|
||||||
|
""")]
|
||||||
[BuiltinType(name = "input.pad", version = 1)]
|
[BuiltinType(name = "input.pad", version = 1)]
|
||||||
declare builtin type InputPad() {
|
declare builtin type InputPad() {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the up directional button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "up", version = 1)]
|
[IntrinsicCall(name = "up", version = 1)]
|
||||||
fn up() -> InputButton;
|
fn up() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the down directional button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "down", version = 1)]
|
[IntrinsicCall(name = "down", version = 1)]
|
||||||
fn down() -> InputButton;
|
fn down() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the left directional button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "left", version = 1)]
|
[IntrinsicCall(name = "left", version = 1)]
|
||||||
fn left() -> InputButton;
|
fn left() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the right directional button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "right", version = 1)]
|
[IntrinsicCall(name = "right", version = 1)]
|
||||||
fn right() -> InputButton;
|
fn right() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the A action button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "a", version = 1)]
|
[IntrinsicCall(name = "a", version = 1)]
|
||||||
fn a() -> InputButton;
|
fn a() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the B action button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "b", version = 1)]
|
[IntrinsicCall(name = "b", version = 1)]
|
||||||
fn b() -> InputButton;
|
fn b() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the X action button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "x", version = 1)]
|
[IntrinsicCall(name = "x", version = 1)]
|
||||||
fn x() -> InputButton;
|
fn x() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the Y action button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "y", version = 1)]
|
[IntrinsicCall(name = "y", version = 1)]
|
||||||
fn y() -> InputButton;
|
fn y() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the left shoulder button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "l", version = 1)]
|
[IntrinsicCall(name = "l", version = 1)]
|
||||||
fn l() -> InputButton;
|
fn l() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the right shoulder button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "r", version = 1)]
|
[IntrinsicCall(name = "r", version = 1)]
|
||||||
fn r() -> InputButton;
|
fn r() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the start button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "start", version = 1)]
|
[IntrinsicCall(name = "start", version = 1)]
|
||||||
fn start() -> InputButton;
|
fn start() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the select button.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "select", version = 1)]
|
[IntrinsicCall(name = "select", version = 1)]
|
||||||
fn select() -> InputButton;
|
fn select() -> InputButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Represents the current touch input state.
|
||||||
|
""")]
|
||||||
[BuiltinType(name = "input.touch", version = 1)]
|
[BuiltinType(name = "input.touch", version = 1)]
|
||||||
declare builtin type InputTouch() {
|
declare builtin type InputTouch() {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the touch contact as a button state.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "button", version = 1)]
|
[IntrinsicCall(name = "button", version = 1)]
|
||||||
fn button() -> InputButton;
|
fn button() -> InputButton;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the current touch x coordinate.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "x", version = 1)]
|
[IntrinsicCall(name = "x", version = 1)]
|
||||||
fn x() -> int;
|
fn x() -> int;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the current touch y coordinate.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "y", version = 1)]
|
[IntrinsicCall(name = "y", version = 1)]
|
||||||
fn y() -> int;
|
fn y() -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Represents one digital input button.
|
||||||
|
""")]
|
||||||
[BuiltinType(name = "input.button", version = 1)]
|
[BuiltinType(name = "input.button", version = 1)]
|
||||||
declare builtin type InputButton() {
|
declare builtin type InputButton() {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns true on the frame where the button becomes down.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "pressed", version = 1)]
|
[IntrinsicCall(name = "pressed", version = 1)]
|
||||||
fn pressed() -> bool;
|
fn pressed() -> bool;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns true on the frame where the button becomes up.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "released", version = 1)]
|
[IntrinsicCall(name = "released", version = 1)]
|
||||||
fn released() -> bool;
|
fn released() -> bool;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns true while the button is currently down.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "down", version = 1)]
|
[IntrinsicCall(name = "down", version = 1)]
|
||||||
fn down() -> bool;
|
fn down() -> bool;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns how long the button has been held.
|
||||||
|
""")]
|
||||||
[IntrinsicCall(name = "hold", version = 1)]
|
[IntrinsicCall(name = "hold", version = 1)]
|
||||||
fn hold() -> int;
|
fn hold() -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
High-level access to player input devices.
|
||||||
|
""")]
|
||||||
declare service Input
|
declare service Input
|
||||||
{
|
{
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the gamepad input state.
|
||||||
|
""")]
|
||||||
fn pad() -> InputPad
|
fn pad() -> InputPad
|
||||||
{
|
{
|
||||||
return InputType.pad();
|
return InputType.pad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Returns the touch input state.
|
||||||
|
""")]
|
||||||
fn touch() -> InputTouch
|
fn touch() -> InputTouch
|
||||||
{
|
{
|
||||||
return InputType.touch();
|
return InputType.touch();
|
||||||
|
|||||||
@ -1,60 +1,134 @@
|
|||||||
|
[Doc(markdown = """
|
||||||
|
Low-level logging host bindings.
|
||||||
|
""")]
|
||||||
declare host LowLog {
|
declare host LowLog {
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a log message at a numeric level.
|
||||||
|
|
||||||
|
- `level`: severity level.
|
||||||
|
- `message`: text to write.
|
||||||
|
""")]
|
||||||
[Host(module = "log", name = "write", version = 1)]
|
[Host(module = "log", name = "write", version = 1)]
|
||||||
[Capability(name = "log")]
|
[Capability(name = "log")]
|
||||||
fn write(level: int, message: str) -> void;
|
fn write(level: int, message: str) -> void;
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged log message at a numeric level.
|
||||||
|
|
||||||
|
- `level`: severity level.
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `message`: text to write.
|
||||||
|
""")]
|
||||||
[Host(module = "log", name = "write_tag", version = 1)]
|
[Host(module = "log", name = "write_tag", version = 1)]
|
||||||
[Capability(name = "log")]
|
[Capability(name = "log")]
|
||||||
fn write_tag(level: int, tag: int, message: str) -> void;
|
fn write_tag(level: int, tag: int, message: str) -> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
High-level logging commands.
|
||||||
|
""")]
|
||||||
declare service Log
|
declare service Log
|
||||||
{
|
{
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a trace log message.
|
||||||
|
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn trace(msg: str) -> void
|
fn trace(msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write(0, msg);
|
LowLog.write(0, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a debug log message.
|
||||||
|
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn debug(msg: str) -> void
|
fn debug(msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write(1, msg);
|
LowLog.write(1, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes an informational log message.
|
||||||
|
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn info(msg: str) -> void
|
fn info(msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write(2, msg);
|
LowLog.write(2, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a warning log message.
|
||||||
|
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn warn(msg: str) -> void
|
fn warn(msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write(3, msg);
|
LowLog.write(3, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a failure log message.
|
||||||
|
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn failure(msg: str) -> void
|
fn failure(msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write(4, msg);
|
LowLog.write(4, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged trace log message.
|
||||||
|
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn trace(tag: int, msg: str) -> void
|
fn trace(tag: int, msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write_tag(0, tag, msg);
|
LowLog.write_tag(0, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged debug log message.
|
||||||
|
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn debug(tag: int, msg: str) -> void
|
fn debug(tag: int, msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write_tag(1, tag, msg);
|
LowLog.write_tag(1, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged informational log message.
|
||||||
|
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn info(tag: int, msg: str) -> void
|
fn info(tag: int, msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write_tag(2, tag, msg);
|
LowLog.write_tag(2, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged warning log message.
|
||||||
|
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn warn(tag: int, msg: str) -> void
|
fn warn(tag: int, msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write_tag(3, tag, msg);
|
LowLog.write_tag(3, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = """
|
||||||
|
Writes a tagged failure log message.
|
||||||
|
|
||||||
|
- `tag`: application-defined tag.
|
||||||
|
- `msg`: text to write.
|
||||||
|
""")]
|
||||||
fn failure(tag: int, msg: str) -> void
|
fn failure(tag: int, msg: str) -> void
|
||||||
{
|
{
|
||||||
LowLog.write_tag(4, tag, msg);
|
LowLog.write_tag(4, tag, msg);
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class PbsDocumentationTextBlockNormalizerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNormalizeIncidentalBoundaryLinesAndIndentation() {
|
||||||
|
final var lexeme = "\"\"\"\n" +
|
||||||
|
" Draws a sprite.\n" +
|
||||||
|
"\n" +
|
||||||
|
" - `x`: screen x\n" +
|
||||||
|
" - `y`: screen y\n" +
|
||||||
|
" \"\"\"";
|
||||||
|
|
||||||
|
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme(lexeme);
|
||||||
|
|
||||||
|
assertEquals("Draws a sprite.\n\n- `x`: screen x\n- `y`: screen y", normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreserveInternalLineBreaksAndMarkdownContent() {
|
||||||
|
final var content = "\n" +
|
||||||
|
" Paragraph one.\n" +
|
||||||
|
" \n" +
|
||||||
|
" `code_span()`\n" +
|
||||||
|
" Paragraph two.\n";
|
||||||
|
|
||||||
|
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeContent(content);
|
||||||
|
|
||||||
|
assertEquals("Paragraph one.\n\n`code_span()`\nParagraph two.", normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyStringForWhitespaceOnlyPayload() {
|
||||||
|
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme("\"\"\"\n \n \t\n\"\"\"");
|
||||||
|
|
||||||
|
assertEquals("", normalized.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNormalizeCarriageReturnLineEndings() {
|
||||||
|
final var normalized = PbsDocumentationTextBlockNormalizer.normalizeContent("\r\n\tLine one\r\n\tLine two\r\n");
|
||||||
|
|
||||||
|
assertEquals("Line one\nLine two", normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyStringForMalformedLexeme() {
|
||||||
|
assertEquals("", PbsDocumentationTextBlockNormalizer.normalizeLexeme("\"not a text block\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import p.studio.compiler.messages.Addressable;
|
import p.studio.compiler.messages.Addressable;
|
||||||
import p.studio.compiler.messages.FESurfaceContext;
|
import p.studio.compiler.messages.FESurfaceContext;
|
||||||
import p.studio.compiler.messages.HostAdmissionContext;
|
import p.studio.compiler.messages.HostAdmissionContext;
|
||||||
|
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||||
|
import p.studio.compiler.models.IRBackendFile;
|
||||||
import p.studio.compiler.models.IRHiddenGlobalKind;
|
import p.studio.compiler.models.IRHiddenGlobalKind;
|
||||||
import p.studio.compiler.models.IRGlobalVisibility;
|
import p.studio.compiler.models.IRGlobalVisibility;
|
||||||
import p.studio.compiler.models.IRSyntheticCallableKind;
|
import p.studio.compiler.models.IRSyntheticCallableKind;
|
||||||
@ -52,6 +54,57 @@ class PbsFrontendCompilerTest {
|
|||||||
assertEquals(1, functions.get(1).parameterCount());
|
assertEquals(1, functions.get(1).parameterCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotLowerDocMetadataIntoRuntimeBackendArtifacts() {
|
||||||
|
final var undocumentedSource = """
|
||||||
|
declare global SCORE: int = 0;
|
||||||
|
|
||||||
|
fn add_one(value: int) -> int {
|
||||||
|
return value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
fn boot() -> void {
|
||||||
|
SCORE = add_one(41);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var marker = "THIS_DOC_PAYLOAD_MUST_NOT_APPEAR_IN_RUNTIME_ARTIFACTS";
|
||||||
|
final var documentedSource = """
|
||||||
|
declare global SCORE: int = 0;
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Adds one to a value.
|
||||||
|
|
||||||
|
This deliberately large documentation payload is editor metadata only:
|
||||||
|
THIS_DOC_PAYLOAD_MUST_NOT_APPEAR_IN_RUNTIME_ARTIFACTS.
|
||||||
|
\""")]
|
||||||
|
fn add_one(value: int) -> int {
|
||||||
|
return value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Initializes the score global.
|
||||||
|
\""")]
|
||||||
|
[Init]
|
||||||
|
fn boot() -> void {
|
||||||
|
SCORE = add_one(41);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var compiler = new PbsFrontendCompiler();
|
||||||
|
final var undocumentedDiagnostics = DiagnosticSink.empty();
|
||||||
|
final var documentedDiagnostics = DiagnosticSink.empty();
|
||||||
|
final var undocumentedBackend = compiler.compileFile(new FileId(20), undocumentedSource, undocumentedDiagnostics);
|
||||||
|
final var documentedBackend = compiler.compileFile(new FileId(20), documentedSource, documentedDiagnostics);
|
||||||
|
|
||||||
|
assertTrue(undocumentedDiagnostics.isEmpty(), diagnosticsSummary(undocumentedDiagnostics));
|
||||||
|
assertTrue(documentedDiagnostics.isEmpty(), diagnosticsSummary(documentedDiagnostics));
|
||||||
|
assertEquals(runtimeFingerprint(undocumentedBackend), runtimeFingerprint(documentedBackend));
|
||||||
|
assertFalse(String.valueOf(documentedBackend).contains(marker));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExposeGlobalsAndSyntheticLifecycleArtifactsInBackendFile() {
|
void shouldExposeGlobalsAndSyntheticLifecycleArtifactsInBackendFile() {
|
||||||
final var source = """
|
final var source = """
|
||||||
@ -812,4 +865,62 @@ class PbsFrontendCompilerTest {
|
|||||||
assertEquals(1L, pixel.fields().get(1).slotStart());
|
assertEquals(1L, pixel.fields().get(1).slotStart());
|
||||||
assertEquals(2L, pixel.fields().get(2).slotStart());
|
assertEquals(2L, pixel.fields().get(2).slotStart());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String runtimeFingerprint(final IRBackendFile backend) {
|
||||||
|
final var out = new StringBuilder();
|
||||||
|
out.append("functions:");
|
||||||
|
backend.functions().forEach(function -> out
|
||||||
|
.append(function.name()).append('/')
|
||||||
|
.append(function.parameterCount()).append('/')
|
||||||
|
.append(function.hasExplicitReturnType()).append(';'));
|
||||||
|
out.append("synthetic:");
|
||||||
|
backend.syntheticFunctions().forEach(function -> out
|
||||||
|
.append(function.moduleId()).append('/')
|
||||||
|
.append(function.callableName()).append('/')
|
||||||
|
.append(function.kind()).append(';'));
|
||||||
|
out.append("globals:");
|
||||||
|
backend.globals().forEach(global -> out
|
||||||
|
.append(global.moduleId()).append('/')
|
||||||
|
.append(global.name()).append('/')
|
||||||
|
.append(global.declaredTypeSurface()).append('/')
|
||||||
|
.append(global.slot()).append('/')
|
||||||
|
.append(global.visibility()).append('/')
|
||||||
|
.append(global.hiddenKind()).append(';'));
|
||||||
|
out.append("executables:");
|
||||||
|
backend.executableFunctions().forEach(function -> {
|
||||||
|
out.append(function.moduleId()).append('/')
|
||||||
|
.append(function.callableName()).append('/')
|
||||||
|
.append(function.callableId().getIndex()).append('/')
|
||||||
|
.append(function.paramSlots()).append('/')
|
||||||
|
.append(function.localSlots()).append('/')
|
||||||
|
.append(function.returnSlots()).append('/')
|
||||||
|
.append(function.maxStackSlots()).append('{');
|
||||||
|
function.instructions().forEach(instruction -> appendInstructionFingerprint(out, instruction));
|
||||||
|
out.append("};");
|
||||||
|
});
|
||||||
|
out.append("modules:").append(backend.modulePool()).append(';');
|
||||||
|
out.append("callables:").append(backend.callableSignatures()).append(';');
|
||||||
|
out.append("intrinsics:").append(backend.intrinsicPool()).append(';');
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendInstructionFingerprint(
|
||||||
|
final StringBuilder out,
|
||||||
|
final IRBackendExecutableFunction.Instruction instruction) {
|
||||||
|
out.append(instruction.kind()).append('(')
|
||||||
|
.append(instruction.calleeModuleId()).append(',')
|
||||||
|
.append(instruction.calleeCallableName()).append(',')
|
||||||
|
.append(instruction.calleeCallableId() == null ? "" : instruction.calleeCallableId().getIndex()).append(',')
|
||||||
|
.append(instruction.hostCall()).append(',')
|
||||||
|
.append(instruction.intrinsicCall()).append(',')
|
||||||
|
.append(instruction.label()).append(',')
|
||||||
|
.append(instruction.targetLabel()).append(',')
|
||||||
|
.append(instruction.expectedArgSlots()).append(',')
|
||||||
|
.append(instruction.expectedRetSlots())
|
||||||
|
.append(");");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String diagnosticsSummary(final DiagnosticSink diagnostics) {
|
||||||
|
return diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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\"";
|
||||||
|
|||||||
@ -339,6 +339,82 @@ class PbsParserTest {
|
|||||||
assertEquals(1, constDecl.attributes().size());
|
assertEquals(1, constDecl.attributes().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldParseDocumentationTextBlockAttributeValuesInInterfaceModuleMode() {
|
||||||
|
final var source = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Represents a packed color.
|
||||||
|
\""")]
|
||||||
|
[BuiltinType(name = "color", version = 1)]
|
||||||
|
declare builtin type Color(
|
||||||
|
pub raw: int
|
||||||
|
) {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Packs this color into an integer.
|
||||||
|
\""")]
|
||||||
|
[IntrinsicCall(name = "color.pack", version = 1)]
|
||||||
|
fn pack() -> int;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
|
||||||
|
final PbsAst.File ast = PbsParser.parse(
|
||||||
|
PbsLexer.lex(source, fileId, diagnostics),
|
||||||
|
fileId,
|
||||||
|
diagnostics,
|
||||||
|
PbsParser.ParseMode.INTERFACE_MODULE);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.isEmpty(), "Parser should accept documentation text block attribute values");
|
||||||
|
final var builtinType = assertInstanceOf(PbsAst.BuiltinTypeDecl.class, ast.topDecls().getFirst());
|
||||||
|
final var typeDoc = builtinType.attributes().getFirst().arguments().getFirst().value();
|
||||||
|
assertInstanceOf(PbsAst.AttributeDocTextBlockValue.class, typeDoc);
|
||||||
|
assertTrue(((PbsAst.AttributeDocTextBlockValue) typeDoc).value().contains("Represents a packed color."));
|
||||||
|
|
||||||
|
final var signatureDoc = builtinType.signatures().getFirst().attributes().getFirst().arguments().getFirst().value();
|
||||||
|
assertInstanceOf(PbsAst.AttributeDocTextBlockValue.class, signatureDoc);
|
||||||
|
assertTrue(((PbsAst.AttributeDocTextBlockValue) signatureDoc).value().startsWith("\"\"\""));
|
||||||
|
assertTrue(((PbsAst.AttributeDocTextBlockValue) signatureDoc).value().endsWith("\"\"\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreserveDocumentationAttributesOnStructAndServiceMethods() {
|
||||||
|
final var source = """
|
||||||
|
declare struct Sprite() {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Draws this sprite.
|
||||||
|
\""")]
|
||||||
|
fn draw() -> void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Provides drawing commands.
|
||||||
|
\""")]
|
||||||
|
declare service Gfx {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Clears the screen.
|
||||||
|
\""")]
|
||||||
|
fn clear() -> void {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
|
||||||
|
final PbsAst.File ast = PbsParser.parse(
|
||||||
|
PbsLexer.lex(source, fileId, diagnostics),
|
||||||
|
fileId,
|
||||||
|
diagnostics,
|
||||||
|
PbsParser.ParseMode.ORDINARY);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.isEmpty(), "Parser should accept Doc attributes on struct and service methods");
|
||||||
|
final var structDecl = assertInstanceOf(PbsAst.StructDecl.class, ast.topDecls().getFirst());
|
||||||
|
assertEquals("Doc", structDecl.methods().getFirst().attributes().getFirst().name());
|
||||||
|
|
||||||
|
final var serviceDecl = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().get(1));
|
||||||
|
assertEquals("Doc", serviceDecl.attributes().getFirst().name());
|
||||||
|
assertEquals("Doc", serviceDecl.methods().getFirst().attributes().getFirst().name());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRecoverFromMalformedReservedDeclarationsInInterfaceModuleMode() {
|
void shouldRecoverFromMalformedReservedDeclarationsInInterfaceModuleMode() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -123,6 +123,49 @@ final class PbsEditorialSupportServiceTest {
|
|||||||
assertEquals("Input", hostMethodHover.origin());
|
assertEquals("Input", hostMethodHover.origin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeDocMetadataOnTopLevelSymbolHover() {
|
||||||
|
final var source = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Runs the program.
|
||||||
|
|
||||||
|
- no parameters
|
||||||
|
\""")]
|
||||||
|
fn run() -> void { return; }
|
||||||
|
""";
|
||||||
|
final var ast = parseOrdinary(source);
|
||||||
|
|
||||||
|
final var hover = requireHover(source, ast, ReadOnlyList.empty(), "run");
|
||||||
|
|
||||||
|
assertEquals(PbsEditorialSymbolKind.FUNCTION, hover.kind());
|
||||||
|
assertEquals("Runs the program.\n\n- no parameters", hover.documentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeDocMetadataOnMemberHover() {
|
||||||
|
final var source = """
|
||||||
|
import { Gfx } from @sdk:gfx;
|
||||||
|
|
||||||
|
fn main() -> void {
|
||||||
|
Gfx.draw_pixel(1, 2, 3);
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var ast = parseOrdinary(source);
|
||||||
|
final var supplementalTopDecls = supplementalTopDecls("""
|
||||||
|
declare host Gfx {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Draws a single pixel.
|
||||||
|
\""")]
|
||||||
|
fn draw_pixel(x: int, y: int, color: int) -> void;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final var hover = requireHover(source, ast, supplementalTopDecls, "draw_pixel");
|
||||||
|
|
||||||
|
assertEquals(PbsEditorialSymbolKind.METHOD, hover.kind());
|
||||||
|
assertEquals("Draws a single pixel.", hover.documentation());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldResolveSignatureHelpForFunctionMethodAndConstructorCalls() {
|
void shouldResolveSignatureHelpForFunctionMethodAndConstructorCalls() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -14,15 +14,27 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldAcceptValidInterfaceModuleReservedMetadataShapes() {
|
void shouldAcceptValidInterfaceModuleReservedMetadataShapes() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Packed color value.
|
||||||
|
\""")]
|
||||||
[BuiltinType(name = "color", version = 1)]
|
[BuiltinType(name = "color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub raw: int
|
pub raw: int
|
||||||
) {
|
) {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Packs the color.
|
||||||
|
\""")]
|
||||||
[IntrinsicCall(name = "pack", version = 1)]
|
[IntrinsicCall(name = "pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Graphics host API.
|
||||||
|
\""")]
|
||||||
declare host Gfx {
|
declare host Gfx {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Draws one pixel.
|
||||||
|
\""")]
|
||||||
[Host(module = "gfx2d", name = "draw_pixel", version = 1)]
|
[Host(module = "gfx2d", name = "draw_pixel", version = 1)]
|
||||||
[Capability(name = "gfx2d")]
|
[Capability(name = "gfx2d")]
|
||||||
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
||||||
@ -43,6 +55,26 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectDocOnBuiltinFields() {
|
||||||
|
final var source = """
|
||||||
|
[BuiltinType(name = "color", version = 1)]
|
||||||
|
declare builtin type Color(
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Raw color bits.
|
||||||
|
\""")]
|
||||||
|
pub raw: int
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRejectInvalidReservedAttributeTargetsAndShapesInInterfaceModule() {
|
void shouldRejectInvalidReservedAttributeTargetsAndShapesInInterfaceModule() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -9,6 +9,106 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
class PbsSemanticsDeclarationsTest {
|
class PbsSemanticsDeclarationsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAcceptDocOnNamedApiDeclarations() {
|
||||||
|
final var source = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Runs the program.
|
||||||
|
\""")]
|
||||||
|
fn run() -> void { return; }
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Holds player state.
|
||||||
|
\""")]
|
||||||
|
declare struct Player(x: int) {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Updates player state.
|
||||||
|
\""")]
|
||||||
|
fn update() -> void { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Game services.
|
||||||
|
\""")]
|
||||||
|
declare service Game {
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Runs one game tick.
|
||||||
|
\""")]
|
||||||
|
fn tick() -> void { return; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectMalformedDuplicateAndEmptyDocAttributes() {
|
||||||
|
final var source = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
First.
|
||||||
|
\""")]
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Second.
|
||||||
|
\""")]
|
||||||
|
fn duplicated() -> void { return; }
|
||||||
|
|
||||||
|
[Doc(text = \"""
|
||||||
|
Wrong argument.
|
||||||
|
\""")]
|
||||||
|
fn malformed() -> void { return; }
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
|
||||||
|
\""")]
|
||||||
|
fn empty() -> void { return; }
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_RESERVED_ATTRIBUTE.name())));
|
||||||
|
final var malformedCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name()))
|
||||||
|
.count();
|
||||||
|
assertTrue(malformedCount >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectDocAliasesExtraArgumentsAndParameterTargets() {
|
||||||
|
final var source = """
|
||||||
|
[Document(markdown = \"""
|
||||||
|
Alias should not be accepted.
|
||||||
|
\""")]
|
||||||
|
fn alias() -> void { return; }
|
||||||
|
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Has an extra argument.
|
||||||
|
\""", format = \"""
|
||||||
|
markdown
|
||||||
|
\""")]
|
||||||
|
fn extra() -> void { return; }
|
||||||
|
|
||||||
|
fn parameter([Doc(markdown = \"""
|
||||||
|
Parameter docs are not allowed.
|
||||||
|
\""")] value: int) -> int { return value; }
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals("E_PARSE_ATTRIBUTES_NOT_ALLOWED")));
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldAllowFunctionOverloadsWithDifferentShapes() {
|
void shouldAllowFunctionOverloadsWithDifferentShapes() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -288,7 +288,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
candidate.label(),
|
candidate.label(),
|
||||||
mapCompletionKind(candidate.kind()),
|
mapCompletionKind(candidate.kind()),
|
||||||
candidate.detail(),
|
candidate.detail(),
|
||||||
candidate.origin());
|
candidate.documentation().isBlank() ? candidate.origin() : candidate.documentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaselineVisualTheme mapVisualTheme(final FrontendVisualThemeSpec theme) {
|
private BaselineVisualTheme mapVisualTheme(final FrontendVisualThemeSpec theme) {
|
||||||
|
|||||||
@ -195,6 +195,8 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
assertTrue(clear565Completion.detail().contains("clear(color:"));
|
assertTrue(clear565Completion.detail().contains("clear(color:"));
|
||||||
|
assertTrue(clear565Completion.documentation().contains("Clears the render target."));
|
||||||
|
assertTrue(clear565Completion.documentation().contains("- `color`: fill color."));
|
||||||
assertFalse(clear565Completion.detail().contains("arg0"));
|
assertFalse(clear565Completion.detail().contains("arg0"));
|
||||||
|
|
||||||
final var hover = bridge.hover(
|
final var hover = bridge.hover(
|
||||||
@ -204,6 +206,8 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
memberPosition.line(),
|
memberPosition.line(),
|
||||||
memberPosition.character());
|
memberPosition.character());
|
||||||
assertTrue(hover.markdown().contains("clear(color:"));
|
assertTrue(hover.markdown().contains("clear(color:"));
|
||||||
|
assertTrue(hover.markdown().contains("Clears the render target."));
|
||||||
|
assertTrue(hover.markdown().contains("- `color`: fill color."));
|
||||||
assertFalse(hover.markdown().contains("arg0"));
|
assertFalse(hover.markdown().contains("arg0"));
|
||||||
|
|
||||||
final var signatureHelp = bridge.signatureHelp(
|
final var signatureHelp = bridge.signatureHelp(
|
||||||
@ -217,6 +221,56 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertFalse(signatureHelp.signatures().getFirst().label().contains("arg0"));
|
assertFalse(signatureHelp.signatures().getFirst().label().contains("arg0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hoverAndCompletionExposeCompilerResolvedDocMarkdown() {
|
||||||
|
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
||||||
|
.resolve("test-projects")
|
||||||
|
.resolve("main")
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize();
|
||||||
|
final Path documentPath = projectRoot.resolve("src").resolve("main.pbs");
|
||||||
|
final String overlay = """
|
||||||
|
[Doc(markdown = \"""
|
||||||
|
Computes the answer.
|
||||||
|
|
||||||
|
- returns `42`
|
||||||
|
- has no parameters
|
||||||
|
\""")]
|
||||||
|
fn helper() -> int { return 42; }
|
||||||
|
|
||||||
|
fn frame() -> void {
|
||||||
|
helper();
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
|
final LspProjectContext context = new LspProjectContext("main", "pbs", projectRoot);
|
||||||
|
final DocumentPositionMapper mapper = new DocumentPositionMapper(overlay);
|
||||||
|
final var helperPosition = mapper.positionOf(overlay.indexOf("helper();"));
|
||||||
|
|
||||||
|
final var hover = bridge.hover(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
helperPosition.line(),
|
||||||
|
helperPosition.character());
|
||||||
|
assertTrue(hover.markdown().contains("Computes the answer."));
|
||||||
|
assertTrue(hover.markdown().contains("- returns `42`"));
|
||||||
|
assertTrue(hover.markdown().indexOf("**function**") < hover.markdown().indexOf("Computes the answer."));
|
||||||
|
|
||||||
|
final var completion = bridge.completion(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
helperPosition.line(),
|
||||||
|
helperPosition.character());
|
||||||
|
final var helper = completion.items().stream()
|
||||||
|
.filter(item -> item.label().equals("helper"))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
assertTrue(helper.documentation().contains("Computes the answer."));
|
||||||
|
assertTrue(helper.documentation().contains("- returns `42`"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void describeServerPublishesFrontendVisualThemes() {
|
void describeServerPublishesFrontendVisualThemes() {
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user