diff --git a/README.md b/README.md index f87140a286..da582b6863 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ The project is under active development, with the core infrastructure operationa | Model save to notation (`%save model.sysml`, `sysml -convert sysml`) | ✅ Complete — writes the source through the formatter, so comments and spacing survive | | SysML ↔ RDF Turtle conversion (`%save model.ttl`, `sysml -convert ttl`) | 🧪 **Experimental** — packages, definitions, usages, ports, connections, values, documentation, and the nodes an action or state body states (every one of the 346 models under `examples/` converts and round-trips; what is not mapped is refused with the construct named), but the vocabulary may change without a compatibility path. Every run says so; see [the RDF mapping's status](docs/reference/rdf-mapping.md#status-experimental) and [worked example](examples/rdf-interop-demo.sysml) | | SysML v1 → v2 migration (`sysml Model.xmi -migrate sysml`, `.uml` and `.mdzip` too; migrated, not converted — `-convert` refuses a v1 model) | 🧪 **Experimental** — OMG UML 2.5 XMI with the SysML 1.x profile is read and written as v2 notation or RDF: packages, blocks, value types, properties, ports and connectors, requirements with satisfy/verify/derive, constraint blocks, instances and allocations, with a per-element migration report (mapped, approximated, unmapped, skipped). Behaviors, operations and units are not migrated yet, and the mapping may change without a compatibility path. Every run says so; see [the migration's status](docs/reference/sysml-v1-migration.md#status-experimental) | -| View rendering (`%render `, `sysml -render`) | ✅ Complete for the kinds produced — containment tree, interconnection diagram, state machine, action flow, sequence diagram and table, as indented text or in the kind's machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`, [D2](https://d2lang.com) with `-render-form d2`). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) | +| View rendering (`%render `, `sysml -render`) | ✅ Complete for the eight produced kinds — containment tree, interconnection diagram, state machine, action flow, case diagram, mixed canvas, sequence diagram and table — as indented text or in each kind's supported machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`, [D2](https://d2lang.com) with `-render-form d2`; D2 refuses case and mixed with a typed error). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) | | Constraint solving (`%check`, `%explain`, `%solve`, `%configure`, `%optimize`) | 🧪 **Experimental** — an external SMT-LIB 2 solver decides whether conditions *can* be satisfied, explains an `unsat` with a minimal unsat core, synthesises satisfying values, enumerates the variant selections a model permits and optimizes an `analysis def`'s objectives (optimization needs z3, which implements it). The solver is optional and discovered on `PATH` or through `OPENSYSML_SMT`; a build with none reports that rather than a verdict — see [installing a solver](docs/guide/01-install.md#installing-a-solver-optional) | | Source-preserving model edits (`ApplyEdits`, `model.edit()`) | ✅ Complete for four operations — set a feature's value, rename a declaration, add a member, and delete a declaration — rewriting the bytes of the model's own source so every untouched byte is identical. A rename rewrites the references to the renamed element too, and a non-cascade deletion of a referenced element is refused rather than approximated | | Standard library bundling | ✅ Complete | @@ -352,7 +352,7 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a **Current commit:** All tests pass (`go test -race ./...`), builds clean (`go build ./...`). **Test coverage:** top-level `Test` functions (counted from the `_test.go` files, as `go test ./...` runs them) covering parsers, semantics, runtime (actions, states, instances, operators, validation), behind golden ASTs, negatives, execution conformance cases, golden traces, runtime robustness cases and gRPC conformance and robustness cases. The figures are counted from the tree when the documentation site is built into the test inventory of [spec compliance](docs/project/spec-compliance.md), never committed, so a branch adding a test does not rewrite this page. A test skips only for want of something the run did not provide, and says what: the held-image round trip declines a conformance case that creates no instance, a few gate on a PDF or Mermaid toolchain, a pinned pilot artifact, the PSSM suite, a locale, a case-insensitive filesystem or a live Flexo stack, and the OMG corpus gates skip until the corpora are downloaded unless asked to fail. -**Parser coverage:** 105/105 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the non-normative `OpenSysML Libraries/OpenSysMLMathFunctions.kerml`, `OpenSysML Libraries/DocumentQueries.sysml`, `OpenSysML Libraries/IdentityMetadata.sysml`, `OpenSysML Libraries/DiagramLayout.sysml`, `OpenSysML Libraries/OOSEM.sysml`, `OpenSysML Libraries/MOSA.sysml`, `OpenSysML Libraries/StateSpaceIntegration.sysml`, `OpenSysML Libraries/Stochastic.sysml`, `OpenSysML Libraries/RandomFunctions.kerml`, `OpenSysML Libraries/Simulation.sysml` and `OpenSysML Libraries/MigrationMetadata.sysml` extensions. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). +**Parser coverage:** 109/109 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 15 non-normative OpenSysML extensions: `OpenSysML Libraries/AnalysisRecords.sysml`, `DiagramLayout.sysml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `MOSA.sysml`, `MigrationMetadata.sysml`, `OOSEM.sysml`, `OpenSysMLMathFunctions.kerml`, `OpenSysMLRenderings.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateMachines.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml` and `SysMLValidation.sysml`. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). **Behavioral execution:** Calc/constraint/requirement/satisfy functional. Action/state executors handle nested invocation, control flow keywords, loop and conditional statements and the send statement (every conformance case passing). Coverage is self-assessed against the specification text and the normative library: the pinned OMG pilot implementation evaluates expressions but does not execute actions or state machines headlessly, so no external implementation currently adjudicates these rows. See [spec compliance](docs/project/spec-compliance.md). **Reference differential:** 386 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 357 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`. **Rejection oracle:** the reverse direction — do we reject what the reference rejects? 312 hand-written invalid models validated by both implementations, 303 rejected by both, 0 the pinned pilot rejects and we accept; the remainder only we reject — the control-node succession rules the pinned pilot leaves unimplemented and a non-Boolean succession guard it accepts once the standard library types it — and every permissiveness gap is enumerated with a reproducer and likely root cause in [the rejection oracle](docs/project/pilot-rejection.md), reproducible with `go run -C tools ./cmd/pilot-reject`. We wrote every case, so the count measures our coverage of the rejection surface, not our conformance — a sample, not a proof. diff --git a/changes/unreleased/case-mixed-rendering.added.md b/changes/unreleased/case-mixed-rendering.added.md new file mode 100644 index 0000000000..fb3b3b45b3 --- /dev/null +++ b/changes/unreleased/case-mixed-rendering.added.md @@ -0,0 +1 @@ +- **Add case and mixed diagrams.** Case views show use, analysis and verification cases with their actors, subjects, objectives and inclusion relationships; mixed views combine structure, state, action and case content on one canvas. diff --git a/changes/unreleased/case-mixed-rendering.fixed.md b/changes/unreleased/case-mixed-rendering.fixed.md new file mode 100644 index 0000000000..b8d846f68b --- /dev/null +++ b/changes/unreleased/case-mixed-rendering.fixed.md @@ -0,0 +1 @@ +- **Keep case-specific rendering behavior isolated.** Case and mixed views use the `OpenSysMLRenderings` library package, read objective documentation without mutating shared semantic models, and limit their specialized shapes and labels to the new kinds. diff --git a/changes/unreleased/plantuml-case-mixed-links.fixed.md b/changes/unreleased/plantuml-case-mixed-links.fixed.md new file mode 100644 index 0000000000..5004f48e41 --- /dev/null +++ b/changes/unreleased/plantuml-case-mixed-links.fixed.md @@ -0,0 +1 @@ +- **Make case and mixed PlantUML links clickable in SVG.** Mixed control circles and each non-empty objective-note line retain source hyperlinks. diff --git a/changes/unreleased/wasm-engine-dependency-check.changed.md b/changes/unreleased/wasm-engine-dependency-check.changed.md new file mode 100644 index 0000000000..ef0ac03e7d --- /dev/null +++ b/changes/unreleased/wasm-engine-dependency-check.changed.md @@ -0,0 +1 @@ +- **The WebAssembly gate checks `sysml-engine`'s dependencies directly.** Both the `js` and `wasip1` builds fail on protobuf, Connect, the gRPC and stdio transports, the analysis framework and its engines, the document backends, the workspace pipeline and the converters. The gzipped size budget rises to 7,700,000 bytes and now bounds growth only, so view-rendering growth no longer reads as a forbidden dependency. diff --git a/cmd/sysml/main.go b/cmd/sysml/main.go index a8fe9a1877..58d044ed7f 100644 --- a/cmd/sysml/main.go +++ b/cmd/sysml/main.go @@ -406,7 +406,7 @@ func runCLI() int { return 2 } if renderPorts != "" && renderView == "" && renderAllDir == "" { - fmt.Fprintln(os.Stderr, "sysml: -render-ports is how much of a part's ports -render or -render-all draws on an interconnection; name the view to render with -render or a directory with -render-all") + fmt.Fprintln(os.Stderr, "sysml: -render-ports is how much of a part's ports -render or -render-all draws on an interconnection or mixed rendering; name the view to render with -render or a directory with -render-all") return 2 } if renderUnplaced != "" && renderView == "" && renderAllDir == "" && renderDoc == "" && renderDocsDir == "" { diff --git a/cmd/sysml/render_test.go b/cmd/sysml/render_test.go index 7b471c4495..fc55a3aa36 100644 --- a/cmd/sysml/render_test.go +++ b/cmd/sysml/render_test.go @@ -16,9 +16,14 @@ import ( // renderModel declares a view stating no rendering, one exposing nothing, and a // part def to ask for by mistake. const renderModel = `package Demo { + private import OpenSysMLRenderings::*; part def Vehicle { part wheel : Wheel; } part def Wheel; + part def Person; + use case def Mission { actor pilot : Person; } view overview { expose Demo::Vehicle; } + view cases { expose Demo::Mission; render asCaseDiagram; } + view mixed { expose Demo; render asMixedDiagram; } view parts { expose Demo::Vehicle; render Views::asElementTable; } view empty; } @@ -46,6 +51,29 @@ func TestRenderWritesTheArtifactOnStdout(t *testing.T) { } } +func TestRenderCaseAndMixedViewsAndPseudoViews(t *testing.T) { + binary := buildCLI(t) + cases := []struct { + view string + flow string + }{ + {"Demo::cases", "flowchart LR"}, + {"#case", "flowchart LR"}, + {"#case:Demo::Mission", "flowchart LR"}, + {"#mixed", "flowchart TD"}, + {"#mixed:Demo::Mission", "flowchart TD"}, + } + for _, tc := range cases { + got := runStreams(t, binary, renderModel, "-render", tc.view) + if got.status != exitHolds { + t.Fatalf("-render %s: exit status = %d\n%s", tc.view, got.status, got.output()) + } + if !strings.Contains(got.stdout, tc.flow) { + t.Errorf("-render %s lacks %q:\n%s", tc.view, tc.flow, got.stdout) + } + } +} + func TestRenderTextFormAndOutputFile(t *testing.T) { binary := buildCLI(t) @@ -312,6 +340,43 @@ func TestRenderD2Form(t *testing.T) { if table.status != exitUnevaluable || !strings.Contains(table.stderr, "table rendering is not written as d2; ask for text, markdown, csv or tsv") { t.Errorf("D2 of a table = %d\n%s", table.status, table.output()) } + for _, tc := range []struct { + name, message string + }{ + {"Demo::cases", "case rendering is not written as d2; ask for text, mermaid, dot or plantuml"}, + {"Demo::mixed", "mixed rendering is not written as d2; ask for text, mermaid, dot or plantuml"}, + } { + got := runStreams(t, binary, renderModel, "-render", tc.name, "-render-form", "d2") + if got.status != exitUnevaluable || got.stdout != "" || !strings.Contains(got.stderr, tc.message) { + t.Errorf("D2 of %s = %d\n%s", tc.name, got.status, got.output()) + } + } + + dir = filepath.Join(t.TempDir(), "case-mixed") + got = runStreams(t, binary, renderModel, "-render-all", dir, "-render-form", "d2") + if got.status != exitHolds { + t.Fatalf("-render-all as d2: exit status = %d\n%s", got.status, got.output()) + } + files, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + wantFiles := []string{"Demo.empty.d2", "Demo.overview.d2"} + if len(files) != len(wantFiles) { + t.Errorf("-render-all as d2 wrote %v, want %v", files, wantFiles) + } else { + for i, name := range wantFiles { + if files[i].Name() != name { + t.Errorf("-render-all as d2 wrote %v, want %v", files, wantFiles) + break + } + } + } + for _, name := range []string{"Demo::cases", "Demo::mixed"} { + if !strings.Contains(got.stderr, name+": skipped:") || !strings.Contains(got.stderr, "not written as d2") { + t.Errorf("-render-all did not refuse %s as d2:\n%s", name, got.stderr) + } + } } // -render-palette fills the DOT and Mermaid forms' nodes from a named palette, @@ -1049,7 +1114,7 @@ const portedModel = `package Demo { } ` -// -render-ports chooses how much of a part's ports an interconnection draws: +// -render-ports chooses how much of a part's ports an interconnection or mixed rendering draws: // minimal, the default, the ports a connector ends at, named alone; full, every // port, typed. A name that is neither is refused with the two there are, and // the flag without something to render likewise. diff --git a/cmd/sysml/usage.go b/cmd/sysml/usage.go index 94bd890ebe..4fcc98c3f0 100644 --- a/cmd/sysml/usage.go +++ b/cmd/sysml/usage.go @@ -432,10 +432,12 @@ func doc() usage.Doc { "by its qualified name with :: as . and every byte unsafe in a filename " + "(/, \\, :, ., %, control characters, what Windows reserves) as %XX; " + "a name past 255 bytes is cut and tagged ~ and a hash of the whole. " + - "A graph-shaped rendering is written as a Mermaid diagram by " + - "default, as Graphviz DOT with -render-form dot and as PlantUML with " + - "-render-form plantuml or as D2 with -render-form d2, which both also write a sequence " + - "rendering; neither Graphviz, PlantUML nor D2 is needed to write them. A table is written as a " + + "A graph-shaped rendering defaults to Mermaid. DOT writes tree, " + + "interconnection, state and action renderings, plus case and mixed; " + + "PlantUML writes those kinds and sequence. D2 writes tree, " + + "interconnection, state, action and sequence renderings, " + + "while case and mixed renderings refuse D2 with a typed error. Neither " + + "Graphviz, PlantUML nor D2 is needed to write them. A table is written as a " + "Markdown table by default, and as comma- or tab-separated values with " + "-render-form csv or tsv: a header record of the columns, then a record " + "per row, quoted as RFC 4180 quotes a field. Graph forms are drawn in the " + @@ -447,8 +449,9 @@ func doc() usage.Doc { "the Pilot visualizer's, or cameo, the look of Cameo Systems Modeler — a " + "diagram frame with a header tab, Arial text, gradient fills, compartments " + "and the UML pseudo-state symbols — for a diagram migrated from Cameo " + - "to keep its look. An interconnection draws on each part the ports " + - "its connectors end at, each a small square on the part's border " + + "to keep its look. An interconnection or mixed rendering draws on " + + "each part the ports its interconnection edges end at, each a small " + + "square on the part's border " + "named beside it; -render-ports full draws every port a part has, " + "labelled name : Type. A DiagramLayout Style on a member colours it over " + "either look, and a Note is drawn beside the member it is about. " + @@ -490,8 +493,10 @@ func doc() usage.Doc { "is installed and as a dot fence otherwise, and every other graph-shaped " + "view is Mermaid source; with Graphviz absent a positioned view falls " + "back to Mermaid under a notice saying so. -diagram-form mermaid, dot, " + - "plantuml or d2 writes every graph-shaped one in that form instead, in " + - "Markdown and HTML alike, while a table-kind view stays a table. Neither " + + "plantuml or d2 writes each graph-shaped diagram in that form where the " + + "kind supports it, in Markdown and HTML alike; D2 writes tree, " + + "interconnection, state, action and sequence, and case or mixed diagrams " + + "are refused. A table-kind view stays a table. Neither " + "Graphviz, PlantUML nor D2 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 " + @@ -670,19 +675,19 @@ 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, case, mixed, sequence or table, 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, d2, 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, d2, csv or tsv (csv and tsv for a table); D2 writes tree, interconnection, state, action and sequence renderings, not case or mixed; default from the destination for -render, each kind's machine form for -render-all") fs.StringVar(&renderPalette, "render-palette", "", "Palette the dot, mermaid, plantuml or d2 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(&renderLink, "render-link", "", "Link template for rendered elements: {file} is the path as loaded; use absolute paths for vscode:// or file:// links. Placeholders: {file}, {line}, {col}, {qname}, {id}") 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") + fs.StringVar(&renderPorts, "render-ports", "", "How much of a part's ports -render or -render-all draws on an interconnection or mixed rendering: minimal (default), the ports its interconnection edges end at, each a small square on the part's border named beside it, or full, every port, labelled name : Type") fs.StringVar(&renderUnplaced, "render-unplaced", "", "Where a graph form of a view some Layout positions puts the nodes none does: omit (default) leaves them undrawn in every form, strip draws them, in rows below the dot drawing; applies to -render, -render-all and document diagrams") 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, plantuml or d2; 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, plantuml or d2; D2 writes tree, interconnection, state, action and sequence renderings, not case or mixed; unset, a positioned view is dot and any other mermaid; a table-kind 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/guide/04-repl.md b/docs/guide/04-repl.md index 8c9378de08..a9170fa8e3 100644 --- a/docs/guide/04-repl.md +++ b/docs/guide/04-repl.md @@ -298,8 +298,9 @@ member before a name nested inside another element. `%render ` renders the exposed elements in the form the view's `render` member specifies: a containment tree with nested views as subtrees, an interconnection diagram of the exposed parts and the connections between them, a state machine's states and transitions, an action's nodes and -successions, or a table of the exposed elements. A view that specifies no rendering is drawn -as a tree: +successions, a case diagram of cases and their actors, subjects and objectives, a mixed canvas +combining structure, behavior and cases, or a table of the exposed elements. A view that specifies +no rendering is drawn as a tree: ``` sysml> %render Demo::summary @@ -316,9 +317,15 @@ view Demo::summary::detail A view that states `render asElementTable;` is rendered as aligned columns instead, listing the exposed elements, what they declare, and the views nested inside the rendered view. -`%render mermaid` writes a graph-shaped rendering as a Mermaid diagram, and -`%render markdown` writes a table as a Markdown table. Either can be pasted straight -into a Markdown document or an editor. A diagram node is labelled the way the graphical notation +The bundled `OpenSysMLRenderings` library selects cases with `render asCaseDiagram;` and mixed content +with `render asMixedDiagram;`; `CaseView` and `MixedView` provide the same choices by specialization. +For a model with no declared view, `%render #case` and `%render #mixed` draw the loaded content, and +`#case:` or `#mixed:` draws one element directly. + +`%render mermaid` writes a graph-shaped rendering as a Mermaid diagram, `dot` as Graphviz +DOT, and `plantuml` as PlantUML. `%render markdown` writes a table as a Markdown table. +These forms can be pasted straight into a document or an editor. A diagram node is labelled the way +the graphical notation heads a compartment — the kind in guillemets, `«part»`, first, then the name, `wheel : Wheel`, on the next line — while the text form above keeps the keyword leading, as the notation declares it. If you ask for a form the rendering kind does not support, the REPL tells you which form it does diff --git a/docs/guide/08-editors.md b/docs/guide/08-editors.md index cdc0cd7c8e..b5962780fc 100644 --- a/docs/guide/08-editors.md +++ b/docs/guide/08-editors.md @@ -69,8 +69,8 @@ REPL's `%view` command prints, as an SVG canvas of its own, and redraws as you e - **Content.** The panel draws a view the document declares (chosen from a dropdown when there are several) or, as is usual for a model under development, the document itself, rendered as a - tree, an interconnection diagram, a state diagram, an action flow, a sequence diagram or a - table. A view whose rendering is unsupported (`geometry`, `textual`) stays in the picker and + tree, an interconnection diagram, a state diagram, an action flow, case and mixed diagrams, + a sequence diagram or a table. A view whose rendering is unsupported (`geometry`, `textual`) stays in the picker and explains why it cannot be drawn. - **Navigation.** Clicking a node jumps to the declaration it was built from, and moving the cursor in the editor highlights the node that contains it. A node built from a standard @@ -106,7 +106,7 @@ The geometry is on every node and edge the server sends (`x`, `y`, `width`, `height`, `route`) and in the Mermaid the REPL and the document pipeline write as `%% layout:` comments, so other clients can honor it; see [Diagram layout annotations](../project/diagram-layout-annotations.md). A drag applies to the -tree, interconnection, state and action diagrams, which read the annotations back. +tree, interconnection, state, action, case and mixed diagrams, which read the annotations back. #### Exporting a diagram diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index be81d6a287..02727e9b12 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -664,7 +664,7 @@ See [the guide](../guide/) for VS Code configuration. | Standard library bundling | ✅ Complete | | LSP server implementation | ✅ Complete | -**Parser coverage:** 106/106 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 12 non-normative OpenSysML extensions: `OpenSysML Libraries/OpenSysMLMathFunctions.kerml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `OOSEM.sysml`, `DiagramLayout.sysml`, `MOSA.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml`, `AnalysisRecords.sysml` and `MigrationMetadata.sysml`. Conformance verified by [stdlib_conformance_test.go](../../internal/workspace/libs/stdlib_conformance_test.go). Grammar reference available at [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). +**Parser coverage:** 109/109 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 15 non-normative OpenSysML extensions: `OpenSysML Libraries/AnalysisRecords.sysml`, `DiagramLayout.sysml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `MOSA.sysml`, `MigrationMetadata.sysml`, `OOSEM.sysml`, `OpenSysMLMathFunctions.kerml`, `OpenSysMLRenderings.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateMachines.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml` and `SysMLValidation.sysml`. Conformance verified by [stdlib_conformance_test.go](../../internal/workspace/libs/stdlib_conformance_test.go). Grammar reference available at [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). --- diff --git a/docs/internals/design/vscode-visual-modeling.md b/docs/internals/design/vscode-visual-modeling.md index 6522186c44..c6805a6a06 100644 --- a/docs/internals/design/vscode-visual-modeling.md +++ b/docs/internals/design/vscode-visual-modeling.md @@ -34,10 +34,11 @@ sections are the design as written before the work, kept for the reasoning behin - **`internal/ir/view`** renders a view of the semantic model into a `Rendering`: nodes, edges, table rows, and the notices for what the kind could not represent. - Five kinds are produced — `tree`, `interconnection`, `state`, `action`, `table` — - read from `semantics.Model.ExposedElements`, the model's connectors, and the + Eight kinds are produced — `tree`, `interconnection`, `state`, `action`, `case`, + `mixed`, `sequence` and `table` — read from `semantics.Model.ExposedElements`, + the model's connectors, and the lowered `ActionGraph`/`StateGraph`, never from source text. `Rendering.Write` - writes it as `text`, `mermaid` or `markdown`. + writes it as `text`, `mermaid`, `dot`, `plantuml` or `markdown`. - **The frontends that use it** are `sysml -render -render-form mermaid` and the REPL's `%view`/`%render`. `Session.viewRenderer` (`internal/frontend/repl/view.go`) is the pattern: build a resolver and a @@ -76,7 +77,7 @@ opensysml/render (request) params: { textDocument: { uri }, view?: string, form?: "mermaid" | "text" | "markdown" } result: { view: string, // qualified name, as the notation writes it - kind: string, // tree | interconnection | state | action | table + kind: string, // tree | interconnection | state | action | case | mixed | sequence | table stated: string, // how the kind was decided, "" for the default form: string, // the form actually written artifact: string, // the Mermaid / text / Markdown document diff --git a/docs/internals/testing.md b/docs/internals/testing.md index 14da1acd1e..b99b01e2c2 100644 --- a/docs/internals/testing.md +++ b/docs/internals/testing.md @@ -37,7 +37,7 @@ New grammar features require a **four-layer test contract**: **Purpose:** Ensure standard library continues to parse cleanly - **Test:** `TestStdlibConformance` (internal/workspace/libs/) -- **Coverage:** 106/106 bundled library files — the 94 official SysML v2 standard library files and twelve non-normative OpenSysML extensions +- **Coverage:** 107/107 bundled library files — the 94 official SysML v2 standard library files and thirteen non-normative OpenSysML extensions - **Acceptance:** All stdlib files parse without errors - **Allowlist:** `testdata/stdlib_known_failures.txt` (currently empty) diff --git a/docs/manual/authoring.md b/docs/manual/authoring.md index f1dce31ece..4b5e428883 100644 --- a/docs/manual/authoring.md +++ b/docs/manual/authoring.md @@ -507,7 +507,8 @@ part structure : Diagram { - A **view** source carries its own rendering kind from its `render` clause; stating a `kind` on the diagram too is a conflict error. - A **plain element** source requires a `kind`: `"tree"`, - `"interconnection"`, `"state"`, `"action"`, `"table"` or `"sequence"`. + `"interconnection"`, `"state"`, `"action"`, `"case"`, `"mixed"`, `"table"` + or `"sequence"`. - `caption` is optional and renders in emphasis above the diagram. - `direction` — `"TB"`, `"LR"`, `"RL"` or `"BT"` — is accepted only by kinds drawn as directed graphs; it becomes the Mermaid flowchart direction or a @@ -518,17 +519,20 @@ part structure : Diagram { sequence diagram is a typed error. - `palette` — `"okabe-ito"`, `"tol-bright"`, `"tol-muted"`, `"tol-light"`, `"brewer-set2"`, `"brewer-dark2"`, `"viridis"` or `"cividis"` — is accepted - for graph-shaped kinds (tree, interconnection, state, action, sequence). - Mermaid, DOT, PlantUML and D2 fill applicable nodes by keyword family from that - colourblind-safe palette, a `part def` and its `part` usages sharing a hue, + for graph-shaped kinds (tree, interconnection, state, action, sequence, case, + mixed). Mermaid, DOT and PlantUML fill applicable nodes by keyword family from + that colourblind-safe palette, a `part def` and its `part` usages sharing a hue, with black text kept legible on every fill ([the palettes](../project/view-rendering-forms.md#palettes)). Mermaid sequence diagrams note that individual participants cannot be filled; HTML figures carry the palette as `data-palette`. Any other name, or a palette on a table diagram, is a typed error. - `ports` — `"minimal"` or `"full"` — is accepted by an interconnection - diagram alone. `"minimal"`, the default, draws on each part the ports a - connector of the view ends at and no other, each a small square on the + D2 fills nodes for tree, interconnection, state, action and sequence renderings; + it does not yet write case or mixed renderings and refuses them with a typed + `WrongFormError`. + `ports` — `"minimal"` or `"full"` — is accepted by an interconnection or + mixed diagram. `"minimal"`, the default, draws on each part the ports an + interconnection edge ends at and no other, each a small square on the part's border named beside it; `"full"` draws every port a part has, labelled `name : Type`. Any other name, or `ports` on another kind, is a typed error. @@ -567,7 +571,8 @@ flowchart LR Rendered with `-diagram-form dot` (`%render-document dot` in the REPL, `diagramForm: "dot"` over the LSP), every graph-shaped diagram of the -document — a `tree`, `interconnection`, `state` or `action` rendering — is a +document — a `tree`, `interconnection`, `state`, `action`, `case` or `mixed` +rendering — is a fenced ` ```dot ` block of Graphviz DOT instead, for a toolchain that lays diagrams out with Graphviz. No Graphviz installation is needed to write it: @@ -637,9 +642,10 @@ it as source under a notice. PlantUML pins no positions, so a view's honours it ([the PlantUML form](../project/view-rendering-forms.md#plantuml)). Rendered with `-diagram-form d2` (`%render-document d2`, `diagramForm: "d2"`), -every diagram — the `sequence` kind included — is a fenced ` ```d2 ` block for a -[D2](https://d2lang.com) toolchain, in the same B&W look as a `classes` block the -nodes and edges name; a part's parts and drawn ports are containers nested in it. +each `tree`, `interconnection`, `state`, `action` or `sequence` diagram is a fenced +` ```d2 ` block for a [D2](https://d2lang.com) toolchain, in the same B&W look as a +`classes` block the nodes and edges name; a part's parts and drawn ports are containers +nested in it. Case and mixed diagrams are refused with a typed unrenderable-form error. No `d2` is needed to write it: ```markdown diff --git a/docs/manual/troubleshooting.md b/docs/manual/troubleshooting.md index 30e85a20e2..49f16ec7a0 100644 --- a/docs/manual/troubleshooting.md +++ b/docs/manual/troubleshooting.md @@ -67,7 +67,7 @@ $ echo $? | No `source` | `missing-view-source` | | A plain-element source without a `kind` | `missing-diagram-kind` | | A `kind` on a declared view (which brings its own) | `conflicting-diagram-kind` | -| A kind other than `tree`/`interconnection`/`state`/`action`/`table`/`sequence` | `unsupported-diagram-kind` | +| A kind other than `tree`/`interconnection`/`state`/`action`/`case`/`mixed`/`table`/`sequence` | `unsupported-diagram-kind` | | A direction other than `TB`/`LR`/`RL`/`BT` | `invalid-direction` | | A direction on a kind that is not a directed graph (e.g. sequence) | `unsupported-direction` | | A palette other than `okabe-ito`, `tol-bright`, `tol-muted`, `tol-light`, `brewer-set2`, `brewer-dark2`, `viridis` or `cividis` | `invalid-palette` | diff --git a/docs/project/diagram-layout-annotations.md b/docs/project/diagram-layout-annotations.md index 67a38cada0..23f106e8fd 100644 --- a/docs/project/diagram-layout-annotations.md +++ b/docs/project/diagram-layout-annotations.md @@ -118,7 +118,7 @@ that: a picture lies under every element symbol or over every one, never between `Picture` belongs to a view alone, `@Picture { … }` in its body; a view carries as many as it shows, in declaration order: within each layer a later picture lies over an earlier one it overlaps, and every picture over the element symbols lies over every one under them. -The graph-shaped renderings (interconnection, tree, state and action views) draw it; a table or +The graph-shaped renderings (interconnection, tree, state, action, case and mixed views) draw it; a table or sequence rendering has no drawing surface for it, so it keeps its rows and states in its notices each picture's file and box under `not drawn`. A `Picture` names a file the way a `DocumentQueries::Image` block's `location` names a local @@ -230,9 +230,9 @@ A `Route` resolves the same way for an edge's declaring element. A `Canvas` belo view alone, stated in its body. Where a view's body states two `about` annotations of one kind for one element, the first in declaration order applies and the second is a warning. -With no view — the `#tree`, `#interconnection:X` pseudo-views and the LSP's render of a -document — only the element-level fallback applies, since there is no view body to look -in. +With no view — a `#tree`, `#interconnection:X`, `#case` or `#mixed` pseudo-view, and the +LSP's render of a document — only the element-level fallback applies, since there is no +view body to look in. The resolution is a lazy, memoized side-table query in `internal/semantic/semantics/layout.go` (`Model.LayoutOf`, `Model.RouteOf`, `Model.CanvasOf`, over `Model.LayoutSitesOf`) built diff --git a/docs/project/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index 173a046672..675ef3ddcb 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -62,7 +62,7 @@ "dir": "examples", "origin": "ours", "files": 48, - "digest": "sha256:76fc1234263ad8f3ac825b1eedf82237bdb91c9a8cb3eb966b01d5073e4bc408" + "digest": "sha256:a80914770ed799c76f966f70419f28bb618ea704eea214be0c4ca1fd16bc181b" }, { "name": "probes", @@ -75,8 +75,8 @@ "name": "opensysml-libraries", "dir": "internal/workspace/libs/stdlib/OpenSysML Libraries", "origin": "ours", - "files": 14, - "digest": "sha256:d84385f8e361e9fc58e3896e697413d5f4bd3d86c18d7858c86b4b61f915536e" + "files": 15, + "digest": "sha256:cfa4ce639ac0808be3cf88a9e01fe6b0843964bc20b8187474bb737ed9adec50" } ], "recorded": "2026-10-06" diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index a37c526678..363538193d 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -134,7 +134,7 @@ repeated runs are byte-identical. own `.kerml` fixtures out of the comparison (see the known limitation below). Beside the roots, every batch hands the reference the OpenSysML libraries -(`internal/workspace/libs/stdlib/OpenSysML Libraries`, 14 files), loaded like the standard +(`internal/workspace/libs/stdlib/OpenSysML Libraries`, 15 files), loaded like the standard library: resolved against, never validated. They are not a compared root — library files report nothing — but they are part of what a run measured, so the baseline's provenance records them as the `opensysml-libraries` input, and a changed library is a movement to adjudicate like a changed @@ -342,7 +342,7 @@ warnings read in the round below, nothing else. The reference validators are now handed the OpenSysML libraries beside the standard library on every batch: both bridges take `--extension-library DIR`, which loads a directory the way `sysml.library` is loaded — resolved against, never validated — and the harness passes -`internal/workspace/libs/stdlib/OpenSysML Libraries` (14 files, the copy this implementation +`internal/workspace/libs/stdlib/OpenSysML Libraries` (15 files, the copy this implementation compiles against) through it. The directory is not an option of the harness: it is the `OpenSysML Libraries` directory of the standard-library root this implementation itself loads (the bundled tree, or `OPENSYSML_LIBRARY_PATH` when set), so both validators always resolve the diff --git a/docs/project/roadmap.md b/docs/project/roadmap.md index a197cc33c6..cf4fafbb58 100644 --- a/docs/project/roadmap.md +++ b/docs/project/roadmap.md @@ -149,7 +149,7 @@ a regression — re-fetch before re-recording anything. |---|---| | OMG training corpus | **100/100 clean** — asserted, not ratcheted: no file reports a semantic error | | OMG pilot corpora (ratchet) | 213 files; 9 report a diagnostic [7] — 2 of 58 KerML examples, 7 of 99 SysML examples, 0 of 56 validation files — each adjudicated in [pilot-corpora.md](pilot-corpora.md) and [omg-issues.md](omg-issues.md) | -| Stdlib parser conformance | 106/106 clean — 94 vendored OMG files and 12 non-normative OpenSysML extensions [100/100: 94 and 6] | +| Stdlib parser conformance | 107/107 clean — 94 vendored OMG files and 13 non-normative OpenSysML extensions [100/100: 94 and 6] | | Execution conformance cases | 1224, all run and pass, none skipped [894] | | Known execution-conformance failures | **0** — `known_failures.txt` holds no case | | Cases admitting several outcomes | 92 `.expected.json` files list `outcomes`, each citing its derivation in the behavior semantic oracle; the harness explores every one of them under `explore` [26] | @@ -2435,7 +2435,7 @@ A view's rendering is a `view.Rendering` — typed nodes (`part def`, `state`, ` **form** is only a writer over it: `text`, `markdown`, `mermaid` and, since W1, W2 and W4 landed, `dot`, `plantuml` and `d2`, chosen by `-render-form`, `%render
`, the `opensysml/render` request the VS Code panel makes, and the document renderer, which embeds the Mermaid form in HTML and rasterizes it -through `mmdc` for PDF. The tree, interconnection, state, action and sequence kinds all render — the +through `mmdc` for PDF. The tree, interconnection, state, action, case, mixed and sequence kinds all render — the state rendering from the lowered `StateGraph` (regions, entry transitions, triggers, guards, effects), the action rendering from the `ActionGraph`, the sequence rendering as lifelines and ordered messages — so what is missing is not a diagram kind but the **formats** a rendering can diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 3aed3065b7..1f2f1aacd9 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -1897,6 +1897,7 @@ reference to an inherited element is not a name conflict. | Semantic Rule | Implementation | Test Case | Status | |--------------|----------------|-----------|--------| +| Case renderings cover the `CaseDefinition`/`CaseUsage` family, including use, analysis and verification cases; mixed renderings combine case, structural, state, action and tree content on one canvas | `view/case.go` `renderCase`; `view/mixed.go` `renderMixed`; `view/view.go` `toolRenderings`, `toolViewDefinitions`; `view/text.go`, `view/mermaid.go`, `view/dot.go`, `view/plantuml.go` | `view/case_mixed_test.go`; `view/render_test.go` `TestGoldenRenderings`; `view/dot_test.go` `TestGoldenDOT`; `view/plantuml_test.go` `TestGoldenPlantUML`; CLI, REPL, LSP and docplan rendering tests | ✅ Implemented as tool-defined rendering extensions | | `render` owns a RenderingUsage through a ViewRenderingMembership: `render asTree;` references the rendering the view uses, `render rendering r : AsTree;` declares one. No ValuePart, and no definition form. The declaration's UsageDeclaration is optional, so it may be anonymous (`render rendering : AsTree;`) | `ast/defusage.go` `UsageViewRendering`; `parser/defusage.go` `parseDefUsage` (render/frame dispatch) and `parseReferenceMemberUsage`; `symbols/builder.go` (`SymbolRenderingUsage`), `passes/typecheck.go` (typed by a `rendering def`), `semantics/implicit.go` (`Views::Rendering`), `export/kinds.go` (`ViewRenderingMembership`), `export/rdf_in.go` `usageHead` (which form to write back is decided by the reference, not the name, so an anonymous declaration keeps its kind keyword) | `parse/view_members.golden`, `export/testdata/convert/kind_keyword_synonyms.golden.sysml`, `parser/negative_test.go` (`render_definition`, `render_reference_value`), `passes/nameres_test.go` `TestRenderReferenceToInheritedRenderingIsNoConflict`, `TestRenderDeclarationOfInheritedNameConflicts`, corpus `42. Views/*` | ✅ Faithful (parse and naming; what a view *renders* is read from this member by `view/view.go` `Renderer.KindOf` — see the rendering rows) | | `frame` owns a ConcernUsage through a FramedConcernMembership: `frame 'system breakdown';` references the concern framed, `frame concern c : SafetyConcern;` declares one, possibly anonymously (`frame concern : SafetyConcern;`). Its body is a requirement body | `ast/defusage.go` `UsageFramedConcern`; `parser/defusage.go` (same dispatch, body `parseRequirementBody`); `symbols/builder.go` (`SymbolConcernUsage`), `passes/typecheck.go` (typed by a `concern def`), `semantics/implicit.go` (`Requirements::ConcernCheck`), `resolve/document.go` `parameterizedByName`, `export/kinds.go` (`FramedConcernMembership`) | `parse/view_members.golden`, `parser/negative_test.go` (`frame_definition`), corpus `42. Views/Viewpoint Example.sysml` | ⚠️ Approximate (`frame concern SafetyConcern;` follows the grammar's `ConstraintUsageDeclaration` and *declares* a concern usage named `SafetyConcern`; the reference to a concern of that name is `frame SafetyConcern;`. Framing is checked against the viewpoint's concerns by `semantics/conformance.go` `Model.ViewConformance` — see the viewpoint conformance row) | | `stakeholder` and `actor` own a PartUsage through a StakeholderMembership / ActorMembership; both are declarations (`stakeholder se : Engineer;`, `actor driver : Person;`) and neither has a definition form | `ast/defusage.go` `UsageStakeholder`, `UsageActor` (the former `ast.ActorMember` node is gone); `parser/defusage.go` `parseDefUsage`; `symbols/builder.go` (`SymbolPartUsage`), `passes/typecheck.go` (typed by a `part def`), `semantics/implicit.go` (`Parts::Part`), `runtime/context.go` `memberBindings` (actor binding, name via `ast.EffectiveName`), `export/kinds.go` | `parse/view_members.golden`, `parse/requirement_members.golden`, `parser/behavior_test.go` `TestParseRequirementBody_Actor`, `parser/negative_test.go` (`stakeholder_definition`, `actor_definition`, `stakeholder_no_declaration`, `actor_no_declaration`), `runtime` `requirement_actor.sysml`, corpus `41. Use Cases/*`, `42. Views/*` | ✅ Faithful (a stakeholder or actor *definition* is rejected: the notation has only the usage, typed by the party's definition) | @@ -2027,7 +2028,7 @@ semantics layer over the conjugation parity of the typing/specialization chain. | A `binding`/`succession` written without `of`/`first` has no declaration of its own (`KerML.xtext:875` `BindingConnectorDeclaration`, `:891` `SuccessionDeclaration`, second alternative), so a multiplicity that leads it is the first end's crossing multiplicity (`ConnectorEnd:854` `OwnedCrossingMultiplicityMember`): `binding [1] a = [1] b;` and `succession [1] a then [*] b;` put both multiplicities on the ends and none on the connector, while `binding [1] of a = b;`, `binding bb [1] of a = b;`, `succession [1] first a then b;` and `succession sn [1] first a then b;` keep `[1]` as the connector's | `parser/defusage.go` `parseBindingDeclaration`, `parseBindingEnds`, `parseConnectorEnds`, `parseConnectorEnd`; `ast.ConnectorEnd.Multiplicity` carries the end multiplicities, `ast.Usage.Multiplicity` the connector's; `export/rdf_out.go` writes each end's `sysml:lowerBound`/`sysml:upperBound` on the end node | `parse/kerml_connector_end_multiplicity.kerml` (every form, accepted by the pinned KerML validator), `parser/negative_test.go` (`binding_end_multiplicity_no_end`, `binding_end_multiplicity_unclosed`, `binding_second_end_multiplicity_no_end`, `binding_end_multiplicity_no_terminator` and the four `succession_*_multiplicity_*` counterparts), `export_test.go:TestKerMLConnectorEndMultiplicitiesAreStatedAsStructure` | ✅ Faithful (the pinned pilot reads the six Kernel Semantic Library sites — `Occurrences.kerml:207`, `:458`, `ControlPerformances.kerml:78`, `:125`, `StatePerformances.kerml:43`, `TransitionPerformances.kerml:31` — with no connector multiplicity and the stated multiplicities on the ends, as we now do; `Occurrences.kerml` written back from its graph alone keeps each `binding [1] … = [1] …` where it was, instead of moving `[1]` behind the second end. A connector's own multiplicity in the `of`/`first` forms is still written after its ends by the RDF mapping, which does not read back; that rendering is the mapping's, not the parser's) | | A `metaclass` is a Class (KerML §8.4.4), so `metaclass AtomMetadata specializes Metaobject` is a metaclass specializing a metaclass and is well-formed in either language; an unrelated definition kind is still a mismatch | `passes/typecheck.go` `defSymbolKind` (`ast.DefMetaclass` → `symbols.SymbolMetaclass`, absent before, which made every metaclass declaration incomparable with its own kind) | `passes/typecheck_kerml_language_test.go:TestTypeCheckMetaclassSpecializesMetaclass`, `:TestTypeCheckMetaclassSpecializesPartDefStillFires` | ✅ Faithful | -| An interconnection draws only the ports its connectors end at by default, or every typed port under `full`; a drawn port is declared inside its part's subgraph before its children, and `Port.ID` names both that pin and the connector endpoint. Minimal labels show the port name; full labels include `«port»` and `name : Type`. `-render-ports minimal|full` and `%render … [minimal|full]` select the display, which applies only to interconnections; action pins remain limited to edge-used ports | `view/ports.go` `portView.of`, `portView.pinLabel`; `view/interconnection.go` `Renderer.typedPorts`, `featureWalk.pinPorts`; `view/mermaid.go` `usedPorts`, `mermaidPinLabel`, `clusterTitleExtraLines`; `cmd/sysml/render.go`; `repl/view.go` | `view/ports_test.go:TestTheMinimalDisplayDrawsTheConnectedPortsAlone`, `:TestPortedAndPinlessEdgesMixUnderTheMinimalDisplay`; `view/interconnection_ports_test.go:TestTypedPartsPinTheirDefinitionsPorts`, `:TestACompositePartPinsItsPortsAndEndsItsBareEndsAtThem`; `view/mermaid_test.go:TestMermaidPortsUseOnlyConnectedPins` | ✅ Implemented | +| An interconnection or mixed rendering draws only the ports its connectors end at by default, or every typed port under `full`; a drawn port is declared inside its part's subgraph before its children, and `Port.ID` names both that pin and the connector endpoint. Minimal labels show the port name; full labels include `«port»` and `name : Type`. `-render-ports minimal|full` and `%render … [minimal|full]` select the display; action pins remain limited to edge-used ports | `view/ports.go` `portView.of`, `portView.pinLabel`; `view/interconnection.go` `Renderer.typedPorts`, `featureWalk.pinPorts`; `view/mermaid.go` `usedPorts`, `mermaidPinLabel`, `clusterTitleExtraLines`; `cmd/sysml/render.go`; `repl/view.go` | `view/ports_test.go:TestTheMinimalDisplayDrawsTheConnectedPortsAlone`, `:TestPortedAndPinlessEdgesMixUnderTheMinimalDisplay`; `view/interconnection_ports_test.go:TestTypedPartsPinTheirDefinitionsPorts`, `:TestACompositePartPinsItsPortsAndEndsItsBareEndsAtThem`; `view/mermaid_test.go:TestMermaidPortsUseOnlyConnectedPins`; `lsp/render_test.go:TestRenderMixedCarriesConnectorPortData` | ✅ Implemented | ### Name Resolution @@ -2108,17 +2109,17 @@ 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 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`, `view/case.go`, `view/mixed.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`, `view/dot.go`, `view/plantuml.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, case, mixed, 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, case and mixed diagrams, 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 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) | -| A graph-shaped rendering (tree, interconnection, state, action) and a sequence are also written as PlantUML, the language the OMG Pilot's own visualizer draws with, without Java or a PlantUML jar: an `@startuml` … `@enduml` file with the `' — rendering ()` header comment and one `' not represented:` line per notice and per loss the writer itself incurs, the Standard B&W style inline as a ` +skinparam wrapWidth 300 +hide stereotype +left to right direction +usecase "//«use case»//\n**CaseExamples::'Provide Transportation' : 'Enter Vehicle'**" as n0 <> <> +rectangle "//«subject»//\n**vehicle : Vehicle**" as n1 <> <> +actor "//«actor»//\n**driver : Person**" as n2 <> <> +note "**objective**\nTransport the vehicle and its passengers safely." as n3 +usecase "//«use case def»//\n**CaseExamples::'Add Fuel'**" as n4 <> +rectangle "//«subject»//\n**vehicle : Vehicle**" as n5 <> <> +actor "//«actor»//\n**fueler : Person**" as n6 <> <> +usecase "//«include use case»//\n**'enter vehicle' : 'Enter Vehicle'**" as n7 <> <> +rectangle "//«subject»//\n**vehicle**" as n8 <> <> +actor "//«actor»//\n**driver**" as n9 <> <> +usecase "//«use case»//\n**'Drive Vehicle'**" as n10 <> <> +rectangle "//«subject»//\n**vehicle**" as n11 <> <> +actor "//«actor»//\n**driver**" as n12 <> <> +usecase "//«analysis def»//\n**CaseExamples::FuelAnalysis**" as n13 <> +rectangle "//«subject»//\n**vehicle : Vehicle**" as n14 <> <> +note "**objective**\nEstimate the fuel remaining after a journey." as n15 +usecase "//«verification def»//\n**CaseExamples::FuelVerification**" as n16 <> +rectangle "//«subject»//\n**vehicle : Vehicle**" as n17 <> <> +note "**objective**" as n18 +n0 -- n1 : subject +n2 -- n0 +n0 .. n3 +n4 -- n5 : subject +n6 -- n4 +n0 ..> n4 : include +n0 ..> n7 : include +n7 -- n8 : subject +n9 -- n7 +n0 *-- n10 +n10 -- n11 : subject +n12 -- n10 +n13 -- n14 : subject +n13 .. n15 +n16 -- n17 : subject +n16 .. n18 +@enduml diff --git a/internal/ir/view/testdata/case.sysml b/internal/ir/view/testdata/case.sysml new file mode 100644 index 0000000000..a5caafe1ab --- /dev/null +++ b/internal/ir/view/testdata/case.sysml @@ -0,0 +1,84 @@ +package CaseExamples { + private import Views::*; + private import StandardViewDefinitions::*; + private import OpenSysMLRenderings::*; + private import DiagramLayout::*; + + part def Vehicle; + part def Person; + part def Equipment; + + requirement def FuelReady { + subject vehicle : Vehicle; + } + requirement fuelReady : FuelReady; + + use case def 'Add Fuel' { + subject vehicle : Vehicle; + actor fueler : Person; + } + + use case def 'Enter Vehicle' { + subject vehicle : Vehicle; + actor driver : Person; + } + + use case 'Provide Transportation' : 'Enter Vehicle' { + subject vehicle : Vehicle; + actor driver : Person; + objective { + doc /* Transport the vehicle and its passengers safely. */ + } + + include 'Add Fuel'[0..*] { + subject vehicle; + actor fueler = driver; + } + + include use case 'enter vehicle' : 'Enter Vehicle' { + subject vehicle; + actor driver = 'Provide Transportation'::driver; + } + + use case 'Drive Vehicle' { + subject vehicle; + actor driver = 'Provide Transportation'::driver; + } + } + + analysis def FuelAnalysis { + subject vehicle : Vehicle; + objective { + doc /* Estimate the fuel remaining after a journey. */ + } + } + + verification def FuelVerification { + subject vehicle : Vehicle; + objective { + verify fuelReady; + } + } + + part def Tool; + + view caseDiagram { + expose CaseExamples::'Provide Transportation'; + expose CaseExamples::FuelAnalysis; + expose CaseExamples::FuelVerification; + expose CaseExamples::Tool; + render asCaseDiagram; + } + + view caseDefinition : CaseView { + expose CaseExamples::'Provide Transportation'; + } + + view caseLayout { + expose CaseExamples::'Provide Transportation'; + render asCaseDiagram; + metadata Layout about CaseExamples::'Provide Transportation' { x = 40; y = 60; } + metadata Layout about CaseExamples::'Provide Transportation'::driver { x = 100; y = 60; } + metadata Route about CaseExamples::'Provide Transportation'::'Add Fuel' { points = (60, 20, 100, 60); } + } +} diff --git a/internal/ir/view/testdata/case.text.golden b/internal/ir/view/testdata/case.text.golden new file mode 100644 index 0000000000..7b449ac1b3 --- /dev/null +++ b/internal/ir/view/testdata/case.text.golden @@ -0,0 +1,42 @@ +CaseExamples::caseDiagram - case rendering (render asCaseDiagram) + +use case CaseExamples::'Provide Transportation' : 'Enter Vehicle' +subject vehicle : Vehicle +actor driver : Person +objective (Transport the vehicle and its passengers safely.) +use case def CaseExamples::'Add Fuel' +subject vehicle : Vehicle +actor fueler : Person +include use case 'enter vehicle' : 'Enter Vehicle' +subject vehicle +actor driver +use case 'Drive Vehicle' +subject vehicle +actor driver +analysis def CaseExamples::FuelAnalysis +subject vehicle : Vehicle +objective (Estimate the fuel remaining after a journey.) +verification def CaseExamples::FuelVerification +subject vehicle : Vehicle +objective + +relationships: + CaseExamples::'Provide Transportation' -- vehicle: «subject» + driver -- CaseExamples::'Provide Transportation' + CaseExamples::'Provide Transportation' .. objective n3 + CaseExamples::'Add Fuel' -- vehicle: «subject» + fueler -- CaseExamples::'Add Fuel' + CaseExamples::'Provide Transportation' ..> CaseExamples::'Add Fuel': «include» + CaseExamples::'Provide Transportation' ..> 'enter vehicle': «include» + 'enter vehicle' -- vehicle: «subject» + driver -- 'enter vehicle' + CaseExamples::'Provide Transportation' *-- 'Drive Vehicle' + 'Drive Vehicle' -- vehicle: «subject» + driver -- 'Drive Vehicle' + CaseExamples::FuelAnalysis -- vehicle: «subject» + CaseExamples::FuelAnalysis .. objective n15 + CaseExamples::FuelVerification -- vehicle: «subject» + CaseExamples::FuelVerification .. objective n18 + +not represented: + - part def CaseExamples::Tool holds no case; a case rendering does not show it diff --git a/internal/ir/view/testdata/links-case-dot.golden b/internal/ir/view/testdata/links-case-dot.golden new file mode 100644 index 0000000000..dea3612fae --- /dev/null +++ b/internal/ir/view/testdata/links-case-dot.golden @@ -0,0 +1,45 @@ +// view: CaseExamples::caseDiagram +// kind: case +// stated: render asCaseDiagram +// not represented: part def CaseExamples::Tool holds no case; a case rendering does not show it +// layout: dot +digraph "CaseExamples::caseDiagram" { + graph [fontname="Helvetica", rankdir=LR]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + "n0" [shape=ellipse, label=<«use case»
CaseExamples::'Provide Transportation' : 'Enter Vehicle'>, URL="https://example.test/src/case.sysml#L26", tooltip="CaseExamples::'Provide Transportation'"]; + "n1" [shape=box, label=<«subject»
vehicle : Vehicle>, URL="https://example.test/src/case.sysml#L27", tooltip="CaseExamples::'Provide Transportation'::vehicle"]; + "n2" [shape=box, label=<«actor»
driver : Person>, URL="https://example.test/src/case.sysml#L28", tooltip="CaseExamples::'Provide Transportation'::driver"]; + "n3" [shape=note, label=<objective
Transport the vehicle and its passengers safely.>, URL="https://example.test/src/case.sysml#L29", tooltip="case.sysml:29:3"]; + "n4" [shape=ellipse, label=<«use case def»
CaseExamples::'Add Fuel'>, URL="https://example.test/src/case.sysml#L16", tooltip="CaseExamples::'Add Fuel'"]; + "n5" [shape=box, label=<«subject»
vehicle : Vehicle>, URL="https://example.test/src/case.sysml#L17", tooltip="CaseExamples::'Add Fuel'::vehicle"]; + "n6" [shape=box, label=<«actor»
fueler : Person>, URL="https://example.test/src/case.sysml#L18", tooltip="CaseExamples::'Add Fuel'::fueler"]; + "n7" [shape=ellipse, label=<«include use case»
'enter vehicle' : 'Enter Vehicle'>, URL="https://example.test/src/case.sysml#L38", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'"]; + "n8" [shape=box, label=<«subject»
vehicle>, URL="https://example.test/src/case.sysml#L39", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'::vehicle"]; + "n9" [shape=box, label=<«actor»
driver>, URL="https://example.test/src/case.sysml#L40", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'::driver"]; + "n10" [shape=ellipse, label=<«use case»
'Drive Vehicle'>, URL="https://example.test/src/case.sysml#L43", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'"]; + "n11" [shape=box, label=<«subject»
vehicle>, URL="https://example.test/src/case.sysml#L44", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'::vehicle"]; + "n12" [shape=box, label=<«actor»
driver>, URL="https://example.test/src/case.sysml#L45", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'::driver"]; + "n13" [shape=ellipse, label=<«analysis def»
CaseExamples::FuelAnalysis>, URL="https://example.test/src/case.sysml#L49", tooltip="CaseExamples::FuelAnalysis"]; + "n14" [shape=box, label=<«subject»
vehicle : Vehicle>, URL="https://example.test/src/case.sysml#L50", tooltip="CaseExamples::FuelAnalysis::vehicle"]; + "n15" [shape=note, label=<objective
Estimate the fuel remaining after a journey.>, URL="https://example.test/src/case.sysml#L51", tooltip="case.sysml:51:3"]; + "n16" [shape=ellipse, label=<«verification def»
CaseExamples::FuelVerification>, URL="https://example.test/src/case.sysml#L56", tooltip="CaseExamples::FuelVerification"]; + "n17" [shape=box, label=<«subject»
vehicle : Vehicle>, URL="https://example.test/src/case.sysml#L57", tooltip="CaseExamples::FuelVerification::vehicle"]; + "n18" [shape=note, label=<objective>, URL="https://example.test/src/case.sysml#L58", tooltip="case.sysml:58:3"]; + "n0" -> "n1" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L27", tooltip="CaseExamples::'Provide Transportation'::vehicle"]; + "n2" -> "n0" [dir=none, URL="https://example.test/src/case.sysml#L28", tooltip="CaseExamples::'Provide Transportation'::driver"]; + "n0" -> "n3" [dir=none, style=dashed, URL="https://example.test/src/case.sysml#L29", tooltip="case.sysml:29:3"]; + "n4" -> "n5" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L17", tooltip="CaseExamples::'Add Fuel'::vehicle"]; + "n6" -> "n4" [dir=none, URL="https://example.test/src/case.sysml#L18", tooltip="CaseExamples::'Add Fuel'::fueler"]; + "n0" -> "n4" [label="«include»", style=dashed, URL="https://example.test/src/case.sysml#L33", tooltip="CaseExamples::'Provide Transportation'::'Add Fuel'"]; + "n0" -> "n7" [label="«include»", style=dashed, URL="https://example.test/src/case.sysml#L38", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'"]; + "n7" -> "n8" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L39", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'::vehicle"]; + "n9" -> "n7" [dir=none, URL="https://example.test/src/case.sysml#L40", tooltip="CaseExamples::'Provide Transportation'::'enter vehicle'::driver"]; + "n0" -> "n10" [arrowtail=diamond, dir=back, URL="https://example.test/src/case.sysml#L43", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'"]; + "n10" -> "n11" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L44", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'::vehicle"]; + "n12" -> "n10" [dir=none, URL="https://example.test/src/case.sysml#L45", tooltip="CaseExamples::'Provide Transportation'::'Drive Vehicle'::driver"]; + "n13" -> "n14" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L50", tooltip="CaseExamples::FuelAnalysis::vehicle"]; + "n13" -> "n15" [dir=none, style=dashed, URL="https://example.test/src/case.sysml#L51", tooltip="case.sysml:51:3"]; + "n16" -> "n17" [label="«subject»", dir=none, URL="https://example.test/src/case.sysml#L57", tooltip="CaseExamples::FuelVerification::vehicle"]; + "n16" -> "n18" [dir=none, style=dashed, URL="https://example.test/src/case.sysml#L58", tooltip="case.sysml:58:3"]; +} diff --git a/internal/ir/view/testdata/links-case-mermaid.golden b/internal/ir/view/testdata/links-case-mermaid.golden new file mode 100644 index 0000000000..ed0ebf7063 --- /dev/null +++ b/internal/ir/view/testdata/links-case-mermaid.golden @@ -0,0 +1,96 @@ +--- +config: + fontFamily: "Helvetica, Arial, sans-serif" + theme: base + themeCSS: ".edgeLabel rect { opacity: 1 !important; } .cluster-label .nodeLabel { text-align: center; }" + themeVariables: + fontFamily: "Helvetica, Arial, sans-serif" + fontSize: "14px" + primaryColor: "#FFFFFF" + secondaryColor: "#FFFFFF" + tertiaryColor: "#FFFFFF" + background: "#FFFFFF" + primaryBorderColor: "#181818" + primaryTextColor: "#000000" + lineColor: "#181818" + textColor: "#000000" + noteBkgColor: "#FEFFDD" + noteBorderColor: "#181818" + noteTextColor: "#000000" + clusterBkg: "#FFFFFF" + clusterBorder: "#181818" + edgeLabelBackground: "#FFFFFF" +--- +%% CaseExamples::caseDiagram — case rendering (render asCaseDiagram) +%% not represented: part def CaseExamples::Tool holds no case; a case rendering does not show it +flowchart LR + n0(["`*«use case»* +**CaseExamples::'Provide Transportation' : 'Enter Vehicle'**`"]) + n1["`*«subject»* +**vehicle : Vehicle**`"] + n2["`*«actor»* +**driver : Person**`"] + n3@{ shape: notch-rect, label: "objective
Transport the vehicle and its passengers safely." } + n4(["`*«use case def»* +**CaseExamples::'Add Fuel'**`"]) + n5["`*«subject»* +**vehicle : Vehicle**`"] + n6["`*«actor»* +**fueler : Person**`"] + n7(["`*«include use case»* +**'enter vehicle' : 'Enter Vehicle'**`"]) + n8["`*«subject»* +**vehicle**`"] + n9["`*«actor»* +**driver**`"] + n10(["`*«use case»* +**'Drive Vehicle'**`"]) + n11["`*«subject»* +**vehicle**`"] + n12["`*«actor»* +**driver**`"] + n13(["`*«analysis def»* +**CaseExamples::FuelAnalysis**`"]) + n14["`*«subject»* +**vehicle : Vehicle**`"] + n15@{ shape: notch-rect, label: "objective
Estimate the fuel remaining after a journey." } + n16(["`*«verification def»* +**CaseExamples::FuelVerification**`"]) + n17["`*«subject»* +**vehicle : Vehicle**`"] + n18@{ shape: notch-rect, label: "objective" } + n0 ---|"«subject»"| n1 + n2 --- n0 + n0 -.- n3 + n4 ---|"«subject»"| n5 + n6 --- n4 + n0 -.->|"«include»"| n4 + n0 -.->|"«include»"| n7 + n7 ---|"«subject»"| n8 + n9 --- n7 + n0 ---|"«composition»"| n10 + n10 ---|"«subject»"| n11 + n12 --- n10 + n13 ---|"«subject»"| n14 + n13 -.- n15 + n16 ---|"«subject»"| n17 + n16 -.- n18 + click n0 href "https://example.test/src/case.sysml#L26" + click n1 href "https://example.test/src/case.sysml#L27" + click n2 href "https://example.test/src/case.sysml#L28" + click n3 href "https://example.test/src/case.sysml#L29" + click n4 href "https://example.test/src/case.sysml#L16" + click n5 href "https://example.test/src/case.sysml#L17" + click n6 href "https://example.test/src/case.sysml#L18" + click n7 href "https://example.test/src/case.sysml#L38" + click n8 href "https://example.test/src/case.sysml#L39" + click n9 href "https://example.test/src/case.sysml#L40" + click n10 href "https://example.test/src/case.sysml#L43" + click n11 href "https://example.test/src/case.sysml#L44" + click n12 href "https://example.test/src/case.sysml#L45" + click n13 href "https://example.test/src/case.sysml#L49" + click n14 href "https://example.test/src/case.sysml#L50" + click n15 href "https://example.test/src/case.sysml#L51" + click n16 href "https://example.test/src/case.sysml#L56" + click n17 href "https://example.test/src/case.sysml#L57" + click n18 href "https://example.test/src/case.sysml#L58" diff --git a/internal/ir/view/testdata/links-case-plantuml.golden b/internal/ir/view/testdata/links-case-plantuml.golden new file mode 100644 index 0000000000..2782be59e1 --- /dev/null +++ b/internal/ir/view/testdata/links-case-plantuml.golden @@ -0,0 +1,88 @@ +@startuml +' CaseExamples::caseDiagram — case rendering (render asCaseDiagram) +' not represented: part def CaseExamples::Tool holds no case; a case rendering does not show it + +skinparam wrapWidth 300 +hide stereotype +left to right direction +usecase "//«use case»//\n**CaseExamples::'Provide Transportation' : 'Enter Vehicle'**" as n0 <> <> [[https://example.test/src/case.sysml#L26]] +rectangle "//«subject»//\n**vehicle : Vehicle**" as n1 <> <> [[https://example.test/src/case.sysml#L27]] +actor "//«actor»//\n**driver : Person**" as n2 <> <> [[https://example.test/src/case.sysml#L28]] +note as n3 + **[[https://example.test/src/case.sysml#L29 objective]]** + [[https://example.test/src/case.sysml#L29 Transport the vehicle and its passengers safely.]] +end note +usecase "//«use case def»//\n**CaseExamples::'Add Fuel'**" as n4 <> [[https://example.test/src/case.sysml#L16]] +rectangle "//«subject»//\n**vehicle : Vehicle**" as n5 <> <> [[https://example.test/src/case.sysml#L17]] +actor "//«actor»//\n**fueler : Person**" as n6 <> <> [[https://example.test/src/case.sysml#L18]] +usecase "//«include use case»//\n**'enter vehicle' : 'Enter Vehicle'**" as n7 <> <> [[https://example.test/src/case.sysml#L38]] +rectangle "//«subject»//\n**vehicle**" as n8 <> <> [[https://example.test/src/case.sysml#L39]] +actor "//«actor»//\n**driver**" as n9 <> <> [[https://example.test/src/case.sysml#L40]] +usecase "//«use case»//\n**'Drive Vehicle'**" as n10 <> <> [[https://example.test/src/case.sysml#L43]] +rectangle "//«subject»//\n**vehicle**" as n11 <> <> [[https://example.test/src/case.sysml#L44]] +actor "//«actor»//\n**driver**" as n12 <> <> [[https://example.test/src/case.sysml#L45]] +usecase "//«analysis def»//\n**CaseExamples::FuelAnalysis**" as n13 <> [[https://example.test/src/case.sysml#L49]] +rectangle "//«subject»//\n**vehicle : Vehicle**" as n14 <> <> [[https://example.test/src/case.sysml#L50]] +note as n15 + **[[https://example.test/src/case.sysml#L51 objective]]** + [[https://example.test/src/case.sysml#L51 Estimate the fuel remaining after a journey.]] +end note +usecase "//«verification def»//\n**CaseExamples::FuelVerification**" as n16 <> [[https://example.test/src/case.sysml#L56]] +rectangle "//«subject»//\n**vehicle : Vehicle**" as n17 <> <> [[https://example.test/src/case.sysml#L57]] +note as n18 + **[[https://example.test/src/case.sysml#L58 objective]]** +end note +n0 -- n1 : subject [[https://example.test/src/case.sysml#L27]] +n2 -- n0 : [[https://example.test/src/case.sysml#L28]] +n0 .. n3 : [[https://example.test/src/case.sysml#L29]] +n4 -- n5 : subject [[https://example.test/src/case.sysml#L17]] +n6 -- n4 : [[https://example.test/src/case.sysml#L18]] +n0 ..> n4 : include [[https://example.test/src/case.sysml#L33]] +n0 ..> n7 : include [[https://example.test/src/case.sysml#L38]] +n7 -- n8 : subject [[https://example.test/src/case.sysml#L39]] +n9 -- n7 : [[https://example.test/src/case.sysml#L40]] +n0 *-- n10 : [[https://example.test/src/case.sysml#L43]] +n10 -- n11 : subject [[https://example.test/src/case.sysml#L44]] +n12 -- n10 : [[https://example.test/src/case.sysml#L45]] +n13 -- n14 : subject [[https://example.test/src/case.sysml#L50]] +n13 .. n15 : [[https://example.test/src/case.sysml#L51]] +n16 -- n17 : subject [[https://example.test/src/case.sysml#L57]] +n16 .. n18 : [[https://example.test/src/case.sysml#L58]] +@enduml diff --git a/internal/ir/view/testdata/links-mixed-dot.golden b/internal/ir/view/testdata/links-mixed-dot.golden new file mode 100644 index 0000000000..323efe6490 --- /dev/null +++ b/internal/ir/view/testdata/links-mixed-dot.golden @@ -0,0 +1,117 @@ +// view: MixedExamples::mixedDiagram +// kind: mixed +// stated: render asMixedDiagram +// layout: dot +digraph "MixedExamples::mixedDiagram" { + graph [fontname="Helvetica", compound=true]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<«package»
MixedExamples>; + color=black; + penwidth=1.5; + URL="https://example.test/src/mixed.sysml#L1"; + tooltip="MixedExamples"; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n19" [label=<«port def»
FluidPort>, URL="https://example.test/src/mixed.sysml#L5", tooltip="MixedExamples::FluidPort"]; + "n20" [label=<«part def»
BaseUnit>, URL="https://example.test/src/mixed.sysml#L6", tooltip="MixedExamples::BaseUnit"]; + subgraph "cluster_n21" { + label=<«part def»
Pump>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L8"; + tooltip="MixedExamples::Pump"; + "n21" [shape=point, style=invis, width=0, height=0, label=""]; + "n22" [style="rounded,filled", label=<«port»
outlet : FluidPort>, URL="https://example.test/src/mixed.sysml#L9", tooltip="MixedExamples::Pump::outlet"]; + } + subgraph "cluster_n23" { + label=<«part def»
Tank>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L12"; + tooltip="MixedExamples::Tank"; + "n23" [shape=point, style=invis, width=0, height=0, label=""]; + "n24" [style="rounded,filled", label=<«port»
inlet : FluidPort>, URL="https://example.test/src/mixed.sysml#L13", tooltip="MixedExamples::Tank::inlet"]; + } + subgraph "cluster_n25" { + label=<«part def»
Plant>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L16"; + tooltip="MixedExamples::Plant"; + "n25" [shape=point, style=invis, width=0, height=0, label=""]; + "n26" [shape=plain, label=<
«part»
pump : Pump
outlet
>, URL="https://example.test/src/mixed.sysml#L17", tooltip="MixedExamples::Plant::pump"]; + "n27" [shape=plain, label=<
inlet
«part»
tank : Tank
>, URL="https://example.test/src/mixed.sysml#L18", tooltip="MixedExamples::Plant::tank"]; + } + subgraph "cluster_n28" { + label=<«connection def»
Connection>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L22"; + tooltip="MixedExamples::Connection"; + "n28" [shape=point, style=invis, width=0, height=0, label=""]; + "n29" [style="rounded,filled", label=<«part»
a>, URL="https://example.test/src/mixed.sysml#L23", tooltip="MixedExamples::Connection::a"]; + "n30" [style="rounded,filled", label=<«part»
b>, URL="https://example.test/src/mixed.sysml#L24", tooltip="MixedExamples::Connection::b"]; + } + "n1" [label=<«state def»
Ready
declares no states>, URL="https://example.test/src/mixed.sysml#L27", tooltip="MixedExamples::Ready"]; + subgraph "cluster_n2" { + label=<«state def»
Modes>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L29"; + tooltip="MixedExamples::Modes"; + "n2" [shape=point, style=invis, width=0, height=0, label=""]; + "n4" [shape=point, fillcolor=black, label=""]; + "n3" [style="rounded,filled", label=<«state»
ready : Ready
initial>, URL="https://example.test/src/mixed.sysml#L31", tooltip="MixedExamples::Modes::ready"]; + } + subgraph "cluster_n5" { + label=<«action def»
Operate>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L34"; + tooltip="MixedExamples::Operate"; + "n5" [shape=point, style=invis, width=0, height=0, label=""]; + "n6" [shape=circle, fillcolor=black, label="", width=0.2, URL="https://example.test/src/mixed.sysml#L35", tooltip="MixedExamples::Operate::start"]; + "n7" [style="rounded,filled", label=<«action»
'step'>, URL="https://example.test/src/mixed.sysml#L36", tooltip="MixedExamples::Operate::'step'"]; + "n8" [shape=doublecircle, fillcolor=black, label="", width=0.2, URL="https://example.test/src/mixed.sysml#L37", tooltip="mixed.sysml:37:3"]; + } + "n9" [shape=ellipse, label=<«use case def»
Inspection>, URL="https://example.test/src/mixed.sysml#L42", tooltip="MixedExamples::Inspection"]; + "n10" [shape=box, label=<«actor»
operator : Person>, URL="https://example.test/src/mixed.sysml#L43", tooltip="MixedExamples::Inspection::operator"]; + "n11" [shape=box, label=<«subject»
plant : Plant>, URL="https://example.test/src/mixed.sysml#L44", tooltip="MixedExamples::Inspection::plant"]; + "n12" [shape=note, label=<objective
Inspect the plant and report its operating state.>, URL="https://example.test/src/mixed.sysml#L45", tooltip="mixed.sysml:45:3"]; + "n13" [style="rounded,filled", label=<«perform action»
run : Operate
declares no nodes>, URL="https://example.test/src/mixed.sysml#L48", tooltip="MixedExamples::Inspection::run"]; + subgraph "cluster_n14" { + label=<«exhibit state»
mode : Modes>; + color=black; + penwidth=0.5; + URL="https://example.test/src/mixed.sysml#L49"; + tooltip="MixedExamples::Inspection::mode"; + "n14" [shape=point, style=invis, width=0, height=0, label=""]; + "n16" [shape=point, fillcolor=black, label=""]; + "n15" [style="rounded,filled", label=<«state»
ready : Ready
initial>, URL="https://example.test/src/mixed.sysml#L31", tooltip="MixedExamples::Modes::ready"]; + } + "n31" [label=<«part def»
Person>, URL="https://example.test/src/mixed.sysml#L52", tooltip="MixedExamples::Person"]; + "n17" [style="rounded,filled", label=<«view»
mixedDiagram>, URL="https://example.test/src/mixed.sysml#L54", tooltip="MixedExamples::mixedDiagram"]; + "n18" [style="rounded,filled", label=<«view»
mixedLayout>, URL="https://example.test/src/mixed.sysml#L59", tooltip="MixedExamples::mixedLayout"]; + } + "n4" -> "n3" [URL="https://example.test/src/mixed.sysml#L30", tooltip="mixed.sysml:30:10"]; + "n6" -> "n7" [URL="https://example.test/src/mixed.sysml#L38", tooltip="mixed.sysml:38:3"]; + "n7" -> "n8" [URL="https://example.test/src/mixed.sysml#L39", tooltip="mixed.sysml:39:3"]; + "n10" -> "n9" [dir=none, URL="https://example.test/src/mixed.sysml#L43", tooltip="MixedExamples::Inspection::operator"]; + "n9" -> "n11" [label="«subject»", dir=none, URL="https://example.test/src/mixed.sysml#L44", tooltip="MixedExamples::Inspection::plant"]; + "n9" -> "n12" [dir=none, style=dashed, URL="https://example.test/src/mixed.sysml#L45", tooltip="mixed.sysml:45:3"]; + "n16" -> "n15" [URL="https://example.test/src/mixed.sysml#L30", tooltip="mixed.sysml:30:10"]; + "n26":"n26.0" -> "n27":"n27.0" [label="supply", arrowhead=none, penwidth=3, URL="https://example.test/src/mixed.sysml#L19", tooltip="MixedExamples::Plant::supply"]; + "n3" -> "n1" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L31", tooltip="MixedExamples::Modes::ready"]; + "n10" -> "n31" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L43", tooltip="MixedExamples::Inspection::operator"]; + "n11" -> "n25" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L44", tooltip="MixedExamples::Inspection::plant", lhead="cluster_n25"]; + "n13" -> "n5" [label="«perform»", style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L48", tooltip="MixedExamples::Inspection::run", lhead="cluster_n5"]; + "n14" -> "n2" [label="«exhibit»", style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L49", tooltip="MixedExamples::Inspection::mode", ltail="cluster_n14", lhead="cluster_n2"]; + "n21" -> "n20" [label="«specializes»", arrowhead=empty, URL="https://example.test/src/mixed.sysml#L8", tooltip="MixedExamples::Pump", ltail="cluster_n21"]; + "n22" -> "n19" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L9", tooltip="MixedExamples::Pump::outlet"]; + "n23" -> "n20" [label="«specializes»", arrowhead=empty, URL="https://example.test/src/mixed.sysml#L12", tooltip="MixedExamples::Tank", ltail="cluster_n23"]; + "n24" -> "n19" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L13", tooltip="MixedExamples::Tank::inlet"]; + "n25" -> "n20" [label="«specializes»", arrowhead=empty, URL="https://example.test/src/mixed.sysml#L16", tooltip="MixedExamples::Plant", ltail="cluster_n25"]; + "n26" -> "n21" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L17", tooltip="MixedExamples::Plant::pump", lhead="cluster_n21"]; + "n27" -> "n23" [style=dashed, arrowhead=open, URL="https://example.test/src/mixed.sysml#L18", tooltip="MixedExamples::Plant::tank", lhead="cluster_n23"]; +} diff --git a/internal/ir/view/testdata/links-mixed-mermaid.golden b/internal/ir/view/testdata/links-mixed-mermaid.golden new file mode 100644 index 0000000000..6f3cd1078c --- /dev/null +++ b/internal/ir/view/testdata/links-mixed-mermaid.golden @@ -0,0 +1,164 @@ +--- +config: + fontFamily: "Helvetica, Arial, sans-serif" + theme: base + themeCSS: ".edgeLabel rect { opacity: 1 !important; } .cluster-label .nodeLabel { text-align: center; }" + themeVariables: + fontFamily: "Helvetica, Arial, sans-serif" + fontSize: "14px" + primaryColor: "#FFFFFF" + secondaryColor: "#FFFFFF" + tertiaryColor: "#FFFFFF" + background: "#FFFFFF" + primaryBorderColor: "#181818" + primaryTextColor: "#000000" + lineColor: "#181818" + textColor: "#000000" + noteBkgColor: "#FEFFDD" + noteBorderColor: "#181818" + noteTextColor: "#000000" + clusterBkg: "#FFFFFF" + clusterBorder: "#181818" + edgeLabelBackground: "#FFFFFF" + flowchart: + subGraphTitleMargin: + bottom: 24 +--- +%% MixedExamples::mixedDiagram — mixed rendering (render asMixedDiagram) +flowchart TD + subgraph n0 ["`*«package»* +**MixedExamples**`"] + direction TD + n19["`*«port def»* +**FluidPort**`"] + n20["`*«part def»* +**BaseUnit**`"] + subgraph n21 ["`*«part def»* +**Pump**`"] + direction TD + n21_anchor[" "] + n22("`*«port»* +**outlet : FluidPort**`") + end + subgraph n23 ["`*«part def»* +**Tank**`"] + direction TD + n23_anchor[" "] + n24("`*«port»* +**inlet : FluidPort**`") + end + subgraph n25 ["`*«part def»* +**Plant**`"] + direction TD + n25_anchor[" "] + subgraph n26 ["`*«part»* +**pump : Pump**`"] + direction TD + n26_anchor[" "] + n26.0["outlet"] + end + subgraph n27 ["`*«part»* +**tank : Tank**`"] + direction TD + n27_anchor[" "] + n27.0["inlet"] + end + end + subgraph n28 ["`*«connection def»* +**Connection**`"] + direction TD + n29("`*«part»* +**a**`") + n30("`*«part»* +**b**`") + end + n1["`*«state def»* +**Ready** +declares no states`"] + subgraph n2 ["`*«state def»* +**Modes**`"] + direction TD + n2_anchor[" "] + n4@{ shape: f-circ, label: "" } + n3("`*«state»* +**ready : Ready** +initial`") + end + subgraph n5 ["`*«action def»* +**Operate**`"] + direction TD + n5_anchor[" "] + n6@{ shape: f-circ, label: "" } + n7("`*«action»* +**'step'**`") + n8@{ shape: fr-circ, label: "" } + end + n9(["`*«use case def»* +**Inspection**`"]) + n10["`*«actor»* +**operator : Person**`"] + n11["`*«subject»* +**plant : Plant**`"] + n12@{ shape: notch-rect, label: "objective
Inspect the plant and report its operating state." } + n13("`*«perform action»* +**run : Operate** +declares no nodes`") + subgraph n14 ["`*«exhibit state»* +**mode : Modes**`"] + direction TD + n14_anchor[" "] + n16@{ shape: f-circ, label: "" } + n15("`*«state»* +**ready : Ready** +initial`") + end + n31["`*«part def»* +**Person**`"] + n17("`*«view»* +**mixedDiagram**`") + n18("`*«view»* +**mixedLayout**`") + end + n4 --> n3 + n6 --> n7 + n7 --> n8 + n10 --- n9 + n9 ---|"«subject»"| n11 + n9 -.- n12 + n16 --> n15 + n26.0 ===|"supply"| n27.0 + n3 -.-> n1 + n10 -.-> n31 + n11 -.-> n25_anchor + n13 -.->|"«perform»"| n5_anchor + n14_anchor -.->|"«exhibit»"| n2_anchor + n21_anchor -->|"«specializes»"| n20 + n22 -.-> n19 + n23_anchor -->|"«specializes»"| n20 + n24 -.-> n19 + n25_anchor -->|"«specializes»"| n20 + n26_anchor -.-> n21_anchor + n27_anchor -.-> n23_anchor + classDef anchor fill:none,stroke:none + class n14_anchor,n21_anchor,n23_anchor,n25_anchor,n26_anchor,n27_anchor,n2_anchor,n5_anchor anchor + linkStyle 7 stroke-width:3px + click n19 href "https://example.test/src/mixed.sysml#L5" + click n20 href "https://example.test/src/mixed.sysml#L6" + click n22 href "https://example.test/src/mixed.sysml#L9" + click n24 href "https://example.test/src/mixed.sysml#L13" + click n29 href "https://example.test/src/mixed.sysml#L23" + click n30 href "https://example.test/src/mixed.sysml#L24" + click n1 href "https://example.test/src/mixed.sysml#L27" + click n3 href "https://example.test/src/mixed.sysml#L31" + click n6 href "https://example.test/src/mixed.sysml#L35" + click n7 href "https://example.test/src/mixed.sysml#L36" + click n8 href "https://example.test/src/mixed.sysml#L37" + click n9 href "https://example.test/src/mixed.sysml#L42" + click n10 href "https://example.test/src/mixed.sysml#L43" + click n11 href "https://example.test/src/mixed.sysml#L44" + click n12 href "https://example.test/src/mixed.sysml#L45" + click n13 href "https://example.test/src/mixed.sysml#L48" + click n15 href "https://example.test/src/mixed.sysml#L31" + click n31 href "https://example.test/src/mixed.sysml#L52" + click n17 href "https://example.test/src/mixed.sysml#L54" + click n18 href "https://example.test/src/mixed.sysml#L59" diff --git a/internal/ir/view/testdata/links-mixed-plantuml.golden b/internal/ir/view/testdata/links-mixed-plantuml.golden new file mode 100644 index 0000000000..e3edf1bffc --- /dev/null +++ b/internal/ir/view/testdata/links-mixed-plantuml.golden @@ -0,0 +1,110 @@ +@startuml +' MixedExamples::mixedDiagram — mixed rendering (render asMixedDiagram) + +skinparam wrapWidth 300 +hide stereotype +package "//«package»//\n**MixedExamples**" as n0 <> [[https://example.test/src/mixed.sysml#L1]] { + rectangle "//«port def»//\n**FluidPort**" as n19 <> [[https://example.test/src/mixed.sysml#L5]] + rectangle "//«part def»//\n**BaseUnit**" as n20 <> [[https://example.test/src/mixed.sysml#L6]] + rectangle "//«part def»//\n**Pump**" as n21 <> [[https://example.test/src/mixed.sysml#L8]] { + rectangle "//«port»//\n**outlet : FluidPort**" as n22 <> <> [[https://example.test/src/mixed.sysml#L9]] + } + rectangle "//«part def»//\n**Tank**" as n23 <> [[https://example.test/src/mixed.sysml#L12]] { + rectangle "//«port»//\n**inlet : FluidPort**" as n24 <> <> [[https://example.test/src/mixed.sysml#L13]] + } + rectangle "//«part def»//\n**Plant**" as n25 <> [[https://example.test/src/mixed.sysml#L16]] { + rectangle "//«part»//\n**pump : Pump**" as n26 <> <> [[https://example.test/src/mixed.sysml#L17]] { + port "outlet" as n26.0 + } + rectangle "//«part»//\n**tank : Tank**" as n27 <> <> [[https://example.test/src/mixed.sysml#L18]] { + port "inlet" as n27.0 + } + } + rectangle "//«connection def»//\n**Connection**" as n28 <> [[https://example.test/src/mixed.sysml#L22]] { + rectangle "//«part»//\n**a**" as n29 <> <> [[https://example.test/src/mixed.sysml#L23]] + rectangle "//«part»//\n**b**" as n30 <> <> [[https://example.test/src/mixed.sysml#L24]] + } + rectangle "//«state def»//\n**Ready**\ndeclares no states" as n1 <> [[https://example.test/src/mixed.sysml#L27]] + rectangle "//«state def»//\n**Modes**" as n2 <> [[https://example.test/src/mixed.sysml#L29]] { + circle "**start**" as n4 <> <> + rectangle "//«state»//\n**ready : Ready**\ninitial" as n3 <> <> [[https://example.test/src/mixed.sysml#L31]] + } + rectangle "//«action def»//\n**Operate**" as n5 <> [[https://example.test/src/mixed.sysml#L34]] { + circle "**initial**" as n6 <> [[https://example.test/src/mixed.sysml#L35]] + rectangle "//«action»//\n**'step'**" as n7 <> <> [[https://example.test/src/mixed.sysml#L36]] + circle "**final**" as n8 <> [[https://example.test/src/mixed.sysml#L37]] + } + usecase "//«use case def»//\n**Inspection**" as n9 <> [[https://example.test/src/mixed.sysml#L42]] + actor "//«actor»//\n**operator : Person**" as n10 <> <> [[https://example.test/src/mixed.sysml#L43]] + rectangle "//«subject»//\n**plant : Plant**" as n11 <> <> [[https://example.test/src/mixed.sysml#L44]] + note as n12 + **[[https://example.test/src/mixed.sysml#L45 objective]]** + [[https://example.test/src/mixed.sysml#L45 Inspect the plant and report its operating state.]] + end note + rectangle "//«perform action»//\n**run : Operate**\ndeclares no nodes" as n13 <> <> [[https://example.test/src/mixed.sysml#L48]] + rectangle "//«exhibit state»//\n**mode : Modes**" as n14 <> <> [[https://example.test/src/mixed.sysml#L49]] { + circle "**start**" as n16 <> <> + rectangle "//«state»//\n**ready : Ready**\ninitial" as n15 <> <> [[https://example.test/src/mixed.sysml#L31]] + } + rectangle "//«part def»//\n**Person**" as n31 <> [[https://example.test/src/mixed.sysml#L52]] + rectangle "//«view»//\n**mixedDiagram**" as n17 <> <> [[https://example.test/src/mixed.sysml#L54]] + rectangle "//«view»//\n**mixedLayout**" as n18 <> <> [[https://example.test/src/mixed.sysml#L59]] +} +n4 --> n3 : [[https://example.test/src/mixed.sysml#L30]] +n6 --> n7 : [[https://example.test/src/mixed.sysml#L38]] +n7 --> n8 : [[https://example.test/src/mixed.sysml#L39]] +n10 -- n9 : [[https://example.test/src/mixed.sysml#L43]] +n9 -- n11 : subject [[https://example.test/src/mixed.sysml#L44]] +n9 .. n12 : [[https://example.test/src/mixed.sysml#L45]] +n16 --> n15 : [[https://example.test/src/mixed.sysml#L30]] +n26.0 -[thickness=3]- n27.0 : supply [[https://example.test/src/mixed.sysml#L19]] +n3 ..> n1 : [[https://example.test/src/mixed.sysml#L31]] +n10 ..> n31 : [[https://example.test/src/mixed.sysml#L43]] +n11 ..> n25 : [[https://example.test/src/mixed.sysml#L44]] +n13 ..> n5 : perform [[https://example.test/src/mixed.sysml#L48]] +n14 ..> n2 : exhibit [[https://example.test/src/mixed.sysml#L49]] +n21 --|> n20 : specializes [[https://example.test/src/mixed.sysml#L8]] +n22 ..> n19 : [[https://example.test/src/mixed.sysml#L9]] +n23 --|> n20 : specializes [[https://example.test/src/mixed.sysml#L12]] +n24 ..> n19 : [[https://example.test/src/mixed.sysml#L13]] +n25 --|> n20 : specializes [[https://example.test/src/mixed.sysml#L16]] +n26 ..> n21 : [[https://example.test/src/mixed.sysml#L17]] +n27 ..> n23 : [[https://example.test/src/mixed.sysml#L18]] +@enduml diff --git a/internal/ir/view/testdata/mixed-action-ports.sysml b/internal/ir/view/testdata/mixed-action-ports.sysml new file mode 100644 index 0000000000..e80fc7d718 --- /dev/null +++ b/internal/ir/view/testdata/mixed-action-ports.sysml @@ -0,0 +1,40 @@ +package MixedActionPorts { + private import OpenSysMLRenderings::*; + + item def Request; + port def Signal; + + part def Source { + port output : Signal; + } + + part def Sink { + port input : Signal; + } + + part def Monitor { + port unused : Signal; + } + + part def Plant { + part source : Source; + part sink : Sink; + part monitor : Monitor; + connection supply : Connection connect source.output to sink.input; + } + + connection def Connection { + end part a; + end part b; + } + + action def Check { + in request : Request; + } + + view mixedView { + expose Plant; + expose Check; + render asMixedDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed-behavior-connector.sysml b/internal/ir/view/testdata/mixed-behavior-connector.sysml new file mode 100644 index 0000000000..46af030826 --- /dev/null +++ b/internal/ir/view/testdata/mixed-behavior-connector.sysml @@ -0,0 +1,16 @@ +package BehaviorConnector { + private import OpenSysMLRenderings::*; + + part def Vehicle { + action inspect { + part a; + part b; + connect a to b; + } + } + + view connectorView { + expose Vehicle; + render asMixedDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed-deferred-connector.sysml b/internal/ir/view/testdata/mixed-deferred-connector.sysml new file mode 100644 index 0000000000..94719dd113 --- /dev/null +++ b/internal/ir/view/testdata/mixed-deferred-connector.sysml @@ -0,0 +1,18 @@ +package DeferredConnector { + private import OpenSysMLRenderings::*; + + requirement def R; + + part def Vehicle { + part a; + requirement r : R { + part b; + } + connect a to r.b; + } + + view connectorView { + expose Vehicle; + render asMixedDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed-inherited-actor.sysml b/internal/ir/view/testdata/mixed-inherited-actor.sysml new file mode 100644 index 0000000000..514ceaf3ca --- /dev/null +++ b/internal/ir/view/testdata/mixed-inherited-actor.sysml @@ -0,0 +1,28 @@ +package MixedInheritedActor { + private import OpenSysMLRenderings::*; + + part def Person; + part def Vehicle; + + use case def Base { + actor driver : Person; + subject vehicle : Vehicle; + } + + use case def A :> Base; + use case def B :> Base; + + view mixedView { + expose A; + expose B; + expose Person; + expose Vehicle; + render asMixedDiagram; + } + + view caseView { + expose A; + expose B; + render asCaseDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed-inherited-reference.sysml b/internal/ir/view/testdata/mixed-inherited-reference.sysml new file mode 100644 index 0000000000..5db001dcac --- /dev/null +++ b/internal/ir/view/testdata/mixed-inherited-reference.sysml @@ -0,0 +1,54 @@ +package MixedInheritedReference { + private import OpenSysMLRenderings::*; + + action def Drive; + part def Person; + state def Mode; + + use case def Base { + subject task : Drive; + actor driver : Person; + perform task; + } + + use case def A :> Base; + use case def B :> Base; + + use case def ModeCase { + subject mode : Mode; + } + + use case def C :> ModeCase; + use case def D :> ModeCase; + + part sysA { + perform A::task; + } + + part sysB { + perform B::task; + } + + part sysAll { + perform Base::task; + } + + part sysC { + exhibit C::mode; + } + + view mixedView { + expose A; + expose B; + expose C; + expose D; + expose sysA; + expose sysB; + expose sysAll; + expose sysC; + expose Drive; + expose Person; + expose Mode; + render asMixedDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed-review.sysml b/internal/ir/view/testdata/mixed-review.sysml new file mode 100644 index 0000000000..118af8c8c8 --- /dev/null +++ b/internal/ir/view/testdata/mixed-review.sysml @@ -0,0 +1,70 @@ +package MixedReview { + private import OpenSysMLRenderings::*; + + package P { + part def Vehicle { + action def Drive; + part def Wheel; + action def Brake; + } + + use case def A { + include B; + } + use case def B { + include C; + } + use case def C; + } + + package CaseOwners { + part def Person; + part def Vehicle { + use case def Drive { + actor driver : Person; + subject v : Vehicle; + use case nested; + } + } + } + + package NestedStructures { + part def Vehicle { + requirement def R { + part def Inner; + } + } + } + + requirement def Requirement { + action a { + first start; + then action step; + } + } + + view mixed { + expose P; + render asMixedDiagram; + } + + view behaviorView { + expose Requirement; + render asMixedDiagram; + } + + view caseOwnerView { + expose CaseOwners; + render asMixedDiagram; + } + + view transitiveIncludes { + expose P::A; + render asMixedDiagram; + } + + view nestedStructuresView { + expose NestedStructures; + render asMixedDiagram; + } +} diff --git a/internal/ir/view/testdata/mixed.cameo.dot.golden b/internal/ir/view/testdata/mixed.cameo.dot.golden new file mode 100644 index 0000000000..e8ab19e36c --- /dev/null +++ b/internal/ir/view/testdata/mixed.cameo.dot.golden @@ -0,0 +1,134 @@ +// view: MixedExamples::mixedDiagram +// kind: mixed +// stated: render asMixedDiagram +// layout: dot +digraph "MixedExamples::mixedDiagram" { + graph [fontname="Arial", fontcolor="#424242", compound=true]; + node [shape=box, style=filled, fillcolor="#FFCC99:#FFFAD4", gradientangle=0, color="#99795C", fontname="Arial", fontsize=11, fontcolor="#424242", penwidth=1]; + edge [color="#424242", fontname="Arial", fontsize=9, fontcolor="#424242", penwidth=1, arrowhead=open]; + subgraph "cluster_frame" { + label=<mixed [Package] MixedExamples [ mixedDiagram ]>; + labeljust=l; + labelloc=t; + fontsize=11; + color="#5B5B59"; + penwidth=1; + margin=8; + subgraph "cluster_n0" { + label=""; + style=filled; + fillcolor="#FFCC99:#FFFAD4"; + gradientangle=0; + color="#99795C"; + penwidth=1; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n19" [label=<«port def»
FluidPort>]; + "n20" [label=<«part def»
BaseUnit>]; + subgraph "cluster_n21" { + label=<«part def»
Pump>; + style=filled; + fillcolor="#FFCC99:#FFFAD4"; + gradientangle=0; + color="#99795C"; + penwidth=1; + "n21" [shape=point, style=invis, width=0, height=0, label=""]; + "n22" [label=<«port»
outlet : FluidPort>]; + } + subgraph "cluster_n23" { + label=<«part def»
Tank>; + style=filled; + fillcolor="#FFCC99:#FFFAD4"; + gradientangle=0; + color="#99795C"; + penwidth=1; + "n23" [shape=point, style=invis, width=0, height=0, label=""]; + "n24" [label=<«port»
inlet : FluidPort>]; + } + subgraph "cluster_n25" { + label=<«part def»
Plant>; + style=filled; + fillcolor="#FFCC99:#FFFAD4"; + gradientangle=0; + color="#99795C"; + penwidth=1; + "n25" [shape=point, style=invis, width=0, height=0, label=""]; + "n26" [shape=plain, label=<
«part»
pump : Pump
outlet
>]; + "n27" [shape=plain, label=<
inlet
«part»
tank : Tank
>]; + } + subgraph "cluster_n28" { + label=<«connection def»
Connection>; + style=filled; + fillcolor="#FFCC99:#FFFAD4"; + gradientangle=0; + color="#99795C"; + penwidth=1; + "n28" [shape=point, style=invis, width=0, height=0, label=""]; + "n29" [label=<«part»
a>]; + "n30" [label=<«part»
b>]; + } + "n1" [style="rounded,filled", fillcolor="#FFFFCC:#FFFFF2", color="#5B5B59", label=<
«state def»
Ready
declares no states
>]; + subgraph "cluster_n2" { + label=<«state def»
Modes>; + style="rounded,filled"; + fillcolor="#FFFFCC:#FFFFF2"; + gradientangle=0; + color="#5B5B59"; + penwidth=1; + "n2" [shape=point, style=invis, width=0, height=0, label=""]; + "n4" [shape=circle, fillcolor=black, width=0.2, height=0.2, fixedsize=true, label=""]; + "n3" [style="rounded,filled", fillcolor="#FFFFCC:#FFFFF2", color="#5B5B59", label=<ready : Ready>]; + } + subgraph "cluster_n5" { + label=<«action def»
Operate>; + style="rounded,filled"; + fillcolor="#E1E1C3:#F7F7EF"; + gradientangle=0; + color="#424242"; + penwidth=1; + "n5" [shape=point, style=invis, width=0, height=0, label=""]; + "n6" [shape=circle, fillcolor=black, width=0.2, height=0.2, fixedsize=true, label=""]; + "n7" [style="rounded,filled", fillcolor="#E1E1C3:#F7F7EF", color="#424242", label=<step>]; + "n8" [shape=doublecircle, fillcolor=black, width=0.2, height=0.2, fixedsize=true, label=""]; + } + "n9" [shape=ellipse, label=<«use case def»
Inspection>]; + "n10" [shape=box, label=<«actor»
operator : Person>]; + "n11" [shape=box, label=<«subject»
plant : Plant>]; + "n12" [shape=note, label=<
objective
Inspect the plant and report its operating state.
>]; + "n13" [style="rounded,filled", fillcolor="#E1E1C3:#F7F7EF", color="#424242", label=<
«perform action»
run : Operate
declares no nodes
>]; + subgraph "cluster_n14" { + label=<«exhibit state»
mode : Modes>; + style="rounded,filled"; + fillcolor="#FFFFCC:#FFFFF2"; + gradientangle=0; + color="#5B5B59"; + penwidth=1; + "n14" [shape=point, style=invis, width=0, height=0, label=""]; + "n16" [shape=circle, fillcolor=black, width=0.2, height=0.2, fixedsize=true, label=""]; + "n15" [style="rounded,filled", fillcolor="#FFFFCC:#FFFFF2", color="#5B5B59", label=<ready : Ready>]; + } + "n31" [label=<«part def»
Person>]; + "n17" [label=<«view»
mixedDiagram>]; + "n18" [label=<«view»
mixedLayout>]; + } + } + "n4" -> "n3"; + "n6" -> "n7"; + "n7" -> "n8"; + "n10" -> "n9" [dir=none]; + "n9" -> "n11" [label="«subject»", dir=none]; + "n9" -> "n12" [dir=none, style=dashed]; + "n16" -> "n15"; + "n26":"n26.0" -> "n27":"n27.0" [label="supply", arrowhead=none]; + "n3" -> "n1" [style=dashed, arrowhead=open]; + "n10" -> "n31" [style=dashed, arrowhead=open]; + "n11" -> "n25" [style=dashed, arrowhead=open, lhead="cluster_n25"]; + "n13" -> "n5" [label="«perform»", style=dashed, arrowhead=open, lhead="cluster_n5"]; + "n14" -> "n2" [label="«exhibit»", style=dashed, arrowhead=open, ltail="cluster_n14", lhead="cluster_n2"]; + "n21" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n21"]; + "n22" -> "n19" [style=dashed, arrowhead=open]; + "n23" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n23"]; + "n24" -> "n19" [style=dashed, arrowhead=open]; + "n25" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n25"]; + "n26" -> "n21" [style=dashed, arrowhead=open, lhead="cluster_n21"]; + "n27" -> "n23" [style=dashed, arrowhead=open, lhead="cluster_n23"]; +} diff --git a/internal/ir/view/testdata/mixed.dot.golden b/internal/ir/view/testdata/mixed.dot.golden new file mode 100644 index 0000000000..6cab75cc8c --- /dev/null +++ b/internal/ir/view/testdata/mixed.dot.golden @@ -0,0 +1,101 @@ +// view: MixedExamples::mixedDiagram +// kind: mixed +// stated: render asMixedDiagram +// layout: dot +digraph "MixedExamples::mixedDiagram" { + graph [fontname="Helvetica", compound=true]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<«package»
MixedExamples>; + color=black; + penwidth=1.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n19" [label=<«port def»
FluidPort>]; + "n20" [label=<«part def»
BaseUnit>]; + subgraph "cluster_n21" { + label=<«part def»
Pump>; + color=black; + penwidth=0.5; + "n21" [shape=point, style=invis, width=0, height=0, label=""]; + "n22" [style="rounded,filled", label=<«port»
outlet : FluidPort>]; + } + subgraph "cluster_n23" { + label=<«part def»
Tank>; + color=black; + penwidth=0.5; + "n23" [shape=point, style=invis, width=0, height=0, label=""]; + "n24" [style="rounded,filled", label=<«port»
inlet : FluidPort>]; + } + subgraph "cluster_n25" { + label=<«part def»
Plant>; + color=black; + penwidth=0.5; + "n25" [shape=point, style=invis, width=0, height=0, label=""]; + "n26" [shape=plain, label=<
«part»
pump : Pump
outlet
>]; + "n27" [shape=plain, label=<
inlet
«part»
tank : Tank
>]; + } + subgraph "cluster_n28" { + label=<«connection def»
Connection>; + color=black; + penwidth=0.5; + "n28" [shape=point, style=invis, width=0, height=0, label=""]; + "n29" [style="rounded,filled", label=<«part»
a>]; + "n30" [style="rounded,filled", label=<«part»
b>]; + } + "n1" [label=<«state def»
Ready
declares no states>]; + subgraph "cluster_n2" { + label=<«state def»
Modes>; + color=black; + penwidth=0.5; + "n2" [shape=point, style=invis, width=0, height=0, label=""]; + "n4" [shape=point, fillcolor=black, label=""]; + "n3" [style="rounded,filled", label=<«state»
ready : Ready
initial>]; + } + subgraph "cluster_n5" { + label=<«action def»
Operate>; + color=black; + penwidth=0.5; + "n5" [shape=point, style=invis, width=0, height=0, label=""]; + "n6" [shape=circle, fillcolor=black, label="", width=0.2]; + "n7" [style="rounded,filled", label=<«action»
'step'>]; + "n8" [shape=doublecircle, fillcolor=black, label="", width=0.2]; + } + "n9" [shape=ellipse, label=<«use case def»
Inspection>]; + "n10" [shape=box, label=<«actor»
operator : Person>]; + "n11" [shape=box, label=<«subject»
plant : Plant>]; + "n12" [shape=note, label=<objective
Inspect the plant and report its operating state.>]; + "n13" [style="rounded,filled", label=<«perform action»
run : Operate
declares no nodes>]; + subgraph "cluster_n14" { + label=<«exhibit state»
mode : Modes>; + color=black; + penwidth=0.5; + "n14" [shape=point, style=invis, width=0, height=0, label=""]; + "n16" [shape=point, fillcolor=black, label=""]; + "n15" [style="rounded,filled", label=<«state»
ready : Ready
initial>]; + } + "n31" [label=<«part def»
Person>]; + "n17" [style="rounded,filled", label=<«view»
mixedDiagram>]; + "n18" [style="rounded,filled", label=<«view»
mixedLayout>]; + } + "n4" -> "n3"; + "n6" -> "n7"; + "n7" -> "n8"; + "n10" -> "n9" [dir=none]; + "n9" -> "n11" [label="«subject»", dir=none]; + "n9" -> "n12" [dir=none, style=dashed]; + "n16" -> "n15"; + "n26":"n26.0" -> "n27":"n27.0" [label="supply", arrowhead=none, penwidth=3]; + "n3" -> "n1" [style=dashed, arrowhead=open]; + "n10" -> "n31" [style=dashed, arrowhead=open]; + "n11" -> "n25" [style=dashed, arrowhead=open, lhead="cluster_n25"]; + "n13" -> "n5" [label="«perform»", style=dashed, arrowhead=open, lhead="cluster_n5"]; + "n14" -> "n2" [label="«exhibit»", style=dashed, arrowhead=open, ltail="cluster_n14", lhead="cluster_n2"]; + "n21" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n21"]; + "n22" -> "n19" [style=dashed, arrowhead=open]; + "n23" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n23"]; + "n24" -> "n19" [style=dashed, arrowhead=open]; + "n25" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n25"]; + "n26" -> "n21" [style=dashed, arrowhead=open, lhead="cluster_n21"]; + "n27" -> "n23" [style=dashed, arrowhead=open, lhead="cluster_n23"]; +} diff --git a/internal/ir/view/testdata/mixed.mermaid.golden b/internal/ir/view/testdata/mixed.mermaid.golden new file mode 100644 index 0000000000..6d27e46b34 --- /dev/null +++ b/internal/ir/view/testdata/mixed.mermaid.golden @@ -0,0 +1,144 @@ +--- +config: + fontFamily: "Helvetica, Arial, sans-serif" + theme: base + themeCSS: ".edgeLabel rect { opacity: 1 !important; } .cluster-label .nodeLabel { text-align: center; }" + themeVariables: + fontFamily: "Helvetica, Arial, sans-serif" + fontSize: "14px" + primaryColor: "#FFFFFF" + secondaryColor: "#FFFFFF" + tertiaryColor: "#FFFFFF" + background: "#FFFFFF" + primaryBorderColor: "#181818" + primaryTextColor: "#000000" + lineColor: "#181818" + textColor: "#000000" + noteBkgColor: "#FEFFDD" + noteBorderColor: "#181818" + noteTextColor: "#000000" + clusterBkg: "#FFFFFF" + clusterBorder: "#181818" + edgeLabelBackground: "#FFFFFF" + flowchart: + subGraphTitleMargin: + bottom: 24 +--- +%% MixedExamples::mixedDiagram — mixed rendering (render asMixedDiagram) +flowchart TD + subgraph n0 ["`*«package»* +**MixedExamples**`"] + direction TD + n19["`*«port def»* +**FluidPort**`"] + n20["`*«part def»* +**BaseUnit**`"] + subgraph n21 ["`*«part def»* +**Pump**`"] + direction TD + n21_anchor[" "] + n22("`*«port»* +**outlet : FluidPort**`") + end + subgraph n23 ["`*«part def»* +**Tank**`"] + direction TD + n23_anchor[" "] + n24("`*«port»* +**inlet : FluidPort**`") + end + subgraph n25 ["`*«part def»* +**Plant**`"] + direction TD + n25_anchor[" "] + subgraph n26 ["`*«part»* +**pump : Pump**`"] + direction TD + n26_anchor[" "] + n26.0["outlet"] + end + subgraph n27 ["`*«part»* +**tank : Tank**`"] + direction TD + n27_anchor[" "] + n27.0["inlet"] + end + end + subgraph n28 ["`*«connection def»* +**Connection**`"] + direction TD + n29("`*«part»* +**a**`") + n30("`*«part»* +**b**`") + end + n1["`*«state def»* +**Ready** +declares no states`"] + subgraph n2 ["`*«state def»* +**Modes**`"] + direction TD + n2_anchor[" "] + n4@{ shape: f-circ, label: "" } + n3("`*«state»* +**ready : Ready** +initial`") + end + subgraph n5 ["`*«action def»* +**Operate**`"] + direction TD + n5_anchor[" "] + n6@{ shape: f-circ, label: "" } + n7("`*«action»* +**'step'**`") + n8@{ shape: fr-circ, label: "" } + end + n9(["`*«use case def»* +**Inspection**`"]) + n10["`*«actor»* +**operator : Person**`"] + n11["`*«subject»* +**plant : Plant**`"] + n12@{ shape: notch-rect, label: "objective
Inspect the plant and report its operating state." } + n13("`*«perform action»* +**run : Operate** +declares no nodes`") + subgraph n14 ["`*«exhibit state»* +**mode : Modes**`"] + direction TD + n14_anchor[" "] + n16@{ shape: f-circ, label: "" } + n15("`*«state»* +**ready : Ready** +initial`") + end + n31["`*«part def»* +**Person**`"] + n17("`*«view»* +**mixedDiagram**`") + n18("`*«view»* +**mixedLayout**`") + end + n4 --> n3 + n6 --> n7 + n7 --> n8 + n10 --- n9 + n9 ---|"«subject»"| n11 + n9 -.- n12 + n16 --> n15 + n26.0 ===|"supply"| n27.0 + n3 -.-> n1 + n10 -.-> n31 + n11 -.-> n25_anchor + n13 -.->|"«perform»"| n5_anchor + n14_anchor -.->|"«exhibit»"| n2_anchor + n21_anchor -->|"«specializes»"| n20 + n22 -.-> n19 + n23_anchor -->|"«specializes»"| n20 + n24 -.-> n19 + n25_anchor -->|"«specializes»"| n20 + n26_anchor -.-> n21_anchor + n27_anchor -.-> n23_anchor + classDef anchor fill:none,stroke:none + class n14_anchor,n21_anchor,n23_anchor,n25_anchor,n26_anchor,n27_anchor,n2_anchor,n5_anchor anchor + linkStyle 7 stroke-width:3px diff --git a/internal/ir/view/testdata/mixed.okabe-ito.dot.golden b/internal/ir/view/testdata/mixed.okabe-ito.dot.golden new file mode 100644 index 0000000000..43656c8577 --- /dev/null +++ b/internal/ir/view/testdata/mixed.okabe-ito.dot.golden @@ -0,0 +1,101 @@ +// view: MixedExamples::mixedDiagram +// kind: mixed +// stated: render asMixedDiagram +// layout: dot +digraph "MixedExamples::mixedDiagram" { + graph [fontname="Helvetica", compound=true]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<«package»
MixedExamples>; + color=black; + penwidth=1.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n19" [fillcolor="#009E73", color="#009E73", penwidth=1, label=<«port def»
FluidPort>]; + "n20" [fillcolor="#E69F00", color="#E69F00", penwidth=1, label=<«part def»
BaseUnit>]; + subgraph "cluster_n21" { + label=<«part def»
Pump>; + color=black; + penwidth=0.5; + "n21" [shape=point, style=invis, width=0, height=0, label=""]; + "n22" [style="rounded,filled", fillcolor="#99D8C7", color="#009E73", penwidth=1, label=<«port»
outlet : FluidPort>]; + } + subgraph "cluster_n23" { + label=<«part def»
Tank>; + color=black; + penwidth=0.5; + "n23" [shape=point, style=invis, width=0, height=0, label=""]; + "n24" [style="rounded,filled", fillcolor="#99D8C7", color="#009E73", penwidth=1, label=<«port»
inlet : FluidPort>]; + } + subgraph "cluster_n25" { + label=<«part def»
Plant>; + color=black; + penwidth=0.5; + "n25" [shape=point, style=invis, width=0, height=0, label=""]; + "n26" [shape=plain, fillcolor="#F5D999", color="#E69F00", penwidth=1, label=<
«part»
pump : Pump
outlet
>]; + "n27" [shape=plain, fillcolor="#F5D999", color="#E69F00", penwidth=1, label=<
inlet
«part»
tank : Tank
>]; + } + subgraph "cluster_n28" { + label=<«connection def»
Connection>; + color=black; + penwidth=0.5; + "n28" [shape=point, style=invis, width=0, height=0, label=""]; + "n29" [style="rounded,filled", fillcolor="#F5D999", color="#E69F00", penwidth=1, label=<«part»
a>]; + "n30" [style="rounded,filled", fillcolor="#F5D999", color="#E69F00", penwidth=1, label=<«part»
b>]; + } + "n1" [fillcolor="#D55E00", color="#D55E00", penwidth=1, label=<«state def»
Ready
declares no states>]; + subgraph "cluster_n2" { + label=<«state def»
Modes>; + color=black; + penwidth=0.5; + "n2" [shape=point, style=invis, width=0, height=0, label=""]; + "n4" [shape=point, fillcolor=black, label=""]; + "n3" [style="rounded,filled", fillcolor="#EEBF99", color="#D55E00", penwidth=1, label=<«state»
ready : Ready
initial>]; + } + subgraph "cluster_n5" { + label=<«action def»
Operate>; + color=black; + penwidth=0.5; + "n5" [shape=point, style=invis, width=0, height=0, label=""]; + "n6" [shape=circle, fillcolor=black, label="", width=0.2]; + "n7" [style="rounded,filled", fillcolor="#99C7E0", color="#0072B2", penwidth=1, label=<«action»
'step'>]; + "n8" [shape=doublecircle, fillcolor=black, label="", width=0.2]; + } + "n9" [shape=ellipse, fillcolor="#009E73", color="#009E73", penwidth=1, label=<«use case def»
Inspection>]; + "n10" [shape=box, fillcolor="#99D8C7", color="#009E73", penwidth=1, label=<«actor»
operator : Person>]; + "n11" [shape=box, fillcolor="#F9F4B3", color="#F0E442", penwidth=1, label=<«subject»
plant : Plant>]; + "n12" [shape=note, fillcolor="#EBC9DC", color="#CC79A7", penwidth=1, label=<objective
Inspect the plant and report its operating state.>]; + "n13" [style="rounded,filled", fillcolor="#99C7E0", color="#0072B2", penwidth=1, label=<«perform action»
run : Operate
declares no nodes>]; + subgraph "cluster_n14" { + label=<«exhibit state»
mode : Modes>; + color=black; + penwidth=0.5; + "n14" [shape=point, style=invis, width=0, height=0, label=""]; + "n16" [shape=point, fillcolor=black, label=""]; + "n15" [style="rounded,filled", fillcolor="#EEBF99", color="#D55E00", penwidth=1, label=<«state»
ready : Ready
initial>]; + } + "n31" [fillcolor="#E69F00", color="#E69F00", penwidth=1, label=<«part def»
Person>]; + "n17" [style="rounded,filled", fillcolor="#99D8C7", color="#009E73", penwidth=1, label=<«view»
mixedDiagram>]; + "n18" [style="rounded,filled", fillcolor="#99D8C7", color="#009E73", penwidth=1, label=<«view»
mixedLayout>]; + } + "n4" -> "n3"; + "n6" -> "n7"; + "n7" -> "n8"; + "n10" -> "n9" [dir=none]; + "n9" -> "n11" [label="«subject»", dir=none]; + "n9" -> "n12" [dir=none, style=dashed]; + "n16" -> "n15"; + "n26":"n26.0" -> "n27":"n27.0" [label="supply", arrowhead=none, penwidth=3]; + "n3" -> "n1" [style=dashed, arrowhead=open]; + "n10" -> "n31" [style=dashed, arrowhead=open]; + "n11" -> "n25" [style=dashed, arrowhead=open, lhead="cluster_n25"]; + "n13" -> "n5" [label="«perform»", style=dashed, arrowhead=open, lhead="cluster_n5"]; + "n14" -> "n2" [label="«exhibit»", style=dashed, arrowhead=open, ltail="cluster_n14", lhead="cluster_n2"]; + "n21" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n21"]; + "n22" -> "n19" [style=dashed, arrowhead=open]; + "n23" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n23"]; + "n24" -> "n19" [style=dashed, arrowhead=open]; + "n25" -> "n20" [label="«specializes»", arrowhead=empty, ltail="cluster_n25"]; + "n26" -> "n21" [style=dashed, arrowhead=open, lhead="cluster_n21"]; + "n27" -> "n23" [style=dashed, arrowhead=open, lhead="cluster_n23"]; +} diff --git a/internal/ir/view/testdata/mixed.plantuml.golden b/internal/ir/view/testdata/mixed.plantuml.golden new file mode 100644 index 0000000000..aeb577fb72 --- /dev/null +++ b/internal/ir/view/testdata/mixed.plantuml.golden @@ -0,0 +1,107 @@ +@startuml +' MixedExamples::mixedDiagram — mixed rendering (render asMixedDiagram) + +skinparam wrapWidth 300 +hide stereotype +package "//«package»//\n**MixedExamples**" as n0 <> { + rectangle "//«port def»//\n**FluidPort**" as n19 <> + rectangle "//«part def»//\n**BaseUnit**" as n20 <> + rectangle "//«part def»//\n**Pump**" as n21 <> { + rectangle "//«port»//\n**outlet : FluidPort**" as n22 <> <> + } + rectangle "//«part def»//\n**Tank**" as n23 <> { + rectangle "//«port»//\n**inlet : FluidPort**" as n24 <> <> + } + rectangle "//«part def»//\n**Plant**" as n25 <> { + rectangle "//«part»//\n**pump : Pump**" as n26 <> <> { + port "outlet" as n26.0 + } + rectangle "//«part»//\n**tank : Tank**" as n27 <> <> { + port "inlet" as n27.0 + } + } + rectangle "//«connection def»//\n**Connection**" as n28 <> { + rectangle "//«part»//\n**a**" as n29 <> <> + rectangle "//«part»//\n**b**" as n30 <> <> + } + rectangle "//«state def»//\n**Ready**\ndeclares no states" as n1 <> + rectangle "//«state def»//\n**Modes**" as n2 <> { + circle "**start**" as n4 <> <> + rectangle "//«state»//\n**ready : Ready**\ninitial" as n3 <> <> + } + rectangle "//«action def»//\n**Operate**" as n5 <> { + circle "**initial**" as n6 <> + rectangle "//«action»//\n**'step'**" as n7 <> <> + circle "**final**" as n8 <> + } + usecase "//«use case def»//\n**Inspection**" as n9 <> + actor "//«actor»//\n**operator : Person**" as n10 <> <> + rectangle "//«subject»//\n**plant : Plant**" as n11 <> <> + note "**objective**\nInspect the plant and report its operating state." as n12 + rectangle "//«perform action»//\n**run : Operate**\ndeclares no nodes" as n13 <> <> + rectangle "//«exhibit state»//\n**mode : Modes**" as n14 <> <> { + circle "**start**" as n16 <> <> + rectangle "//«state»//\n**ready : Ready**\ninitial" as n15 <> <> + } + rectangle "//«part def»//\n**Person**" as n31 <> + rectangle "//«view»//\n**mixedDiagram**" as n17 <> <> + rectangle "//«view»//\n**mixedLayout**" as n18 <> <> +} +n4 --> n3 +n6 --> n7 +n7 --> n8 +n10 -- n9 +n9 -- n11 : subject +n9 .. n12 +n16 --> n15 +n26.0 -[thickness=3]- n27.0 : supply +n3 ..> n1 +n10 ..> n31 +n11 ..> n25 +n13 ..> n5 : perform +n14 ..> n2 : exhibit +n21 --|> n20 : specializes +n22 ..> n19 +n23 --|> n20 : specializes +n24 ..> n19 +n25 --|> n20 : specializes +n26 ..> n21 +n27 ..> n23 +@enduml diff --git a/internal/ir/view/testdata/mixed.sysml b/internal/ir/view/testdata/mixed.sysml new file mode 100644 index 0000000000..696b5402a1 --- /dev/null +++ b/internal/ir/view/testdata/mixed.sysml @@ -0,0 +1,66 @@ +package MixedExamples { + private import OpenSysMLRenderings::*; + private import DiagramLayout::*; + + port def FluidPort; + part def BaseUnit; + + part def Pump :> BaseUnit { + port outlet : FluidPort; + } + + part def Tank :> BaseUnit { + port inlet : FluidPort; + } + + part def Plant :> BaseUnit { + part pump : Pump; + part tank : Tank; + connection supply : Connection connect pump.outlet to tank.inlet; + } + + connection def Connection { + end part a; + end part b; + } + + state def Ready; + + state def Modes { + entry; then ready; + state ready : Ready; + } + + action def Operate { + first start; + action step; + done; + succession first start then step; + succession first step then done; + } + + use case def Inspection { + actor operator : Person; + subject plant : Plant; + objective { + doc /* Inspect the plant and report its operating state. */ + } + perform action run : Operate; + exhibit state mode : Modes; + } + + part def Person; + + view mixedDiagram { + expose MixedExamples; + render asMixedDiagram; + } + + view mixedLayout { + expose MixedExamples; + render asMixedDiagram; + metadata Layout about MixedExamples::Inspection { x = 50; y = 30; } + metadata Layout about MixedExamples::Inspection::operator { x = 20; y = 80; } + metadata Route about MixedExamples::Plant::pump { points = (100, 40, 140, 80); } + } +} diff --git a/internal/ir/view/testdata/mixed.text.golden b/internal/ir/view/testdata/mixed.text.golden new file mode 100644 index 0000000000..7c700d71bd --- /dev/null +++ b/internal/ir/view/testdata/mixed.text.golden @@ -0,0 +1,58 @@ +MixedExamples::mixedDiagram - mixed rendering (render asMixedDiagram) + +package MixedExamples + port def MixedExamples::FluidPort + part def MixedExamples::BaseUnit + part def MixedExamples::Pump + port outlet : FluidPort + part def MixedExamples::Tank + port inlet : FluidPort + part def MixedExamples::Plant + part pump : Pump + port outlet + part tank : Tank + port inlet + connection def MixedExamples::Connection + part a + part b + state def MixedExamples::Ready (declares no states) + state def MixedExamples::Modes + start + state ready : Ready (initial) + action def MixedExamples::Operate + initial start + action 'step' + final done + use case def Inspection + actor operator : Person + subject plant : Plant + objective (Inspect the plant and report its operating state.) + perform action MixedExamples::Inspection::run : Operate (declares no nodes) + exhibit state MixedExamples::Inspection::mode : Modes + start + state ready : Ready (initial) + part def MixedExamples::Person + view mixedDiagram + view mixedLayout + +relationships: + start of MixedExamples::Modes -> ready + start -> 'step' + 'step' -> done + operator -- Inspection + Inspection -- plant: «subject» + Inspection .. objective n12 + start of MixedExamples::Inspection::mode -> ready + pump.outlet -- tank.inlet: supply + ready ..> MixedExamples::Ready + operator ..> MixedExamples::Person + plant ..> MixedExamples::Plant + MixedExamples::Inspection::run ..> MixedExamples::Operate: «perform» + MixedExamples::Inspection::mode ..> MixedExamples::Modes: «exhibit» + MixedExamples::Pump --|> MixedExamples::BaseUnit: «specializes» + outlet ..> MixedExamples::FluidPort + MixedExamples::Tank --|> MixedExamples::BaseUnit: «specializes» + inlet ..> MixedExamples::FluidPort + MixedExamples::Plant --|> MixedExamples::BaseUnit: «specializes» + pump ..> MixedExamples::Pump + tank ..> MixedExamples::Tank diff --git a/internal/ir/view/testdata/view-role-inheritance.sysml b/internal/ir/view/testdata/view-role-inheritance.sysml new file mode 100644 index 0000000000..2db880d421 --- /dev/null +++ b/internal/ir/view/testdata/view-role-inheritance.sysml @@ -0,0 +1,35 @@ +package RoleInheritance { + private import OpenSysMLRenderings::*; + + part def Person; + part def Vehicle; + + use case def Base { + actor driver : Person; + subject s : Vehicle; + objective { + doc /* Arrive safely. */ + } + } + + use case def Trip :> Base; + use case def Trip2 :> Base { + actor :>> driver; + } + use case def X; + + view trip { + expose Trip; + render asCaseDiagram; + } + + view trip2 { + expose Trip2; + render asCaseDiagram; + } + + view x { + expose X; + render asCaseDiagram; + } +} diff --git a/internal/ir/view/text.go b/internal/ir/view/text.go index ce868f7963..42c6e31933 100644 --- a/internal/ir/view/text.go +++ b/internal/ir/view/text.go @@ -21,7 +21,8 @@ func (r *Rendering) Text() string { return r.TextWidth(WidthUnbounded) } func (r *Rendering) TextWidth(width int) string { return r.textWith(Options{Width: width}) } // textWith is the text form written to options' width, listing under each part -// of an interconnection the ports options' Ports display draws. +// of an interconnection or mixed rendering the ports options' Ports display +// draws. func (r *Rendering) textWith(options Options) string { width := options.Width var b strings.Builder @@ -154,7 +155,8 @@ func endLabel(labels map[string]string, node, port string) string { // writeNodeText writes one node and its children, and records the label an edge // names the node by. A body's start is named by the body it starts. In an -// interconnection, the node's ports the display draws are written under it, +// interconnection or mixed view, the node's ports the display draws are written +// under it, // each a line of its own, and recorded as `node.port`; elsewhere they are left // to the edges' labels, which an action's flows name their pins in. func writeNodeText(b *strings.Builder, node *Node, depth int, labels map[string]string, ports portView) { @@ -193,10 +195,9 @@ func writeNodeText(b *strings.Builder, node *Node, depth int, labels map[string] } } -// pinLine is a port's line under its node: `port