From 63215ac02fd47a3b3b4ffdca5217116c6dbc1cdb Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 15 Jul 2026 07:18:19 +0100 Subject: [PATCH] implements PLN-0100 --- discussion/index.ndjson | 2 +- ...r-stdlib-sdk-and-interface-declarations.md | 2 +- .../pbs/parser/PbsDeclarationParser.java | 27 +++- .../PbsDeclarationSemanticsValidator.java | 2 + .../game/stdlib/1/core/color/main.pbs | 75 +++++++++++ .../game/stdlib/1/sdk/asset/main.pbs | 48 +++++++ .../game/stdlib/1/sdk/composer/main.pbs | 60 +++++++++ .../resources/game/stdlib/1/sdk/gfx/main.pbs | 122 ++++++++++++++++++ .../game/stdlib/1/sdk/input/main.pbs | 84 ++++++++++++ .../resources/game/stdlib/1/sdk/log/main.pbs | 74 +++++++++++ .../compiler/pbs/parser/PbsParserTest.java | 38 ++++++ 11 files changed, 525 insertions(+), 9 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 0c24ec7a..8f143683 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,7 +1,7 @@ {"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-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":"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":"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":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} {"type":"discussion","id":"DSC-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"}]} diff --git a/discussion/workflow/plans/PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md b/discussion/workflow/plans/PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md index 3ea1fde2..c2ca18a4 100644 --- a/discussion/workflow/plans/PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md +++ b/discussion/workflow/plans/PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md @@ -2,7 +2,7 @@ id: PLN-0100 ticket: pbs-symbol-documentation-and-hover-markdown title: Author Doc metadata for stdlib SDK and interface declarations -status: open +status: done created: 2026-07-15 ref_decisions: [DEC-0039] tags: [compiler, compiler-pbs, studio, lsp, vscode, editor, hover, documentation, markdown] diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java index 9bef5713..aab9d6d2 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java @@ -388,10 +388,17 @@ final class PbsDeclarationParser { final var methods = new ArrayList(); final var ctors = new ArrayList(); while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + var attributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes = attributeParser.parseAttributeList(); + } if (cursor.match(PbsTokenKind.FN)) { - methods.add(parseFunctionLike(cursor.previous(), ReadOnlyList.empty())); + methods.add(parseFunctionLike(cursor.previous(), attributes)); continue; } + if (!attributes.isEmpty()) { + reportAttributesNotAllowed(attributes, "Attributes are valid only on struct methods in struct bodies"); + } if (cursor.match(PbsTokenKind.CTOR)) { ctors.add(parseCtorDeclarationInBody(cursor.previous())); continue; @@ -451,13 +458,20 @@ final class PbsDeclarationParser { consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body"); final var methods = new ArrayList(); while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + var methodAttributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + methodAttributes = attributeParser.parseAttributeList(); + } 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, "Service body accepts only method declarations"); cursor.advance(); 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"); return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), attributes, span(declareToken.start(), rightBrace.end())); @@ -708,17 +722,16 @@ final class PbsDeclarationParser { return PbsAst.LifecycleMarker.NONE; } - if (!isOrdinaryMode()) { - reportAttributesNotAllowed(attributes, "Attributes are not allowed before top-level functions"); - return PbsAst.LifecycleMarker.NONE; - } - PbsAst.LifecycleMarker marker = PbsAst.LifecycleMarker.NONE; for (final var attribute : attributes) { final PbsAst.LifecycleMarker nextMarker; if ("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())) { nextMarker = PbsAst.LifecycleMarker.INIT; } else if ("Frame".equals(attribute.name())) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java index 7cc9e63b..d73d725c 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java @@ -199,6 +199,7 @@ public final class PbsDeclarationSemanticsValidator { false); final var methodScope = PbsCallableScope.structMethods(ownerNameId); for (final var method : structDecl.methods()) { + validateDocAttributes(method.attributes(), "struct methods", diagnostics); validateCallable( methodScope, method.name(), @@ -238,6 +239,7 @@ public final class PbsDeclarationSemanticsValidator { final DiagnosticSink diagnostics) { final var methodScope = PbsCallableScope.serviceMethods(nameTable.register(serviceDecl.name())); for (final var method : serviceDecl.methods()) { + validateDocAttributes(method.attributes(), "service methods", diagnostics); validateCallable( methodScope, method.name(), diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/core/color/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/core/color/main.pbs index 103de9ab..5021657a 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/core/color/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/core/color/main.pbs @@ -1,61 +1,136 @@ +[Doc(markdown = """ +Represents a packed RGBA color. +""")] [BuiltinType(name = "color", version = 1)] declare builtin type Color( pub raw: int ) { + [Doc(markdown = """ + Builds a color from its packed integer representation. + + - `raw`: packed color value. + """)] [IntrinsicCall(name = "from_raw", version = 1)] 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)] 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)] 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)] fn html_rgba(value: str) -> Color; + [Doc(markdown = """ + Builds an opaque grayscale color. + + - `value`: grayscale channel value. + """)] [IntrinsicCall(name = "gray_scale", version = 1)] fn gray_scale(value: int) -> Color; + [Doc(markdown = """ + Returns the packed RGB portion of this color. + """)] [IntrinsicCall(name = "hex", version = 1)] fn hex() -> int; + [Doc(markdown = """ + Returns the alpha channel of this color. + """)] [IntrinsicCall(name = "alpha", version = 1)] fn alpha() -> int; } +[Doc(markdown = """ +Opaque black. +""")] [BuiltinConst(target = "color", name = "black", version = 1)] declare const BLACK: Color; +[Doc(markdown = """ +Opaque white. +""")] [BuiltinConst(target = "color", name = "white", version = 1)] declare const WHITE: Color; +[Doc(markdown = """ +Opaque red. +""")] [BuiltinConst(target = "color", name = "red", version = 1)] declare const RED: Color; +[Doc(markdown = """ +Opaque green. +""")] [BuiltinConst(target = "color", name = "green", version = 1)] declare const GREEN: Color; +[Doc(markdown = """ +Opaque blue. +""")] [BuiltinConst(target = "color", name = "blue", version = 1)] declare const BLUE: Color; +[Doc(markdown = """ +Opaque yellow. +""")] [BuiltinConst(target = "color", name = "yellow", version = 1)] declare const YELLOW: Color; +[Doc(markdown = """ +Opaque orange. +""")] [BuiltinConst(target = "color", name = "orange", version = 1)] declare const ORANGE: Color; +[Doc(markdown = """ +Opaque indigo. +""")] [BuiltinConst(target = "color", name = "indigo", version = 1)] declare const INDIGO: Color; +[Doc(markdown = """ +Opaque gray. +""")] [BuiltinConst(target = "color", name = "gray", version = 1)] declare const GRAY: Color; +[Doc(markdown = """ +Opaque cyan. +""")] [BuiltinConst(target = "color", name = "cyan", version = 1)] declare const CYAN: Color; +[Doc(markdown = """ +Opaque magenta. +""")] [BuiltinConst(target = "color", name = "magenta", version = 1)] declare const MAGENTA: Color; +[Doc(markdown = """ +Fully transparent color. +""")] [BuiltinConst(target = "color", name = "transparent", version = 1)] declare const TRANSPARENT: Color; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/asset/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/asset/main.pbs index cca76a92..0f34fc46 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/asset/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/asset/main.pbs @@ -1,35 +1,83 @@ +[Doc(markdown = """ +Low-level asset loading host bindings. +""")] 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)] [Capability(name = "asset")] [AssetLowering(param = 0)] 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)] [Capability(name = "asset")] 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)] [Capability(name = "asset")] 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)] [Capability(name = "asset")] fn cancel(loading_handle: int) -> int; } +[Doc(markdown = """ +High-level asset loading commands. +""")] 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) { 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 { 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 { return LowAssets.commit(loading_handle); } + [Doc(markdown = """ + Cancels an asset loading operation. + + - `loading_handle`: handle returned by `load`. + """)] fn cancel(loading_handle: int) -> int { return LowAssets.cancel(loading_handle); } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/composer/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/composer/main.pbs index 17f14894..5e739556 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/composer/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/composer/main.pbs @@ -1,16 +1,46 @@ +[Doc(markdown = """ +Low-level scene composer host bindings. +""")] 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)] [Capability(name = "composer")] 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)] [Capability(name = "composer")] fn bind_scene(scene_bank_id: int) -> int; + [Doc(markdown = """ + Unbinds the active scene bank. + """)] [Host(module = "composer", name = "unbind_scene", version = 1)] [Capability(name = "composer")] 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)] [Capability(name = "composer")] fn emit_sprite( @@ -26,23 +56,53 @@ declare host LowComposer { ) -> int; } +[Doc(markdown = """ +High-level scene composition commands. +""")] 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 { 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 { return LowComposer.bind_scene(scene_bank_id); } + [Doc(markdown = """ + Unbinds the active scene bank. + """)] fn unbind_scene() -> int { 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( glyph_id: int, palette_id: int, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/gfx/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/gfx/main.pbs index ae8f3ead..2fb3b02d 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/gfx/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/gfx/main.pbs @@ -1,67 +1,189 @@ import { Color } from @core:color; +[Doc(markdown = """ +Low-level 2D graphics host bindings. +""")] declare host LowGfx { + [Doc(markdown = """ + Clears the render target. + + - `color`: fill color. + """)] [Host(module = "gfx2d", name = "clear", version = 1)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] 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)] [Capability(name = "gfx2d")] fn draw_text(x: int, y: int, message: str, color: Color) -> void; } +[Doc(markdown = """ +High-level 2D drawing commands. +""")] declare service Gfx { + [Doc(markdown = """ + Clears the render target. + + - `color`: fill color. + """)] fn clear(color: Color) -> void { 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 { 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 { 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 { 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 { 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 { 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 { LowGfx.draw_text(x, y, message, color); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/input/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/input/main.pbs index 510ce2fb..9a137053 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/input/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/input/main.pbs @@ -1,85 +1,169 @@ +[Doc(markdown = """ +Root input device provider. +""")] [BuiltinType(name = "input", version = 1)] declare builtin type InputType() { + [Doc(markdown = """ + Returns the gamepad input state. + """)] [IntrinsicCall(name = "pad", version = 1)] fn pad() -> InputPad; + [Doc(markdown = """ + Returns the touch input state. + """)] [IntrinsicCall(name = "touch", version = 1)] fn touch() -> InputTouch; } +[Doc(markdown = """ +Represents the current gamepad state. +""")] [BuiltinType(name = "input.pad", version = 1)] declare builtin type InputPad() { + [Doc(markdown = """ + Returns the up directional button. + """)] [IntrinsicCall(name = "up", version = 1)] fn up() -> InputButton; + [Doc(markdown = """ + Returns the down directional button. + """)] [IntrinsicCall(name = "down", version = 1)] fn down() -> InputButton; + [Doc(markdown = """ + Returns the left directional button. + """)] [IntrinsicCall(name = "left", version = 1)] fn left() -> InputButton; + [Doc(markdown = """ + Returns the right directional button. + """)] [IntrinsicCall(name = "right", version = 1)] fn right() -> InputButton; + [Doc(markdown = """ + Returns the A action button. + """)] [IntrinsicCall(name = "a", version = 1)] fn a() -> InputButton; + [Doc(markdown = """ + Returns the B action button. + """)] [IntrinsicCall(name = "b", version = 1)] fn b() -> InputButton; + [Doc(markdown = """ + Returns the X action button. + """)] [IntrinsicCall(name = "x", version = 1)] fn x() -> InputButton; + [Doc(markdown = """ + Returns the Y action button. + """)] [IntrinsicCall(name = "y", version = 1)] fn y() -> InputButton; + [Doc(markdown = """ + Returns the left shoulder button. + """)] [IntrinsicCall(name = "l", version = 1)] fn l() -> InputButton; + [Doc(markdown = """ + Returns the right shoulder button. + """)] [IntrinsicCall(name = "r", version = 1)] fn r() -> InputButton; + [Doc(markdown = """ + Returns the start button. + """)] [IntrinsicCall(name = "start", version = 1)] fn start() -> InputButton; + [Doc(markdown = """ + Returns the select button. + """)] [IntrinsicCall(name = "select", version = 1)] fn select() -> InputButton; } +[Doc(markdown = """ +Represents the current touch input state. +""")] [BuiltinType(name = "input.touch", version = 1)] declare builtin type InputTouch() { + [Doc(markdown = """ + Returns the touch contact as a button state. + """)] [IntrinsicCall(name = "button", version = 1)] fn button() -> InputButton; + [Doc(markdown = """ + Returns the current touch x coordinate. + """)] [IntrinsicCall(name = "x", version = 1)] fn x() -> int; + [Doc(markdown = """ + Returns the current touch y coordinate. + """)] [IntrinsicCall(name = "y", version = 1)] fn y() -> int; } +[Doc(markdown = """ +Represents one digital input button. +""")] [BuiltinType(name = "input.button", version = 1)] declare builtin type InputButton() { + [Doc(markdown = """ + Returns true on the frame where the button becomes down. + """)] [IntrinsicCall(name = "pressed", version = 1)] fn pressed() -> bool; + [Doc(markdown = """ + Returns true on the frame where the button becomes up. + """)] [IntrinsicCall(name = "released", version = 1)] fn released() -> bool; + [Doc(markdown = """ + Returns true while the button is currently down. + """)] [IntrinsicCall(name = "down", version = 1)] fn down() -> bool; + [Doc(markdown = """ + Returns how long the button has been held. + """)] [IntrinsicCall(name = "hold", version = 1)] fn hold() -> int; } +[Doc(markdown = """ +High-level access to player input devices. +""")] declare service Input { + [Doc(markdown = """ + Returns the gamepad input state. + """)] fn pad() -> InputPad { return InputType.pad(); } + [Doc(markdown = """ + Returns the touch input state. + """)] fn touch() -> InputTouch { return InputType.touch(); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/log/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/log/main.pbs index 1d7ee6e7..2ecf0ed6 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/log/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/game/stdlib/1/sdk/log/main.pbs @@ -1,60 +1,134 @@ +[Doc(markdown = """ +Low-level logging host bindings. +""")] 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)] [Capability(name = "log")] 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)] [Capability(name = "log")] fn write_tag(level: int, tag: int, message: str) -> void; } +[Doc(markdown = """ +High-level logging commands. +""")] declare service Log { + [Doc(markdown = """ + Writes a trace log message. + + - `msg`: text to write. + """)] fn trace(msg: str) -> void { LowLog.write(0, msg); } + [Doc(markdown = """ + Writes a debug log message. + + - `msg`: text to write. + """)] fn debug(msg: str) -> void { LowLog.write(1, msg); } + [Doc(markdown = """ + Writes an informational log message. + + - `msg`: text to write. + """)] fn info(msg: str) -> void { LowLog.write(2, msg); } + [Doc(markdown = """ + Writes a warning log message. + + - `msg`: text to write. + """)] fn warn(msg: str) -> void { LowLog.write(3, msg); } + [Doc(markdown = """ + Writes a failure log message. + + - `msg`: text to write. + """)] fn failure(msg: str) -> void { 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 { 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 { 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 { 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 { 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 { LowLog.write_tag(4, tag, msg); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java index d4831506..2162cfa6 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java @@ -377,6 +377,44 @@ class PbsParserTest { 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 void shouldRecoverFromMalformedReservedDeclarationsInInterfaceModuleMode() { final var source = """