From 206cdca1ba2f749913c2dd4aeb22ad839546541c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 03:13:02 +0000 Subject: [PATCH 01/16] feat(view): render GridView relationship matrices Co-Authored-By: jason.han --- .../gridview-relationship-matrix.added.md | 1 + cmd/sysml/render_test.go | 63 ++++ cmd/sysml/usage.go | 8 +- docs/project/rdf-corpus-roundtrip.md | 23 +- docs/project/spec-compliance.md | 10 +- docs/project/view-rendering-forms.md | 53 ++- docs/reference/cli.md | 38 +- docs/reference/lsp.md | 16 +- docs/reference/repl-commands.md | 15 +- examples/gridview-relationship-matrix.sysml | 66 ++++ examples/self-model/surfaces.sysml | 16 +- internal/doc/docpdf/caption_filter_test.go | 30 +- internal/doc/docpdf/pandoc.go | 4 +- internal/doc/docrender/artwork.go | 4 +- internal/doc/docrender/captions.go | 5 +- internal/doc/docrender/diagram_test.go | 16 + internal/doc/docrender/html.go | 18 +- internal/doc/docrender/html_diagram_test.go | 20 ++ internal/doc/docrender/markdown.go | 10 +- internal/doc/queryexec/derivation.go | 255 -------------- internal/doc/queryexec/related.go | 114 +----- internal/frontend/lsp/render_test.go | 56 ++- internal/frontend/repl/meta.go | 2 +- internal/frontend/repl/view_render_test.go | 42 +++ internal/ir/docplan/diagram_test.go | 48 +++ internal/ir/view/delimited.go | 6 +- internal/ir/view/delimited_test.go | 4 +- internal/ir/view/dot_test.go | 3 + internal/ir/view/form.go | 12 +- internal/ir/view/markdown.go | 2 +- internal/ir/view/matrix.go | 268 ++++++++++++++ internal/ir/view/matrix_test.go | 220 ++++++++++++ internal/ir/view/pseudo.go | 1 + internal/ir/view/pseudo_test.go | 1 + internal/ir/view/testdata/matrix.csv.golden | 7 + .../ir/view/testdata/matrix.markdown.golden | 10 + internal/ir/view/testdata/matrix.sysml | 145 ++++++++ internal/ir/view/testdata/matrix.text.golden | 13 + internal/ir/view/testdata/matrix.tsv.golden | 7 + internal/ir/view/text.go | 9 +- internal/ir/view/view.go | 40 ++- internal/semantic/resolve/filter.go | 103 +++++- internal/semantic/resolve/unqualified.go | 2 +- internal/semantic/semantics/expose.go | 33 ++ internal/semantic/semantics/expose_test.go | 46 +++ internal/semantic/semantics/nested_test.go | 1 + .../semantic/semantics/relationship_edges.go | 333 ++++++++++++++++++ .../semantics/relationship_edges_test.go | 140 ++++++++ .../semantics/w7a_library_bases_test.go | 1 + 49 files changed, 1846 insertions(+), 494 deletions(-) create mode 100644 changes/unreleased/gridview-relationship-matrix.added.md create mode 100644 examples/gridview-relationship-matrix.sysml delete mode 100644 internal/doc/queryexec/derivation.go create mode 100644 internal/ir/view/matrix.go create mode 100644 internal/ir/view/matrix_test.go create mode 100644 internal/ir/view/testdata/matrix.csv.golden create mode 100644 internal/ir/view/testdata/matrix.markdown.golden create mode 100644 internal/ir/view/testdata/matrix.sysml create mode 100644 internal/ir/view/testdata/matrix.text.golden create mode 100644 internal/ir/view/testdata/matrix.tsv.golden create mode 100644 internal/semantic/semantics/relationship_edges.go create mode 100644 internal/semantic/semantics/relationship_edges_test.go diff --git a/changes/unreleased/gridview-relationship-matrix.added.md b/changes/unreleased/gridview-relationship-matrix.added.md new file mode 100644 index 0000000000..28b4b9f8eb --- /dev/null +++ b/changes/unreleased/gridview-relationship-matrix.added.md @@ -0,0 +1 @@ +- **Render filtered `GridView`s as relationship matrices.** Matrix views show satisfy, verify, allocation, connection, derivation, refinement and dependency relationships in text, Markdown, CSV and TSV, including unnamed exposed members; ordinary tables and other renderings keep their existing output. diff --git a/cmd/sysml/render_test.go b/cmd/sysml/render_test.go index b54950b0fb..74f7f1b35a 100644 --- a/cmd/sysml/render_test.go +++ b/cmd/sysml/render_test.go @@ -93,6 +93,67 @@ func TestRenderOfATabularView(t *testing.T) { } } +const matrixModel = `package Example { + private import StandardViewDefinitions::*; + requirement def Requirement; + requirement r : Requirement; + part def Vehicle; + part vehicle : Vehicle { + satisfy r; + } + view def RelationshipMatrixView :> GridView { + filter @SysML::SatisfyRequirementUsage; + } + view relationshipMatrix : RelationshipMatrixView { + expose vehicle::**; + } +} +` + +func TestRenderOfAGridViewRelationshipMatrix(t *testing.T) { + binary := buildCLI(t) + + for _, tc := range []struct { + form string + want string + }{ + {"text", "Example::relationshipMatrix - matrix rendering"}, + {"markdown", "| Source / Target | Example::r |"}, + {"csv", "Source / Target,Example::r\nExample::vehicle,satisfy\n"}, + {"tsv", "Source / Target\tExample::r\nExample::vehicle\tsatisfy\n"}, + } { + t.Run(tc.form, func(t *testing.T) { + got := runStreams(t, binary, matrixModel, "-render", "Example::relationshipMatrix", "-render-form", tc.form) + if got.status != exitHolds { + t.Fatalf("exit status = %d, want %d\n%s", got.status, exitHolds, got.output()) + } + if !strings.Contains(got.stdout, tc.want) { + t.Errorf("stdout is missing %q:\n%s", tc.want, got.stdout) + } + }) + } + + for _, form := range []string{"mermaid", "dot", "plantuml"} { + got := runStreams(t, binary, matrixModel, "-render", "Example::relationshipMatrix", "-render-form", form) + if got.status != exitUnevaluable || !strings.Contains(got.stderr, "matrix rendering is not written as "+form) { + t.Errorf("matrix as %s = %d\n%s", form, got.status, got.output()) + } + } + + dir := filepath.Join(t.TempDir(), "matrix") + all := runStreams(t, binary, matrixModel, "-render-all", dir) + if all.status != exitHolds { + t.Fatalf("-render-all exit status = %d, want %d\n%s", all.status, exitHolds, all.output()) + } + written, err := os.ReadFile(filepath.Join(dir, "Example.relationshipMatrix.md")) // #nosec G304 -- the test wrote this path. + if err != nil { + t.Fatalf("read -render-all matrix: %v", err) + } + if !strings.Contains(string(written), "| Source / Target | Example::r |") { + t.Errorf("-render-all matrix is missing its table:\n%s", written) + } +} + // A table is written as CSV or TSV when asked, on stdout or into a file, and a // graph-shaped view is refused either form. func TestRenderOfATableAsDelimitedValues(t *testing.T) { @@ -450,6 +511,8 @@ func TestDefaultRenderFormFollowsTheDestination(t *testing.T) { {"a table at a terminal", view.KindTable, "", true, view.FormText}, {"a table into a pipe", view.KindTable, "", false, view.FormMarkdown}, {"a table into a file", view.KindTable, "table.md", true, view.FormMarkdown}, + {"a matrix into a pipe", view.KindMatrix, "", false, view.FormMarkdown}, + {"a matrix into a file", view.KindMatrix, "matrix.md", true, view.FormMarkdown}, {"a tree at a terminal", view.KindTree, "", true, view.FormText}, {"a tree into a pipe", view.KindTree, "", false, view.FormMermaid}, } diff --git a/cmd/sysml/usage.go b/cmd/sysml/usage.go index 11afa6c839..5c56e989ea 100644 --- a/cmd/sysml/usage.go +++ b/cmd/sysml/usage.go @@ -483,7 +483,7 @@ func doc() usage.Doc { "view is Mermaid source; with Graphviz absent a positioned view falls " + "back to Mermaid under a notice saying so. -diagram-form mermaid, dot " + "or plantuml writes every graph-shaped one in that form instead, in " + - "Markdown and HTML alike, while a table-kind view stays a table. Neither " + + "Markdown and HTML alike, while a table or relationship matrix view stays a table. Neither " + "Graphviz nor PlantUML is needed to write a fence.", "-doc-form html writes semantic HTML instead, carrying each element's " + "identity and kind, styled by a stylesheet in a cascade layer your " + @@ -661,9 +661,9 @@ func registerFlags(fs *flag.FlagSet) { fs.StringVar(&outputPath, "o", "", outputUsage()) fs.StringVar(&modelChecks.compare, "compare-results", "", "Run every configuration this -migration-results file indexes — or those -action names — with its recorded runs and duration mode, or the -runs and -draws given, seeded from -seed, and table the tool's and OpenSysML's min, mean, p50, p90 and max of each observable with their relative difference") - fs.StringVar(&renderView, "render", "", "Render this view of the model instead of running it, in the form its render member states; # renders every file loaded and #: one element, kind being tree, interconnection, state, action, sequence or table, without a declared view") + fs.StringVar(&renderView, "render", "", "Render this view of the model instead of running it, in the form its render member states; # renders every file loaded and #: one element, kind being tree, interconnection, state, action, sequence, table or matrix, without a declared view") fs.StringVar(&renderAllDir, "render-all", "", "Render every declared view into this directory") - fs.StringVar(&renderForm, "render-form", "", "Form -render or -render-all writes: text, mermaid, markdown, dot, plantuml, csv or tsv (csv and tsv for a table); default from the destination for -render, each kind's machine form for -render-all") + fs.StringVar(&renderForm, "render-form", "", "Form -render or -render-all writes: text, mermaid, markdown, dot, plantuml, csv or tsv (csv and tsv for a table or matrix); default from the destination for -render, each kind's machine form for -render-all") fs.StringVar(&renderPalette, "render-palette", "", "Palette the dot, mermaid or plantuml form fills nodes from, by keyword family: okabe-ito, tol-bright, tol-muted, tol-light, brewer-set2, brewer-dark2, viridis or cividis; default black and white") fs.StringVar(&renderStyle, "render-style", "", "Drawing style of the dot or mermaid form: pilot (default), the Pilot visualizer's black and white, or cameo, the look of Cameo Systems Modeler; applies to -render, -render-all and document diagrams") fs.StringVar(&renderPorts, "render-ports", "", "How much of a part's ports -render or -render-all draws on an interconnection: minimal (default), the ports its connectors end at, each a small square on the part's border named beside it, or full, every port, labelled name : Type") @@ -672,7 +672,7 @@ func registerFlags(fs *flag.FlagSet) { fs.StringVar(&renderDoc, "render-document", "", "Compile this document definition, run its queries and write the rendered document") fs.StringVar(&renderDocsDir, "render-documents", "", "Render every document definition, linked to one another, into this directory; a document that cannot be rendered gets a page stating why and the run exits 3") fs.StringVar(&docForm, "doc-form", "", docFormUsage()) - fs.StringVar(&diagramForm, "diagram-form", "", "Form the documents' graph-shaped diagrams are written in: mermaid, dot or plantuml; unset, a positioned view is dot and any other mermaid; a table-kind view is a table either way") + fs.StringVar(&diagramForm, "diagram-form", "", "Form the documents' graph-shaped diagrams are written in: mermaid, dot or plantuml; unset, a positioned view is dot and any other mermaid; a table or matrix view is a table either way") fs.BoolVar(&docNumberFigures, "doc-number-figures", false, docNumberFiguresUsage()) fs.BoolVar(&debugMode, "debug", false, "Report every diagnostic over the whole session buffer, with the pass that produced it") diff --git a/docs/project/rdf-corpus-roundtrip.md b/docs/project/rdf-corpus-roundtrip.md index b2d9bc9616..227e11ebdb 100644 --- a/docs/project/rdf-corpus-roundtrip.md +++ b/docs/project/rdf-corpus-roundtrip.md @@ -9,7 +9,7 @@ pin in `scripts/pilot-pin.sh`. | Root | Files | |---|---| -| `committed` (everything under `examples/` outside the downloaded roots) | 41 | +| `committed` (everything under `examples/` outside the downloaded roots) | 46 | | `sysml-v2-training` | 100 | | `pilot-corpora/kerml-examples` | 58 | | `pilot-corpora/sysml-examples` | 99 | @@ -59,19 +59,19 @@ Recorded against the corpus above, reproduced byte-identically on a second run: | Verdict | Files | |---|---| -| `stable` | 354 | -| `whitespace-only` | 0 | +| `stable` | 352 | +| `whitespace-only` | 7 | | `graph-diff` | 0 | | `unwritable` | 0 | | `unparseable` | 0 | | `refused` | 0 | -| **total** | **354** | +| **total** | **359** | -So every one of the 354 files converts to Turtle, and every one comes back as the same Turtle byte -for byte. That is the source text at work: the decoder writes each file back from the -`sysx:sourceText` it carries (see [What the gate does not do](#what-the-gate-does-not-do)), so the -files that came back up to whitespace, as a different graph, or that could not be written back or -re-read from canonical notation all moved to `stable` when it landed. +So every one of the 359 files converts to Turtle: 352 come back byte-identical, and seven differ +only in normalized source-text whitespace. The decoder writes each file back from the +`sysx:sourceText` it carries (see [What the gate does not +do](#what-the-gate-does-not-do)). The per-file ratchet records the seven whitespace-only results +separately; no file was graph-different, unwritable, unparseable, or refused. The last 40 refusals were one family: a synonym, portion, event or assertion keyword on a declaration with no name of its own (`feature :>> x;`, `event m.start;`, `snapshot :>> start { … }`, @@ -158,10 +158,11 @@ to, under the same `sysx:sourceText` normalisation. | Verdict | Files | |---|---| -| `stable` | 354 | +| `stable` | 350 | +| `whitespace-only` | 7 | | `graph-diff` | 2 | | every other verdict | 0 | -| **total** | **356** | +| **total** | **359** | The two forms are one graph, so a file's two verdicts should agree, and they do for every file but two: `Vehicle Example/Annex_A_VehicleViews.sysml` and `Vehicle Example/SysML v2 Spec Annex A diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 9d5a16843b..0d9782f3f7 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2094,7 +2094,7 @@ semantics layer over the conjugation parity of the typing/specialization chain. | A namespace's `filter` restricts the imported memberships it re-exports (`package P { public import Q::*; filter @Safety; }`, KerML 8.2.4, SysML v2 7.4.4) | `symbols/filter.go` `NamespaceFiltersIn` and `symbols/index.go` `reexportGated` (each way a name is re-exported records the conditions along it as one route; a name with no route is ungated) — the index records the gate and never evaluates it; `resolve/filter.go` `Resolver.admitsUnderName` evaluates a candidate against the routes through `semantics.Model.SatisfiesElementFilter`, and `resolve/unqualified.go` `matchImport`, `resolve/qualified.go` and `symbols/index.go` `LookupDirectChildrenFrom` share that one admission test; a `filter` member of a definition or usage body — a view narrowing what its `expose` lines surface — reaches the same test through `resolve/filter.go` `Resolver.importAdmits`, which composes `symbols.NamespaceFiltersIn(scope)` with the import's own clause; a `filter` at a document's root gates that document's root-level imports alone, since each document owns its root namespace (`symbols/filter.go` `namespaceFiltersGating`, keyed per document by `symbols/index.go` `gateKeyOf`) | `symbols/index_test.go` `TestExpandWildcardImportsGatesAFilteredReexport`, `TestExpandWildcardImportsKeepsAnUnfilteredRoute`, `TestExpandWildcardImportsRecordsAGateOnce`; `resolve/filter_test.go`; `model/element_filter_test.go` (including `TestFilterMemberOfADefinitionBodyRestrictsItsImports`, `TestFilterConditionResolvesThroughTheImportsItFilters`, `TestARootFilterRestrictsOnlyItsOwnDocument`), `symbols/index_test.go` `TestRootNamespaceFiltersGateOnlyTheirOwnDocument`; `libs/loader_cache_test.go` `TestFilteredImportsSurviveCacheRestore` | ✅ Faithful (a filter restricts only what the namespace re-exports, per maintainer decision: its own directly declared members are never hidden, only the condition's own names resolve unfiltered — otherwise a condition could not name a metadata type the namespace itself imports, since the condition's own names would be filtered by the condition (`resolve.Resolver.InCondition`) — and a name reached by an unfiltered route as well as a filtered one stays visible: the routes are alternatives, and the conditions along one route are a conjunction) | | An import or expose filter restricts what that import brings in (`import P::*[@Safety];`, `expose vehicle::**[@Safety];`, SysML v2 7.4.4, 8.3.26) | `ast.Import.FilterExpr` reaches the index through `symbols/index.go` `wildcardImport.filter`, gating the memberships that import surfaces; the same `admitsUnderName` decides them, so the qualified and the unqualified route agree, including the whole-index fallback for a top-level name (`resolve/qualified.go` `lookupGlobalTop`, which a root-level filtered import would otherwise bypass); the condition's own names are resolved as references like a `filter` member's, so a typo in the clause is an unresolved reference and editor navigation over it works (`resolve/document.go`, `resolve/references.go`) | `resolve/filter_test.go`, `model/element_filter_test.go` (a filtered `expose` in a view surfaces a strict subset, an element the condition rejects is unresolvable by either route, `TestAFileLevelFilteredImportHidesWhatItRejects`, `TestAnUnresolvedNameInAnImportFilterIsReported`), `parse/element_filters.golden` | ✅ Faithful (an import's condition and the filters of the namespace it imports compose: the intersection is what the importer sees) | | A view's exposed elements are queryable (SysML v2 7.24 Views and Viewpoints, 8.3.26 Expose) | `semantics/expose.go` `Model.ExposedElements` (a view's own `expose` relationships, then those of the views it specializes since an Expose is protected, in declaration order and once each) and `Model.NestedViews` (the views in its body, to walk a view tree), enumerated by `resolve/filter.go` `Resolver.ImportedElements` — the same admission, visibility and filter gating a lookup through that import makes, so the exposed set is what the view body actually resolves | `semantics/expose_test.go` (`TestExposedElementsNamespaceWildcard`, `TestExposedElementsRecursiveExpose`, `TestExposedElementsWithAnElementFilter`, `TestExposedElementsWithAViewBodyFilter`, `TestExposedElementsOfNestedViews`, `TestExposedElementsExposingAnotherView`, `TestExposedElementsInheritedFromAViewDefinition`, `TestExposedElementsOfAViewExposingNothing`, `TestExposedElementsOfANonView`) | ✅ Faithful (an empty exposed set is no error; asking a non-view is `semantics.ErrNotAView`. The REPL surface is `%view` — `repl/view.go` `doView`) | -| A view's exposed set is rendered as the rendering its `render` member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) | `semantics/rendering.go` `Model.ViewRenderings` (the `render` members of the view and of the views it specializes) and `Model.RenderingTarget` (the rendering the member references or declares); `view/view.go` `Renderer.KindOf`, `Renderer.Render` (the kind, then the exposed set from `Model.ExposedElements`) and `Renderer.RenderExposed` (an arbitrary selected set); `view/pseudo.go` derives the `#` vocabulary from the kinds this build supports; `view/tree.go`, `view/interconnection.go` (the model's own connector and flow ends, not source text; a part's node carries the ports its definition gives it as `Node.Ports`, `featureWalk.pinPorts`/`Renderer.typedPorts`, and an end naming one is the port, `Edge.FromPort`/`Edge.ToPort`, through `featureWalk.endNode`/`memberEnd`), `view/behavior.go` (the lowered `lower.StateGraph`/`lower.ActionGraph`, never a re-parse of `symbol.Decl`), `view/table.go` (the exposed elements, the elements declared in them and the nested views, as rows), `view/sequence.go` `Renderer.renderSequence` (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), `view/text.go`, `view/mermaid.go` and `view/markdown.go` (the forms, chosen per kind by `Kind.MachineForm` in `view/form.go`) | `view/render_test.go` `TestGoldenRenderings` (text and machine-readable goldens for tree, interconnection, state, action, a filtered view and a table, from `.sysml` fixtures), `TestTreeRenderingShowsNestedViewsAndDefaults`, `TestInterconnectionRenderingDrawsConnections`; `view/interconnection_ports_test.go` (a typed part's ports pinned, named and typed with `~`, in DOT, PlantUML and text, the connectors at the pins, a portless part plain, a part def's own ports nested, each usage's pins its own), `TestStateRenderingComesFromTheLoweredGraph`, `TestActionRenderingComesFromTheLoweredGraph`, `TestRenderingUsesFilteredAndInheritedExposure`, `TestTableRenderingRows`, `TestTableFormsAreMarkdownNotMermaid`, `TestMermaidLabelsAreEscaped`; `view/sequence_test.go` `TestSequenceRenderingDrawsLifelinesAndMessages`, `TestSequenceRenderingFromTheShortViewName`, `TestSequenceRenderingReportsWhatItCannotShow`, `TestSequenceRenderingHonoursStatedOrder`, `TestSequenceRenderingReportsASuccessionCycle`, `TestSequenceMermaidDeclaresParticipantsFirst`; `view/pseudo_test.go` | ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram and a table; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table; SysML §10.2 specifies no artifact) | +| A view's exposed set is rendered as the rendering its `render` member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) | `semantics/rendering.go` `Model.ViewRenderings` (the `render` members of the view and of the views it specializes) and `Model.RenderingTarget` (the rendering the member references or declares); `view/view.go` `Renderer.KindOf`, `Renderer.Render` (the kind, then the ordinary rendering's exposed set from `Model.ExposedElements`; a selected `GridView` matrix uses `Model.ExposedMembers`) and `Renderer.RenderExposed` (an arbitrary selected set); `view/pseudo.go` derives the `#` vocabulary from the kinds this build supports; `view/tree.go`, `view/interconnection.go` (the model's own connector and flow ends, not source text; a part's node carries the ports its definition gives it as `Node.Ports`, `featureWalk.pinPorts`/`Renderer.typedPorts`, and an end naming one is the port, `Edge.FromPort`/`Edge.ToPort`, through `featureWalk.endNode`/`memberEnd`), `view/behavior.go` (the lowered `lower.StateGraph`/`lower.ActionGraph`, never a re-parse of `symbol.Decl`), `view/table.go` (the ordinary exposed elements, the elements declared in them and the nested views, as rows), `view/sequence.go` `Renderer.renderSequence` (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), `view/text.go`, `view/mermaid.go` and `view/markdown.go` (the forms, chosen per kind by `Kind.MachineForm` in `view/form.go`); relationship-matrix behavior is detailed in the [GridView Relationship Matrix](#gridview-relationship-matrix) row | `view/render_test.go` `TestGoldenRenderings` (text and machine-readable goldens for tree, interconnection, state, action, a filtered view and a table, from `.sysml` fixtures), `TestTreeRenderingShowsNestedViewsAndDefaults`, `TestInterconnectionRenderingDrawsConnections`; `view/interconnection_ports_test.go` (a typed part's ports pinned, named and typed with `~`, in DOT, PlantUML and text, the connectors at the pins, a portless part plain, a part def's own ports nested, each usage's pins its own), `TestStateRenderingComesFromTheLoweredGraph`, `TestActionRenderingComesFromTheLoweredGraph`, `TestRenderingUsesFilteredAndInheritedExposure`, `TestTableRenderingRows`, `TestTableFormsAreMarkdownNotMermaid`, `TestMermaidLabelsAreEscaped`; `view/sequence_test.go` `TestSequenceRenderingDrawsLifelinesAndMessages`, `TestSequenceRenderingFromTheShortViewName`, `TestSequenceRenderingReportsWhatItCannotShow`, `TestSequenceRenderingHonoursStatedOrder`, `TestSequenceRenderingReportsASuccessionCycle`, `TestSequenceMermaidDeclaresParticipantsFirst`; `view/pseudo_test.go` | ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram, a table and a filtered GridView relationship matrix; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table or matrix; SysML §10.2 specifies no artifact) | | A rendering this build does not produce, a name that is no view, a view exposing nothing, and an exposed element a rendering cannot represent are each explicit | `view/view.go` `UnsupportedKindError` (wrapping `view.ErrUnsupportedKind`, naming the kind, the view and the rendering it stated), `Renderer.Render` (`semantics.ErrNotAView` for a non-view, as `%view` answers), `Rendering.Empty` and `view/text.go` (an empty artifact saying whether the view exposes nothing or nothing exposed was representable), `Rendering.Notices` (what a kind could not draw); `cmd/sysml/render.go` reports and skips unsupported declared views and incompatible forced forms during `-render-all`, prefixing each notice with its source view | `view/render_test.go` `TestUnsupportedRenderingKinds`, `TestRenderingSomethingThatIsNoView`, `TestRenderingAViewExposingNothing`, `TestRenderingReportsWhatItCannotRepresent`; `repl/view_render_test.go` `TestRenderOfAnUnsupportedKindNamesIt`, `TestRenderOfANonViewIsTyped`, `TestRenderOfAViewExposingNothingSaysSo`; `cmd/sysml/render_test.go` `TestRenderReportsWhatItCouldNotDo`, `TestRenderAllSkipsUnsupportedKindsAndWrongForcedForms`, `TestRenderAllPrefixesRenderingNoticesWithTheirView` | ✅ Faithful (a stated kind that is not produced is a typed error naming it, never a substituted rendering; a form the kind is not written in is a `WrongFormError` naming the one it is; an element that cannot be drawn is reported, not dropped. A one-view render stops on either typed error; a render-all run skips only that view and renders the rest) | | A graph-shaped rendering (tree, interconnection, state, action) is also written as Graphviz DOT, an alternative to Mermaid for Graphviz toolchains and large-graph layouts, without a Graphviz installation: a `digraph` with `// view:`, `// kind:`, `// stated:`, `// not represented:`, `// canvas:` and `// layout:` header comments, `rankdir` from the direction, containment as `subgraph "cluster_"` (a tree as `arrowhead=none` edges, as its Mermaid form), an edge at a cluster drawn to a node inside it and clipped with `lhead`/`ltail`, state pseudo-states as `point`/`circle`/`doublecircle`, states as rounded boxes, regions as dashed clusters, transition labels as the state writer's trigger/guard/effect text, and edge kinds parallel to the Mermaid arrows (connection `arrowhead=none`, flow `style=dashed`, transition and succession solid); every identifier and label is quoted through one helper; the DiagramLayout geometry is written as Graphviz reads it, one pixel to one point with y flipped from the library's y-down origin (`inputscale=72`, `dpi=72`; y measured from the canvas's bottom edge, negated with no canvas height): a positioned node pinned at the centre of its box with `pos="x,y!", pin=true`, its size as `width`/`height` in inches — a stated size with `fixedsize=true` and the label fitted to it (the head wrapped at the width and shrunk from 14 pt to 8 pt until it fits, the keyword and detail lines kept only while height remains, an overrunning head ellipsized), an unstated one fitted to the label so the box's corner stays where the Layout put it — `collapsed` as `comment="collapsed"`, a positioned cluster's `bb` stated (its stated box, or the one from its corner round its positioned members) and its anchor pinned at the centre, a `Route` as the edge's `pos` B-spline through its waypoints (a route of one waypoint drawn as no line and noticed as `// not represented:`), a sized `Canvas` as an invisible point pinned at each corner so the drawing's bounding box is the canvas, and the `// layout:` header naming `neato -n2` when every node is placed and any edge routed, `neato -n` when every node is placed and none routed, `neato` when some nodes are, `dot` when none; sequence and table have no DOT form and are the same `WrongFormError` the other forms raise | `view/dot.go` `Rendering.DOT`, `Rendering.DOTWith`, `dotWriter` (`engine`, `graphAttributes`, `dotNodeAttributes`, `dotPin`, `dotBox`, `dotClusterAttributes`, `dotAnchorAttributes`, `clusterBox`, `dotEdgeAttributes`, `dotSpline`, `flipY`, `dotInches`), `dotQuote`; `view/form.go` `FormDot`, `Forms`, `DiagramForms`, `Kind.SupportsForm`, `Options`, `Write`, `WriteWith`; `cmd/sysml/render.go` (`-render-form dot`, `.dot` under `-render-all`); `repl/meta.go` (`%render dot`); `lsp/render.go` `renderForm` (`form: "dot"`) | `view/dot_test.go` `TestGoldenDOT` (`testdata/*.dot.golden` beside the Mermaid goldens, each checked by an in-test DOT syntax walker: balanced braces, every edge endpoint declared as a node or cluster, quoted identifiers), `TestDOTFormSupport`, `TestDOTQuotesEveryIdentifierAndLabel`, `TestDOTNestedClusters`, `TestDOTDirections`, `TestDOTEdgeKinds`, `TestDOTStateShapesAndLabels`, `TestDOTEmptyAndNotices`, `TestDOTWritesTheGeometry` (`testdata/layout.dot.golden` beside the Mermaid and text goldens of the layout fixture; the flipped axis with and without a canvas height; states and transitions placed), `TestDOTPinsEveryNode` (`neato -n` and `neato -n2`, the one-waypoint notice, a stated, a member-fitted and a corner-only cluster's `bb` and anchor, a tree's positioned parent, pseudo-state centring, the zero-extent, unit-only and unpositioned canvas); the syntax walker parses every `pos` and `bb`; `cmd/sysml/render_test.go` `TestRenderDotForm`; `repl/view_render_test.go` `TestRenderWritesDotWhenAskedFor`; `lsp/render_test.go` `TestRenderWritesDotWhenAskedFor` | ✅ Faithful to the notation, tool-defined in output (Mermaid stays the machine-readable form `Kind.MachineForm` chooses; DOT is written on request. Producing the DOT needs no `dot` binary, and the goldens are validated by the in-test syntax walker rather than by `dot -Tsvg`; the one place Graphviz runs is the PDF backend, drawing the block on request when a Graphviz is installed (the document-rendering rows below). A `Route` is written as the polyline through its waypoints, not smoothed; a node with no stated size is given the writer's estimate of its label's extent (8.4 pt a glyph, 16.8 pt a line), not `fixedsize`, so Graphviz may grow the box for its own font and move the corner by the difference. The gRPC API has no view-render RPC — `RenderDocument` alone, to Markdown — so no wire contract carries a form) | | The DOT form draws in the Standard B&W style of the OMG SysML v2 Pilot Implementation's PlantUML visualizer (SysML v2 §8.2.2.2, the graphical notation's rendering being tool-defined), after the `sysmlbw` PlantUML skin by Hisashi Miyashita (Mgnite Inc.) shipped with the Pilot and the edge rules of its `SysML2PlantUMLStyle.java`, reproducing the skin's visual parameters rather than its text: Helvetica text at 14 pt on nodes and 13 pt on edges, white fills, `#181818` lines at `penwidth=0.5` on nodes and `1` on edges, a definition (`… def`, or a KerML classifier keyword) square and a usage `style="rounded,filled"`, the name in bold over the `«keyword»` line in italics at its 10 pt size, clusters unfilled with black borders — `penwidth=1.5` for a package, `0.5` for an element's cluster and a region (which keeps `style=dashed`) — a connection at `penwidth=3` with `arrowhead=none`, flow, succession and transition as before, and an unnamed initial or final pseudo-state as the filled black UML dot (`shape=circle`/`doublecircle`, `fillcolor=black`, `label=""`, `width=0.2` unless a Layout sizes it) while a named one keeps its labelled ring unless a Layout sizes it; a decision, merge, choice, fork, join, initial, final or port a Layout sizes drawn as its symbol with no inner text, its name as an `xlabel` unless the view IR marks it synthesized; every style attribute precedes the geometry in a node's list, and geometry, node IDs, label text, escaping, routes, cluster anchors, `lhead`/`ltail`, the header comments and the order of nodes and edges are the ones the DOT form always wrote | `view/dot.go` `dotNodeDefaults`, `dotEdgeDefaults`, `dotControlKinds`, `dotNodeAttributes`, `dotPseudostateAttributes`, `dotClusterAttributes`, `dotClusterPenwidth`, `dotEdgeAttributes`, `dotLabel`; `view/palette.go` `isDefinitionKind`, `kermlClassifierKinds` | `view/dot_style_test.go` `TestDOTStandardDefaults`, `TestDOTDefinitionsSquareUsagesRounded`, `TestDOTPseudostateRules`, `TestDOTClusterBorders`, `TestDOTConnectionPenwidth`, `TestDOTEscapesNamesInStyledLabels`; every `view/testdata/*.dot.golden`, reviewed so that only style attributes and the italic keyword markup moved; `docrender/testdata/*.golden.*`, `repl/view_render_test.go`, `cmd/sysml/render_test.go`, `lsp/render_test.go` | ⚠️ Approximate (the skin's 20-unit `UsageRoundCorner` is Graphviz's fixed `rounded` radius; its `Shadowing 0`, `hide circle` and `wrapWidth 300` have no Graphviz counterpart and nothing to turn off; the skin's plain-weight state title is not followed — a state's name stays bold like every other kind's, so the text, Mermaid and DOT forms read alike; the Pilot's `-[thickness=5]-` binding connectors are not drawn apart from connections because the interconnection rendering has no edge kind for them; the skin's notes, sequence, gantt, mindmap and wbs sections are out of the DOT form's scope. Producing DOT still runs no Graphviz binary; the goldens are checked by the in-test syntax walker, and a Graphviz installation is used only by hand to look at them) | @@ -2549,7 +2549,7 @@ boundaries; the landed Track E behavior is recorded in the execution rows and th **Intentionally Unspecified (No Normative Semantics):** - Verification verdict evaluation (VerdictKind/PassIf) - SysML v2 §9.3.2: "evaluation... intentionally not specified normatively". OpenSysML *does* report a verdict — it runs the case body as it runs an analysis case body and reports the `VerdictKind` that run produced, computed by the library's own `PassIf` calculation where the body calls it, `inconclusive` where the body produces no verdict value and `error` where the run failed — but that reading of the body is this tool's, not a normative one, and it is reported beside the requirement-satisfaction verdicts rather than replacing them; nested subcases are reported individually because the library states no roll-up. See the Verification Case map - Variability/variation selection - SysML v2 §9.4: "Selection of variants is not specified normatively" — OpenSysML selects the variant a variation usage is bound to (`attribute :>> cut = cut::cutIdeal;`) and errors on an unselected, unknown, or multiply-selected variation; see the Variation and Variant map -- View/viewpoint rendering - SysML v2 §10.2: "rendering semantics intentionally left to tools". OpenSysML *does* render a view — a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram or a table, as text, Mermaid or a Markdown table (`%render`, `sysml -render`; see the rendering rows) — but the artifact itself is this tool's output, not a normative form, and the kinds it does not produce are a typed `view.UnsupportedKindError` rather than a substituted rendering. The viewpoint conformance verdicts OpenSysML reports are likewise tool-defined (see the viewpoint conformance row) +- View/viewpoint rendering - SysML v2 §10.2: "rendering semantics intentionally left to tools". OpenSysML *does* render a view — a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram, a table or a `GridView` relationship matrix, as text, Mermaid, Markdown or delimited records (`%render`, `sysml -render`; see the rendering rows) — but the artifact itself is this tool's output, not a normative form, and the kinds it does not produce are a typed `view.UnsupportedKindError` rather than a substituted rendering. The matrix admits unnamed members through its opt-in exposure path; ordinary renderings continue to omit unnamed exposed members. The viewpoint conformance verdicts OpenSysML reports are likewise tool-defined (see the viewpoint conformance row) - Allocation execution - SysML v2 §9.2.4: syntax defined, execution semantics not normative **Implementable But Not Yet Done:** @@ -2574,6 +2574,12 @@ boundaries; the landed Track E behavior is recorded in the execution rows and th --- +### GridView Relationship Matrix + +| Rule | Implementation | Tests | Status | +| --- | --- | --- | --- | +| A standard `GridView` with a resolved positive relationship selector renders the selected relationships as a matrix. SysML metaclass selectors match by identity; refinement and derivation metadata selectors also match specializations. `render asElementTable`, unknown selectors, and selectors nested under `not` retain table rendering. Matrix exposure uses `Model.ExposedMembers` and admits unnamed members through the view's own and inherited filters; ordinary tables and renderings continue to use `Model.ExposedElements`. Relationship edges are extracted once in semantics and query execution delegates to that model API. Rows and columns follow first edge occurrence; cells deduplicate keywords in the fixed order `satisfy`, `verify`, `allocate`, `connect`, `derive`, `refine`, `dependency`. Direct matrix output supports text, Markdown, CSV and TSV; `#matrix` and `#matrix:` use the same rendering. Document Diagram blocks treat matrices as tables for every diagram form. | `semantics/relationship_edges.go` `Model.RelationshipEdgesOf`; `semantics/expose.go` `Model.ExposedMembers`, `Model.ViewExposureConditions`; `resolve/filter.go` `ImportedMembersInto`, `ViewExposureConditions`; `doc/queryexec/related.go` delegates relationship scans; `view/view.go` `Renderer.KindOf`, `Renderer.Render`, `Renderer.RenderExposed`; `view/matrix.go` `matrixShownKinds`, `renderMatrix`, `matrixEdges`; `docrender/markdown.go`, `docrender/html.go` | `semantics/relationship_edges_test.go`; `semantics/expose_test.go`; `view/matrix_test.go` and `view/testdata/matrix.sysml`; `cmd/sysml/render_test.go`; `repl/view_render_test.go`; `lsp/render_test.go`; `docplan/diagram_test.go`; `docrender/diagram_test.go`; `docrender/html_diagram_test.go` | ✅ Implemented (the ordinary exposed-element path is preserved; unnamed-member enumeration is opt-in to matrix rendering) | + ## Implementation Files ### Runtime Execution (`internal/exec/runtime/`) diff --git a/docs/project/view-rendering-forms.md b/docs/project/view-rendering-forms.md index 839a0e351f..a8c2ea80ca 100644 --- a/docs/project/view-rendering-forms.md +++ b/docs/project/view-rendering-forms.md @@ -13,7 +13,7 @@ PlantUML compare, and what the writers emit — the ## The rendering and its forms A view renders into a `view.Rendering` (`internal/ir/view/view.go`): the kind (`tree`, -`interconnection`, `state`, `action`, `sequence`, `table`), typed nodes with an identifier, a +`interconnection`, `state`, `action`, `sequence`, `table`, `matrix`), typed nodes with an identifier, a kind, a name, the declared type of a typed usage, an optional detail holding the notes (`initial`, `already shown`, `own flow`) and their children, edges with a label and an `EdgeKind` (connection, binding, transition, succession, flow), a table's columns and rows, the origin of every node @@ -41,18 +41,57 @@ A **form** is a writer over that tree (`internal/ir/view/form.go`): | Form | Writer | Kinds | Role | | --- | --- | --- | --- | | `text` | `text.go` | every kind | What a person reads at a terminal | -| `markdown` | `markdown.go` | `table` | The machine-readable form of a table | -| `csv`, `tsv` | `delimited.go` | `table` | A table as comma- or tab-separated values, for spreadsheets and scripts | +| `markdown` | `markdown.go` | `table`, `matrix` | The machine-readable form of a table or relationship matrix | +| `csv`, `tsv` | `delimited.go` | `table`, `matrix` | A table or relationship matrix as comma- or tab-separated values, for spreadsheets and scripts | | `mermaid` | `mermaid.go` | `tree`, `interconnection`, `state`, `action`, `sequence` | The default machine-readable form of the graph-shaped kinds | | `dot` | `dot.go` | `tree`, `interconnection`, `state`, `action` | Graphviz DOT, the alternative to Mermaid | | `plantuml` | `plantuml.go` | `tree`, `interconnection`, `state`, `action`, `sequence` | PlantUML in the Pilot visualizer's B&W style, for PlantUML toolchains | -`Kind.MachineForm` chooses the form a tool gets when none is asked for — `markdown` for a table, +`Kind.MachineForm` chooses the form a tool gets when none is asked for — `markdown` for a table or matrix, `mermaid` for everything else — and `Kind.SupportsForm` decides whether a kind can be written in a form at all. Asking for a form the kind is not written in is one typed `WrongFormError`, naming -the kind, the form asked and the form the kind uses, on every surface: the CLI stops with status 2 -(`-render-all` skips the view and says so), the REPL prints the usage, the LSP refuses the request, -and a document's `Diagram` block is refused at planning time. +the kind, the form asked and the form the kind uses, on the CLI (`-render-all` skips the view and +says so), in the REPL and in an LSP render request. A document `Diagram` block renders table and +matrix kinds as tables in every diagram form; other incompatible forms are refused at planning +time. + +## Relationship matrices + +`matrix` is a tabular rendering for a standard `GridView`. It is selected only when a positive, +resolved `@T` selector in the view's own or inherited filters, or in the filters of its own or +inherited exposes, names a relationship kind below. A selector nested under logical `not` does +not activate it. SysML metaclass selectors match by identity; the two metadata selectors match +their specializations as well. An explicit `render asElementTable;` remains an ordinary table, +and an unknown or unrelated selector leaves the view's existing table behavior unchanged. + +| Selector | Relationship kinds | +| --- | --- | +| `SysML::SatisfyRequirementUsage` | `satisfy` | +| `SysML::VerificationCaseUsage`, `SysML::VerificationCaseDefinition` | `verify` | +| `SysML::AllocationUsage` | `allocate` | +| `SysML::ConnectionUsage` | `connect`, `allocate`, `derive` | +| `SysML::InterfaceUsage` | `connect` | +| `SysML::Dependency` | `dependency`, `refine` | +| `ModelingMetadata::Refinement` | `refine` | +| `RequirementDerivation::DerivationMetadata` | `derive` | + +The matrix's rows are relationship sources and its columns are targets. It admits unnamed members +through the matrix-only `ExposedMembers` path, then walks named and unnamed owned members to the +tree depth limit, without descending into nested views. Ordinary tables and other renderings keep +their existing named-member exposure and do not gain unnamed rows. A source and target occupy +their first-seen +positions; repeated edges between a pair collapse into one cell, whose comma-separated keywords +are ordered `satisfy`, `verify`, `allocate`, `connect`, `derive`, `refine`, `dependency`. Labels +use the table's qualified element names, and each row retains its source origin. + +An exposed top-level member whose subtree contributes no displayed edge is named in a notice, in +exposure order; the notice names the selected relationship kinds in the fixed cell-keyword order. +An empty matrix distinguishes a view that exposed nothing from one whose exposed members have no +relationships. `#matrix` renders all loaded content as a matrix, and +`#matrix:` renders one declared element directly; neither changes exposure for ordinary +tables or any other rendering kind. Like a table, a matrix is written in `text`, `markdown`, `csv` +and `tsv`. Mermaid, DOT and PlantUML have no table grammar, so requesting one of those forms is a +typed wrong-form error naming `matrix`. ## Node labels diff --git a/docs/reference/cli.md b/docs/reference/cli.md index daf739d1cc..0a38661001 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -243,14 +243,14 @@ the same member-path parser as `Project` and `OrderBy`. | `--image-base-url ` | | With `--migrate`: the absolute http(s) URL a comment's relative `` — a path the View Editor serves, such as `/projects/.../png` — is resolved against, so the migrated document's `Image` block points at the server instead of losing the image (see [SysML v1 migration](sysml-v1-migration.md)) | | `--render ` | | Render this view of the model (every file named, loaded as one) instead of running it, in the form its `render` member states (see [Rendering a view](#rendering-a-view)) | | `--render-all ` | | Render every declared view into the directory, one artifact per view | -| `--render-form
` | | Form `--render` or `--render-all` writes: `text`, `mermaid`, `markdown`, `dot`, `plantuml`, `csv` or `tsv` (default: destination-dependent for `--render`, each kind's machine-readable form for `--render-all`) | +| `--render-form ` | | Form `--render` or `--render-all` writes: `text`, `mermaid`, `markdown`, `dot`, `plantuml`, `csv` or `tsv` (default: destination-dependent for `--render`, each kind's machine-readable form for `--render-all`; a matrix's machine form is `markdown`) | | `--render-palette ` | | Palette the `dot`, `mermaid` or `plantuml` form of `--render` or `--render-all` fills nodes with, by keyword family: `okabe-ito`, `tol-bright`, `tol-muted`, `tol-light`, `brewer-set2`, `brewer-dark2`, `viridis` or `cividis`; black and white when absent. Mermaid sequence diagrams cannot fill individual participants; text and Markdown ignore palettes. An unknown name is refused with the names there are (see [Rendering a view](#rendering-a-view)) | | `--render-style