From 742a1947d9362524f8536b82247784c56f1684f8 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 09:49:17 -0400 Subject: [PATCH 1/6] feat: implement Phase 2 semantic model (symbol table, reference resolver, supertype walker) --- .reviewmark.yaml | 46 +++ docs/design/introduction.md | 11 +- docs/design/sysml2-tools-core.md | 24 +- docs/design/sysml2-tools-core/semantic.md | 82 +++++ .../sysml2-tools-core/semantic/internal.md | 22 ++ .../semantic/internal/ast-builder.md | 41 +++ .../semantic/internal/reference-resolver.md | 26 ++ .../semantic/internal/supertype-walker.md | 26 ++ .../semantic/internal/symbol-table.md | 22 ++ .../semantic/internal/sysml-node.md | 28 ++ .../semantic/workspace-loader.md | 31 ++ .../reqstream/sysml2-tools-core/semantic.yaml | 79 +++++ .../sysml2-tools-core/semantic/internal.yaml | 43 +++ .../semantic/internal/ast-builder.yaml | 40 +++ .../semantic/internal/reference-resolver.yaml | 28 ++ .../semantic/internal/supertype-walker.yaml | 19 ++ .../semantic/internal/symbol-table.yaml | 29 ++ .../semantic/internal/sysml-node.yaml | 29 ++ .../semantic/workspace-loader.yaml | 29 ++ docs/reqstream/sysml2-tools-tool/lint.yaml | 8 +- docs/verification/sysml2-tools-core.md | 21 +- .../sysml2-tools-core/semantic.md | 31 ++ .../sysml2-tools-core/semantic/internal.md | 17 + .../semantic/internal/ast-builder.md | 3 + .../semantic/internal/reference-resolver.md | 3 + .../semantic/internal/supertype-walker.md | 3 + .../semantic/internal/symbol-table.md | 3 + .../semantic/internal/sysml-node.md | 3 + .../semantic/workspace-loader.md | 22 ++ requirements.yaml | 8 + .../Lint/LintCommand.cs | 3 +- .../Parser/Internal/StdlibLoader.cs | 6 +- .../Parser/WorkspaceParser.cs | 44 ++- .../Semantic/Internal/AstBuilder.cs | 291 ++++++++++++++++++ .../Semantic/Internal/ReferenceResolver.cs | 126 ++++++++ .../Semantic/Internal/SupertypeWalker.cs | 63 ++++ .../Semantic/Internal/SymbolTable.cs | 54 ++++ .../Semantic/Internal/SysmlNode.cs | 90 ++++++ .../Semantic/SysmlLoadResult.cs | 21 ++ .../Semantic/SysmlWorkspace.cs | 21 ++ .../Semantic/WorkspaceLoader.cs | 160 ++++++++++ .../Parser/WorkspaceParserTests.cs | 6 +- .../Semantic/SemanticOmgModelsTests.cs | 65 ++++ .../Semantic/WorkspaceLoaderTests.cs | 254 +++++++++++++++ test/SysMLModels/software-structure.sysml | 41 +++ 45 files changed, 1993 insertions(+), 29 deletions(-) create mode 100644 docs/design/sysml2-tools-core/semantic.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal/ast-builder.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal/symbol-table.md create mode 100644 docs/design/sysml2-tools-core/semantic/internal/sysml-node.md create mode 100644 docs/design/sysml2-tools-core/semantic/workspace-loader.md create mode 100644 docs/reqstream/sysml2-tools-core/semantic.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal/ast-builder.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal/reference-resolver.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal/symbol-table.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/internal/sysml-node.yaml create mode 100644 docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml create mode 100644 docs/verification/sysml2-tools-core/semantic.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md create mode 100644 docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md create mode 100644 docs/verification/sysml2-tools-core/semantic/workspace-loader.md create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/Internal/SysmlNode.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/SysmlLoadResult.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/SysmlWorkspace.cs create mode 100644 src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs create mode 100644 test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs create mode 100644 test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs create mode 100644 test/SysMLModels/software-structure.sysml diff --git a/.reviewmark.yaml b/.reviewmark.yaml index bb72c7d9..3ef084a9 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -170,6 +170,52 @@ reviews: - "test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs" - "test/DemaConsulting.SysML2Tools.Tests/Parser/OmgModelsTests.cs" + - id: SysML2Tools-Core-Semantic-Design + title: Review that DemaConsulting.SysML2Tools Semantic Design is Consistent and Complete + context: + - docs/reqstream/sysml2-tools-core/semantic.yaml + paths: + - "docs/design/introduction.md" + - "docs/design/sysml2-tools-core.md" + - "docs/design/sysml2-tools-core/semantic.md" + - "docs/design/sysml2-tools-core/semantic/**/*.md" + + - id: SysML2Tools-Core-Semantic-Verification + title: Review that DemaConsulting.SysML2Tools Semantic Verification is Consistent and Complete + context: + - docs/reqstream/sysml2-tools-core/semantic.yaml + paths: + - "docs/verification/introduction.md" + - "docs/verification/sysml2-tools-core.md" + - "docs/verification/sysml2-tools-core/semantic.md" + - "docs/verification/sysml2-tools-core/semantic/**/*.md" + + - id: SysML2Tools-Core-Semantic-AllRequirements + title: Review that All DemaConsulting.SysML2Tools Semantic Requirements are Complete + context: + - docs/design/sysml2-tools-core.md + - docs/reqstream/sysml2-tools-core.yaml + paths: + - "docs/reqstream/sysml2-tools-core/semantic/**/*.yaml" + - "docs/reqstream/sysml2-tools-core/semantic.yaml" + + - id: SysML2Tools-Core-Semantic-Implementation + title: Review of DemaConsulting.SysML2Tools Semantic Implementation + context: + - docs/reqstream/sysml2-tools-core/semantic.yaml + - docs/design/sysml2-tools-core/semantic.md + paths: + - "src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/SysmlLoadResult.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/SysmlWorkspace.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/Internal/SysmlNode.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs" + - "src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs" + - "test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs" + - "test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs" + # SysML2Tools SVG Renderer - id: SysML2Tools-Svg-Architecture title: Review that DemaConsulting.SysML2Tools.Svg Architecture Satisfies Requirements diff --git a/docs/design/introduction.md b/docs/design/introduction.md index 1540be6e..71e991d0 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -37,7 +37,14 @@ system, subsystem, and unit levels: - **WorkspaceParser** (Unit) — public API: parses file glob patterns and source strings against the embedded stdlib - **Internal** (Subsystem) — internal implementation details - **SysmlDiagnosticListener** (Unit) — collects ANTLR4 syntax errors as SysmlDiagnostic records - - **StdlibLoader** (Unit) — enumerates and loads embedded .sysml stdlib resources; defers .kerml to Phase 2 + - **StdlibLoader** (Unit) — enumerates and loads embedded stdlib resources; KerML errors are downgraded to Warnings + - **Semantic** (Subsystem) — SysML/KerML semantic model: symbol table, reference resolution, supertype walking + - **WorkspaceLoader** (Unit) — public API: loads SysML/KerML files into a semantic workspace + - **Internal** (Subsystem) — internal semantic implementation + - **AstBuilder** (Unit) — builds AST from ANTLR4 CST with qualified names and supertype lists + - **SymbolTable** (Unit) — registry mapping qualified names to declaration nodes + - **ReferenceResolver** (Unit) — resolves supertype references; detects circular imports + - **SupertypeWalker** (Unit) — walks specialization chains; detects cyclic specialization - **DemaConsulting.SysML2Tools.Svg** (System) — SVG renderer: renders `LayoutTree` to SVG output with zero external dependencies - TODO: subsystems and units to be defined in Phase 4+ @@ -50,7 +57,7 @@ system, subsystem, and unit levels: - **Cli** (Subsystem) — command-line argument parsing and I/O - **Context** (Unit) — argument parser and I/O owner - **Lint** (Subsystem) — lint command implementation - - **LintCommand** (Unit) — resolves glob patterns, invokes WorkspaceParser, reports diagnostics + - **LintCommand** (Unit) — resolves glob patterns, invokes WorkspaceLoader, reports diagnostics - **SelfTest** (Subsystem) — self-validation test runner - **Validation** (Unit) — self-validation test runner - **Utilities** (Subsystem) — shared utilities diff --git a/docs/design/sysml2-tools-core.md b/docs/design/sysml2-tools-core.md index d5232084..cc2f50ec 100644 --- a/docs/design/sysml2-tools-core.md +++ b/docs/design/sysml2-tools-core.md @@ -6,10 +6,14 @@ The `DemaConsulting.SysML2Tools` core library provides the SysML v2 parsing engi standard library, and the foundation for future semantic model, layout algorithms, and the `IRenderer` interface shared by all renderer packages. -The system contains one subsystem in Phase 1: **Parser**, which is further divided into the public -API unit (`WorkspaceParser`) and an internal subsystem (`Internal`) containing -`SysmlDiagnosticListener` and `StdlibLoader`. Supporting data types (`DiagnosticSeverity`, -`SysmlDiagnostic`, `WorkspaceParseResult`) are declared at the `Parser` namespace level. +The system contains two subsystems in Phase 2: **Parser** and **Semantic**. The Parser subsystem +provides syntax-level parsing, while the Semantic subsystem builds a symbol table and performs +reference resolution. The Parser subsystem is further divided into the public API unit +(`WorkspaceParser`) and an internal subsystem (`Internal`) containing `SysmlDiagnosticListener` +and `StdlibLoader`. The Semantic subsystem contains the public `WorkspaceLoader` unit and an +internal subsystem with `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. +Supporting data types (`DiagnosticSeverity`, `SysmlDiagnostic`, `WorkspaceParseResult`, +`SysmlLoadResult`, `SysmlWorkspace`) are declared at the appropriate namespace levels. ```mermaid flowchart TD @@ -18,8 +22,20 @@ flowchart TD SysmlDiagnosticListener StdlibLoader end + subgraph Semantic + WorkspaceLoader + AstBuilder + SymbolTable + ReferenceResolver + SupertypeWalker + end WorkspaceParser --> StdlibLoader WorkspaceParser --> SysmlDiagnosticListener + WorkspaceLoader --> WorkspaceParser + WorkspaceLoader --> AstBuilder + WorkspaceLoader --> SymbolTable + WorkspaceLoader --> ReferenceResolver + WorkspaceLoader --> SupertypeWalker ``` ## External Interfaces diff --git a/docs/design/sysml2-tools-core/semantic.md b/docs/design/sysml2-tools-core/semantic.md new file mode 100644 index 00000000..b4b59c62 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic.md @@ -0,0 +1,82 @@ +# DemaConsulting.SysML2Tools — Semantic Subsystem + +## Overview + +The Semantic subsystem builds a semantic workspace from the parsed SysML/KerML source files. It +operates as a second layer above the Parser subsystem, consuming ANTLR4 CSTs produced by +`WorkspaceParser` and transforming them into a structured symbol table with resolved references. + +## Architecture + +The Semantic subsystem contains one public unit (`WorkspaceLoader`) and an internal subsystem +(`Internal`) containing `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. + +```mermaid +flowchart TD + subgraph Semantic + WorkspaceLoader + subgraph Internal + AstBuilder + SymbolTable + ReferenceResolver + SupertypeWalker + end + end + WorkspaceLoader --> AstBuilder + WorkspaceLoader --> SymbolTable + WorkspaceLoader --> ReferenceResolver + WorkspaceLoader --> SupertypeWalker + AstBuilder --> SymbolTable +``` + +## External Interfaces + +**WorkspaceLoader.LoadAsync**: Loads the embedded stdlib plus every file in the provided +collection asynchronously. + +- *Type*: In-process .NET static async method. +- *Role*: Provider. +- *Contract*: Accepts `IEnumerable filePaths`; returns `Task` containing + a `SysmlWorkspace` with all qualified-name declarations and all collected diagnostics. Stdlib + is loaded and cached; user files are parsed in parallel on the thread pool. +- *Constraints*: `filePaths` must be valid, readable file paths. KerML stdlib parse errors are + downgraded to Warnings since the SysML v2 grammar does not fully cover KerML syntax. + +**SysmlLoadResult**: Aggregate result returned by `WorkspaceLoader.LoadAsync`. + +- *Type*: Sealed record. +- *Role*: Data transfer object. +- *Contract*: Exposes `SysmlWorkspace? Workspace`, `IReadOnlyList Diagnostics`, + and `bool HasErrors`. + +**SysmlWorkspace**: Fully-loaded and semantically-resolved workspace. + +- *Type*: Sealed class. +- *Role*: Data container. +- *Contract*: Exposes `IReadOnlyList Files` and `IReadOnlyDictionary Declarations` + mapping qualified names to declaration nodes. + +## Data Flow + +1. `WorkspaceLoader.LoadAsync` awaits the shared `Lazy>` stdlib result. + On first call the factory fires `Task.Run(BuildStdlibSemanticAsync)`, which reads each stdlib + resource stream, calls `WorkspaceParser.ParseSourceToCst`, downgradeskerml parse errors to + Warnings, builds an AST via `AstBuilder`, and registers it into a `SymbolTable`. +2. Concurrently, all caller-supplied file paths are dispatched via `Task.WhenAll`, each parsing + its content via `WorkspaceParser.ParseSourceToCst`, building an AST, and registering into + the same `SymbolTable`. +3. `ReferenceResolver.ResolveAll` traverses all AST nodes, checks each supertype name against + the symbol table, and emits Warning diagnostics for unresolved references. It also builds + an import graph and performs cycle detection. +4. `SupertypeWalker.WalkAll` traverses specialization chains for all symbols and emits Warning + diagnostics for cyclic specialization. +5. A `SysmlWorkspace` is constructed from the loaded file list and symbol table, and returned + in a `SysmlLoadResult`. + +## Design Constraints + +- KerML stdlib files are parsed with the SysML v2 grammar; any parse errors are downgraded to + Warnings since the grammar does not fully support KerML-specific syntax. +- The stdlib AST and symbol table are cached in a static `Lazy>` and shared across all + concurrent callers. +- `AstBuilder` is not thread-safe — a separate instance is created for each file. diff --git a/docs/design/sysml2-tools-core/semantic/internal.md b/docs/design/sysml2-tools-core/semantic/internal.md new file mode 100644 index 00000000..9ed02759 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal.md @@ -0,0 +1,22 @@ +# Semantic Internal Subsystem + +## Overview + +The Semantic Internal subsystem provides the implementation details of the semantic loading pipeline. +It contains four units: `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. + +## Units + +| Unit | Responsibility | +| --- | --- | +| `AstBuilder` | Visits ANTLR4 CST; builds typed AST nodes with qualified names and supertype lists | +| `SymbolTable` | Registry mapping fully-qualified names to their AST nodes | +| `ReferenceResolver` | Checks supertype references; detects circular import chains | +| `SupertypeWalker` | Walks specialization chains; detects cyclic specialization | + +## Interaction Model + +1. `WorkspaceLoader` creates one `AstBuilder` per file and calls `Build(rootNamespaceContext)`. +2. The returned `SysmlPackageNode` root is passed to `SymbolTable.RegisterAll`. +3. After all files are registered, `ReferenceResolver.ResolveAll` traverses all AST roots. +4. Finally, `SupertypeWalker.WalkAll` iterates over all symbols in the table. diff --git a/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md b/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md new file mode 100644 index 00000000..e39b0bac --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md @@ -0,0 +1,41 @@ +# AstBuilder + +## Overview + +`AstBuilder` extends `SysMLv2ParserBaseVisitor` and builds a typed AST from the +ANTLR4 CST produced by `SysMLv2Parser`. + +## Namespace Stack + +A `List _namespaceStack` tracks the current nesting path. When entering a named package +or definition, the name is pushed; it is popped before returning. `QualifyName(name)` joins the +stack with `::` to form the fully-qualified name. + +## Key Visit Methods + +| Method | Input Context | Output | +| --- | --- | --- | +| `VisitRootNamespace` | `RootNamespaceContext` | `SysmlPackageNode` (root) | +| `VisitPackage` | `PackageContext` | `SysmlPackageNode` | +| `VisitLibraryPackage` | `LibraryPackageContext` | `SysmlPackageNode` | +| `VisitPartDefinition` | `PartDefinitionContext` | `SysmlDefinitionNode` | +| `VisitAttributeDefinition` | `AttributeDefinitionContext` | `SysmlDefinitionNode` | +| `VisitItemDefinition` | `ItemDefinitionContext` | `SysmlDefinitionNode` | +| `VisitViewDefinition` | `ViewDefinitionContext` | `SysmlViewNode` | +| `VisitViewpointDefinition` | `ViewpointDefinitionContext` | `SysmlViewpointNode` | + +## Name Extraction + +`GetDeclaredName(IdentificationContext)` handles the three grammar alternatives: + +- `< shortName > declaredName` (alt 1): returns `name(1).GetText()`. +- `< shortName >` (alt 2): no declared name — returns null. +- `declaredName` (alt 3): returns `name(0).GetText()`. + +Elements with no declared name are treated as anonymous and are not registered in the symbol table. + +## Supertype Extraction + +`GetSubclassificationSupertypes(SubclassificationPartContext)` iterates +`ownedSubclassification()` entries and calls `qualifiedName().GetText()` on each to produce +the supertype name list. diff --git a/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md b/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md new file mode 100644 index 00000000..e35db2b8 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md @@ -0,0 +1,26 @@ +# ReferenceResolver + +## Overview + +`ReferenceResolver` performs two analyses over the loaded files: + +1. **Import graph cycle detection** — builds a directed graph of import relationships between + files and uses depth-first search to detect cycles. +2. **Supertype reference resolution** — checks each `SupertypeName` in all AST nodes against + the symbol table and emits a Warning for any name not found. + +## Import Graph + +`BuildImportGraph` iterates all file roots, collecting `SysmlImportNode.ImportedNamespace` +values into a `HashSet` per file. The result is a `Dictionary>` +from file path to imported names. + +`DetectCircularImports` runs a DFS over the import graph keys. A cycle is detected when a +node in the current DFS stack is encountered again. The Warning message names the file and +the imported namespace that completes the cycle. + +## Supertype Resolution + +`ResolveNode` traverses each AST node's `SupertypeNames`. For each name not found in the +symbol table (and not already reported in this file), a Warning diagnostic is emitted. The +`resolvedInFile` set prevents duplicate warnings for the same name within a file. diff --git a/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md b/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md new file mode 100644 index 00000000..f927fc7a --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md @@ -0,0 +1,26 @@ +# SupertypeWalker + +## Overview + +`SupertypeWalker` traverses the specialization chains of all symbols registered in the +`SymbolTable` to detect cyclic specialization (e.g., A specializes B, B specializes A). + +## Algorithm + +`WalkAll` iterates over all symbols and for each unvisited symbol calls `WalkNode`. `WalkNode` +maintains two sets: + +- `chainVisited` — names in the current DFS path (used to detect cycles in this chain). +- `globalVisited` — all visited names across all chains (used to avoid redundant processing). + +For each supertype name in the current node: + +- If the name is in `chainVisited`, a cyclic specialization warning is emitted and the loop + continues (the cycle is not traversed further). +- If the supertype is registered in the symbol table and not yet globally visited, `WalkNode` + is called recursively with a copy of `chainVisited`. + +## Warning Format + +Cyclic specialization warnings use `DiagnosticSeverity.Warning` with an empty `FilePath` +(since the cycle spans multiple potential source files). diff --git a/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md b/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md new file mode 100644 index 00000000..42261426 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md @@ -0,0 +1,22 @@ +# SymbolTable + +## Overview + +`SymbolTable` is a registry mapping fully-qualified SysML/KerML names to their `SysmlNode` +declaration nodes. It is populated by calling `RegisterAll` on each AST root after parsing. + +## Algorithm + +`RegisterAll(SysmlNode? root)` performs a depth-first traversal of the AST. For each node +with a non-null, non-empty `QualifiedName`, it calls `_symbols.TryAdd(QualifiedName, node)`. +`TryAdd` is used (not direct assignment) to silently ignore duplicate declarations. + +## Lookup + +`Lookup(string qualifiedName)` returns the registered node, or null if not found. +`Contains(string qualifiedName)` returns a boolean without allocating a node reference. + +## Thread Safety + +`SymbolTable` is not thread-safe. In `WorkspaceLoader`, all `RegisterAll` calls occur on a +single thread after the parallel parse tasks complete. diff --git a/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md b/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md new file mode 100644 index 00000000..0fd1a301 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md @@ -0,0 +1,28 @@ +# SysmlNode — AST Node Hierarchy + +## Overview + +`SysmlNode` is the abstract base class for all SysML/KerML AST nodes. Concrete subtypes represent +packages, definitions, features, imports, views, and viewpoints. + +## Class Hierarchy + +| Class | Purpose | +| --- | --- | +| `SysmlNode` (abstract) | Base: Name, QualifiedName, Children, SupertypeNames, ImportedNames | +| `SysmlPackageNode` | Package or namespace declaration | +| `SysmlDefinitionNode` | Definition element (part def, attribute def, etc.); adds DefinitionKeyword | +| `SysmlFeatureNode` | Feature/usage element | +| `SysmlImportNode` | Import declaration; adds ImportedNamespace, IsWildcard | +| `SysmlViewNode` | View definition | +| `SysmlViewpointNode` | Viewpoint definition | + +## Properties + +All nodes carry: + +- `Name` — simple (unqualified) name, or null if anonymous. +- `QualifiedName` — fully-qualified name in containing namespace. +- `Children` — nested AST nodes. +- `SupertypeNames` — qualified names of supertypes referenced via `specializes` / `:>`. +- `ImportedNames` — qualified names of imported namespaces. diff --git a/docs/design/sysml2-tools-core/semantic/workspace-loader.md b/docs/design/sysml2-tools-core/semantic/workspace-loader.md new file mode 100644 index 00000000..6d7bfc46 --- /dev/null +++ b/docs/design/sysml2-tools-core/semantic/workspace-loader.md @@ -0,0 +1,31 @@ +# WorkspaceLoader + +## Overview + +`WorkspaceLoader` is the public entry point for the Semantic subsystem. It orchestrates parsing, +AST building, symbol registration, reference resolution, and supertype walking into a single +`SysmlLoadResult`. + +## Methods + +### `LoadAsync(IEnumerable filePaths)` + +1. Awaits the cached stdlib semantic result (built once via `BuildStdlibSemanticAsync`). +2. Dispatches all user file paths to `ParseUserFileAsync` in parallel via `Task.WhenAll`. +3. Collects all AST roots and registers them into a shared `SymbolTable`. +4. Runs `ReferenceResolver.ResolveAll` on all file roots. +5. Runs `SupertypeWalker.WalkAll` on the populated symbol table. +6. Constructs a `SysmlWorkspace` from the file list and symbol table. +7. Returns a `SysmlLoadResult(workspace, allDiagnostics)`. + +## Caching + +The stdlib semantic result is cached via `static readonly Lazy>`. +This ensures the 94 stdlib files are parsed and their ASTs registered at most once per +application lifetime, regardless of how many concurrent callers invoke `LoadAsync`. + +## KerML Handling + +KerML stdlib parse errors are downgraded to Warnings in `BuildStdlibSemanticAsync`. The +SysML v2 grammar does not fully cover KerML-specific syntax (e.g., `return` keyword), so +parse failures in `.kerml` files are expected and non-fatal. diff --git a/docs/reqstream/sysml2-tools-core/semantic.yaml b/docs/reqstream/sysml2-tools-core/semantic.yaml new file mode 100644 index 00000000..61325e90 --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic.yaml @@ -0,0 +1,79 @@ +--- +# Semantic Subsystem Requirements +# +# PURPOSE: +# - Define requirements for the SysML2Tools Semantic subsystem +# - WorkspaceLoader provides the public API for loading SysML/KerML files into a semantic workspace +# - Requirements describe observable semantic loading behavior, not internal implementation + +sections: + - title: Semantic Subsystem Requirements + requirements: + - id: SysML2Tools-Core-Semantic-LoadAsync + title: >- + WorkspaceLoader.LoadAsync shall accept a collection of file paths and load each file + against the embedded stdlib, returning a SysmlLoadResult. + justification: | + A collection-based async API allows callers to supply any number of pre-resolved + file paths and receive a single aggregate result including a semantic workspace + and all diagnostics. Stdlib is loaded and cached; user files are processed in parallel. + tests: + - WorkspaceLoader_LoadAsync_NoFiles_ReturnsNonNullWorkspace + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + + - id: SysML2Tools-Core-Semantic-SymbolRegistration + title: >- + WorkspaceLoader.LoadAsync shall register all named declarations with their + fully-qualified names in the workspace Declarations dictionary. + justification: | + A qualified-name registry enables callers to look up declarations by their fully + qualified names, supporting IDE integration, reference resolution, and future diagram + rendering features. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + - WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames + - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition + + - id: SysML2Tools-Core-Semantic-StdlibDeclarations + title: >- + WorkspaceLoader.LoadAsync shall load all 94 embedded stdlib files and register + their declarations before processing user files. + justification: | + The SysML v2 and KerML standard library defines the base types and packages that + user models reference. Loading stdlib first ensures user files are resolved in + the correct context and stdlib declarations are available for reference resolution. + tests: + - WorkspaceLoader_LoadAsync_NoFiles_ReturnsNonNullWorkspace + - WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered + + - id: SysML2Tools-Core-Semantic-UnresolvedReference + title: >- + WorkspaceLoader.LoadAsync shall produce a Warning diagnostic for each supertype + reference that cannot be resolved in the symbol table. + justification: | + Unresolved supertype references indicate that a definition specializes a type that + has not been loaded. Reporting these as Warnings (not Errors) allows callers to + diagnose incomplete models while still returning a usable workspace. + tests: + - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning + + - id: SysML2Tools-Core-Semantic-CircularImport + title: >- + WorkspaceLoader.LoadAsync shall detect circular import chains and produce a Warning + diagnostic without looping infinitely. + justification: | + Circular imports in SysML models can arise from split namespace declarations across + files. The loader must detect and report these without hanging the calling process. + tests: + - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop + + - id: SysML2Tools-Core-Semantic-HasErrors + title: >- + SysmlLoadResult.HasErrors shall return true if and only if at least one + Error-severity diagnostic is present in the result. + justification: | + Callers need a simple boolean gate to decide whether to fail a build or report + errors without inspecting the full diagnostic list themselves. + tests: + - WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace + - WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal.yaml new file mode 100644 index 00000000..75a7c7ce --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal.yaml @@ -0,0 +1,43 @@ +--- +# Semantic Internal Subsystem Requirements +# +# PURPOSE: +# - Define requirements for the internal implementation of the Semantic subsystem +# - Internal components: AstBuilder, SymbolTable, ReferenceResolver, SupertypeWalker + +sections: + - title: Semantic Internal Subsystem Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-AstBuild + title: >- + The internal semantic components shall build an AST from the ANTLR4 CST and + register all named elements in the symbol table. + justification: | + A symbol table indexed by qualified name is required for reference resolution + and supertype walking. The AST provides a structured representation of the model. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + - WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames + - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition + + - id: SysML2Tools-Core-Semantic-Internal-ReferenceResolution + title: >- + The ReferenceResolver shall resolve supertype references against the symbol table + and produce Warning diagnostics for any unresolved names. + justification: | + Reference resolution identifies incomplete models where supertype names do not + correspond to any loaded declaration. Warnings allow callers to inspect model + completeness without blocking on hard errors. + tests: + - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning + - WorkspaceLoader_LoadAsync_SpecializesChain_Registered + + - id: SysML2Tools-Core-Semantic-Internal-SupertypeWalking + title: >- + The SupertypeWalker shall traverse specialization chains and detect cyclic + specialization, producing a Warning diagnostic for each cycle. + justification: | + Cyclic specialization (A specializes B, B specializes A) is invalid in SysML v2 + and must be detected to prevent infinite loops in downstream analysis. + tests: + - WorkspaceLoader_LoadAsync_SpecializesChain_Registered diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/ast-builder.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/ast-builder.yaml new file mode 100644 index 00000000..b1a56f9d --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/ast-builder.yaml @@ -0,0 +1,40 @@ +--- +# AstBuilder Requirements +# +# PURPOSE: +# - Define requirements for the AstBuilder internal unit + +sections: + - title: AstBuilder Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-AstBuilder-Build + title: >- + AstBuilder shall visit the ANTLR4 CST produced by SysMLv2Parser and return a + SysmlPackageNode representing the root namespace. + justification: | + The CST produced by the ANTLR4 grammar contains all parse tree nodes. The AST + builder distills these into a compact, traversable tree of named elements. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + + - id: SysML2Tools-Core-Semantic-Internal-AstBuilder-QualifiedNames + title: >- + AstBuilder shall compute fully-qualified names by joining ancestor names with + the "::" separator as it descends into nested namespaces. + justification: | + Fully-qualified names are required for symbol table lookup and reference resolution. + The builder must track the current namespace context during traversal. + tests: + - WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames + - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition + + - id: SysML2Tools-Core-Semantic-Internal-AstBuilder-Supertypes + title: >- + AstBuilder shall extract supertype qualified names from subclassification and + specialization parts and attach them to the corresponding AST node. + justification: | + Supertype names are required for reference resolution and supertype walking. + They must be extracted during AST construction before symbol table operations. + tests: + - WorkspaceLoader_LoadAsync_SpecializesChain_Registered + - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/reference-resolver.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/reference-resolver.yaml new file mode 100644 index 00000000..e1f03769 --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/reference-resolver.yaml @@ -0,0 +1,28 @@ +--- +# ReferenceResolver Requirements +# +# PURPOSE: +# - Define requirements for the ReferenceResolver internal unit + +sections: + - title: ReferenceResolver Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-ReferenceResolver-Resolve + title: >- + ReferenceResolver.ResolveAll shall check each supertype name referenced by + AST nodes against the symbol table and produce a Warning for any unresolved name. + justification: | + Unresolved references indicate model incompleteness. Reporting as Warnings allows + the caller to continue analysis and provide partial results. + tests: + - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning + + - id: SysML2Tools-Core-Semantic-Internal-ReferenceResolver-CircularImport + title: >- + ReferenceResolver.ResolveAll shall detect circular import chains in the import + graph and produce a Warning diagnostic for each detected cycle. + justification: | + Circular imports must be detected to avoid infinite loops in reference resolution. + The cycle detection uses depth-first search with a traversal stack. + tests: + - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml new file mode 100644 index 00000000..fbc0a0c9 --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml @@ -0,0 +1,19 @@ +--- +# SupertypeWalker Requirements +# +# PURPOSE: +# - Define requirements for the SupertypeWalker internal unit + +sections: + - title: SupertypeWalker Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-SupertypeWalker-Walk + title: >- + SupertypeWalker.WalkAll shall traverse the specialization chains of all + registered symbols and detect cyclic specialization. + justification: | + Cyclic specialization (e.g., A specializes B, B specializes A) is semantically + invalid in SysML v2. The walker must detect and report these cycles without + looping infinitely. + tests: + - WorkspaceLoader_LoadAsync_SpecializesChain_Registered diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/symbol-table.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/symbol-table.yaml new file mode 100644 index 00000000..6b7e2e0c --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/symbol-table.yaml @@ -0,0 +1,29 @@ +--- +# SymbolTable Requirements +# +# PURPOSE: +# - Define requirements for the SymbolTable internal unit + +sections: + - title: SymbolTable Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-SymbolTable-Register + title: >- + SymbolTable.RegisterAll shall recursively register all AST nodes with a non-null, + non-empty QualifiedName into the symbol dictionary, keyed by qualified name. + justification: | + All named elements in the model must be reachable by their qualified name for + reference resolution. Anonymous elements (null or empty QualifiedName) are skipped. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + - WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames + + - id: SysML2Tools-Core-Semantic-Internal-SymbolTable-Lookup + title: >- + SymbolTable.Lookup shall return the registered node for a given qualified name, + or null if the name is not registered. + justification: | + Reference resolution and supertype walking require efficient by-name lookup. + Returning null for unknown names allows the resolver to produce diagnostics. + tests: + - WorkspaceLoader_LoadAsync_SpecializesChain_Registered diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/sysml-node.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/sysml-node.yaml new file mode 100644 index 00000000..f8e6efef --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/sysml-node.yaml @@ -0,0 +1,29 @@ +--- +# SysmlNode AST Node Hierarchy Requirements +# +# PURPOSE: +# - Define requirements for the SysmlNode AST node hierarchy + +sections: + - title: SysmlNode Requirements + requirements: + - id: SysML2Tools-Core-Semantic-Internal-SysmlNode-Hierarchy + title: >- + SysmlNode shall define an abstract base class with Name, QualifiedName, Children, + SupertypeNames, and ImportedNames properties shared by all AST node types. + justification: | + A common base class enables uniform traversal and registration of all AST nodes + regardless of their specific element type. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + + - id: SysML2Tools-Core-Semantic-Internal-SysmlNode-Types + title: >- + The SysmlNode hierarchy shall include SysmlPackageNode, SysmlDefinitionNode, + SysmlFeatureNode, SysmlImportNode, SysmlViewNode, and SysmlViewpointNode. + justification: | + Separate node types for packages, definitions, features, imports, views, and + viewpoints allow future analysis passes to dispatch on element type without + string comparisons. + tests: + - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition diff --git a/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml b/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml new file mode 100644 index 00000000..e23d1eab --- /dev/null +++ b/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml @@ -0,0 +1,29 @@ +--- +# WorkspaceLoader Unit Requirements +# +# PURPOSE: +# - Define requirements for the WorkspaceLoader unit +# - WorkspaceLoader is the public entry point for semantic loading + +sections: + - title: WorkspaceLoader Unit Requirements + requirements: + - id: SysML2Tools-Core-Semantic-WorkspaceLoader-Load + title: >- + WorkspaceLoader.LoadAsync shall parse each user file asynchronously on the thread pool. + justification: | + Parallel parsing reduces wall-clock time for multi-file workspaces. The stdlib is + parsed once and cached via a Lazy> so concurrent callers share the result. + tests: + - WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration + - WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames + + - id: SysML2Tools-Core-Semantic-WorkspaceLoader-FileError + title: >- + WorkspaceLoader.LoadAsync shall produce an Error diagnostic when a user file + cannot be read from disk. + justification: | + A file-read failure prevents the workspace from being built for that file. + Reporting it as an Error diagnostic allows callers to surface the problem to users. + tests: + - WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace diff --git a/docs/reqstream/sysml2-tools-tool/lint.yaml b/docs/reqstream/sysml2-tools-tool/lint.yaml index 60cd399c..d1cc6232 100644 --- a/docs/reqstream/sysml2-tools-tool/lint.yaml +++ b/docs/reqstream/sysml2-tools-tool/lint.yaml @@ -22,12 +22,12 @@ sections: - id: SysML2Tools-Tool-Lint-Parse title: >- - The LintCommand shall invoke WorkspaceParser.ParseAsync with the resolved + The LintCommand shall invoke WorkspaceLoader.LoadAsync with the resolved file paths derived from the provided patterns. justification: | - Delegating parsing to WorkspaceParser ensures that all SysML files are - validated against the embedded stdlib and that diagnostics are collected - consistently for all user-supplied files. + Delegating loading to WorkspaceLoader ensures that all SysML files are + validated against the embedded stdlib, that semantic analysis is performed, + and that all diagnostics are collected consistently for all user-supplied files. tests: - LintSubsystem_Parse_ValidFile_ParsesWithoutErrors diff --git a/docs/verification/sysml2-tools-core.md b/docs/verification/sysml2-tools-core.md index 8d48108e..e2eb49b7 100644 --- a/docs/verification/sysml2-tools-core.md +++ b/docs/verification/sysml2-tools-core.md @@ -3,9 +3,10 @@ ## Verification Approach System-level verification for the `DemaConsulting.SysML2Tools` core library uses unit tests -in `DemaConsulting.SysML2Tools.Tests`. Tests exercise the public `WorkspaceParser` API and -validate that the embedded stdlib parses without errors. The xUnit v3 framework discovers -and runs all test methods; results are captured in TRX files consumed by ReqStream. +in `DemaConsulting.SysML2Tools.Tests`. Tests exercise the public `WorkspaceParser` and +`WorkspaceLoader` APIs and validate that the embedded stdlib parses without errors and that +the semantic workspace is populated correctly. The xUnit v3 framework discovers and runs all +test methods; results are captured in TRX files consumed by ReqStream. ## Test Environment @@ -16,12 +17,18 @@ SDK installation. ## Acceptance Criteria - All unit tests pass with zero failures across all three target frameworks. -- All 58 embedded `.sysml` stdlib files parse without producing any error-severity diagnostics. +- All 94 embedded stdlib files are included in parse results; KerML parse errors are downgraded + to Warnings so they do not affect `HasErrors`. - `WorkspaceParser` correctly propagates the caller-supplied file path in all diagnostics. - Invalid SysML syntax produces at least one `Error`-severity diagnostic. +- `WorkspaceLoader` correctly registers qualified names from SysML packages and definitions. +- Unresolved supertype references produce `Warning`-severity diagnostics. ## Test Scenarios -See *Parser Verification Design* for the full list of test scenarios. Primary acceptance -evidence is provided by `Parse_StdlibOnly_NoErrors`, which parses all 58 embedded stdlib -`.sysml` files and asserts `HasErrors` is false. +See *Parser Verification Design* and *Semantic Verification Design* for the full list of +test scenarios. Primary acceptance evidence is provided by: + +- `Parse_StdlibOnly_NoErrors` — parses all 94 stdlib files, asserts `HasErrors` is false. +- `WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered` — loads all stdlib files, asserts + `HasErrors` is false and `Declarations` is non-empty. diff --git a/docs/verification/sysml2-tools-core/semantic.md b/docs/verification/sysml2-tools-core/semantic.md new file mode 100644 index 00000000..a9454dee --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic.md @@ -0,0 +1,31 @@ +# DemaConsulting.SysML2Tools — Semantic Subsystem Verification + +## Verification Approach + +Semantic subsystem verification uses unit tests in `DemaConsulting.SysML2Tools.Tests`. +Tests exercise the public `WorkspaceLoader` API and validate that the symbol table is +populated correctly, that reference resolution produces expected diagnostics, and that +the embedded stdlib loads without errors. The xUnit v3 framework discovers and runs all +test methods; results are captured in TRX files consumed by ReqStream. + +## Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary files are created in `Path.GetTempPath()` and cleaned up after each test. + +## Acceptance Criteria + +- All unit tests pass with zero failures across all three target frameworks. +- The stdlib loads without Error-level diagnostics (KerML parse errors are downgraded to Warnings). +- All 94 stdlib files are counted in the workspace file list. +- A single-package SysML file registers its package name in the workspace declarations. +- Nested packages register both parent and child qualified names. +- Part definitions register their qualified names. +- Unresolved supertype references produce Warning diagnostics. +- Circular imports produce Warning diagnostics without infinite loops. + +## Test Scenarios + +See *Semantic Verification* for the full list of test scenarios. Primary acceptance evidence +is provided by `WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered`, which loads all +94 stdlib files and asserts `HasErrors` is false and `Declarations` is non-empty. diff --git a/docs/verification/sysml2-tools-core/semantic/internal.md b/docs/verification/sysml2-tools-core/semantic/internal.md new file mode 100644 index 00000000..a751a68a --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal.md @@ -0,0 +1,17 @@ +# Semantic Internal Subsystem Verification + +## Verification Approach + +Internal semantic components are verified indirectly through `WorkspaceLoaderTests`. There are +no direct unit tests for `AstBuilder`, `SymbolTable`, `ReferenceResolver`, or `SupertypeWalker` +as these are internal implementation details. Their behavior is observable through the public +`WorkspaceLoader.LoadAsync` API. + +## Traceability + +| Internal Component | Verified By | +| --- | --- | +| `AstBuilder` | All WorkspaceLoaderTests that check Declarations | +| `SymbolTable` | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | +| `ReferenceResolver` | `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning`, `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | +| `SupertypeWalker` | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md b/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md new file mode 100644 index 00000000..55ac2438 --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md @@ -0,0 +1,3 @@ +# AstBuilder Verification + +Verified indirectly through WorkspaceLoaderTests. AstBuilder name extraction is confirmed by WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames. Supertype extraction is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered. diff --git a/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md b/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md new file mode 100644 index 00000000..8ced5a14 --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md @@ -0,0 +1,3 @@ +# ReferenceResolver Verification + +Verified indirectly through WorkspaceLoaderTests. Unresolved reference detection is confirmed by WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning. Circular import detection is confirmed by WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop. diff --git a/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md b/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md new file mode 100644 index 00000000..aa752b42 --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md @@ -0,0 +1,3 @@ +# SupertypeWalker Verification + +Verified indirectly through WorkspaceLoaderTests. Specialization chain walking is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered, which asserts that no unresolved warning is produced for a resolved supertype. diff --git a/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md b/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md new file mode 100644 index 00000000..7da56c3b --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md @@ -0,0 +1,3 @@ +# SymbolTable Verification + +Verified indirectly through WorkspaceLoaderTests. Registration is confirmed by WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration. Lookup behavior is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered. diff --git a/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md b/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md new file mode 100644 index 00000000..dec8d1d9 --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md @@ -0,0 +1,3 @@ +# SysmlNode Verification + +Verified indirectly through WorkspaceLoaderTests. The correct construction of SysmlPackageNode and SysmlDefinitionNode is confirmed by WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration and WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition. diff --git a/docs/verification/sysml2-tools-core/semantic/workspace-loader.md b/docs/verification/sysml2-tools-core/semantic/workspace-loader.md new file mode 100644 index 00000000..7d68e9ff --- /dev/null +++ b/docs/verification/sysml2-tools-core/semantic/workspace-loader.md @@ -0,0 +1,22 @@ +# WorkspaceLoader Verification + +## Verification Approach + +`WorkspaceLoader` is verified through 9 progressive-level tests in `WorkspaceLoaderTests`: + +| Test | Level | Assertion | +| --- | --- | --- | +| `WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace` | 1 | Empty file returns non-null workspace with `HasErrors = false` | +| `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | 2 | Single package name registered | +| `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | 3 | Nested package qualified names registered | +| `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | 4 | Part def qualified name registered | +| `WorkspaceLoader_LoadAsync_NoFiles_ReturnsNonNullWorkspace` | 5 | No-files load returns non-null workspace | +| `WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered` | 6 | Stdlib contributes declarations without errors | +| `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | 7 | Resolved supertype produces no unresolved warning | +| `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning` | 8 | Unresolved supertype produces Warning | +| `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | 9 | Circular import produces Warning, completes in finite time | + +## Level 10 Gate + +`SemanticOmgModels_AllModels_ResolveWithZeroErrors` confirms that all OMG model files in +`test/SysMLModels/` load with zero Error-level diagnostics. diff --git a/requirements.yaml b/requirements.yaml index 6a27b730..25bea5e5 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -3,6 +3,14 @@ includes: - docs/reqstream/sysml2-tools-core.yaml - docs/reqstream/sysml2-tools-core/parser.yaml + - docs/reqstream/sysml2-tools-core/semantic.yaml + - docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal/sysml-node.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal/ast-builder.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal/symbol-table.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal/reference-resolver.yaml + - docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml - docs/reqstream/sysml2-tools-svg.yaml - docs/reqstream/sysml2-tools-png.yaml - docs/reqstream/sysml2-tools-tool.yaml diff --git a/src/DemaConsulting.SysML2Tools.Tool/Lint/LintCommand.cs b/src/DemaConsulting.SysML2Tools.Tool/Lint/LintCommand.cs index 0f81e347..14a69003 100644 --- a/src/DemaConsulting.SysML2Tools.Tool/Lint/LintCommand.cs +++ b/src/DemaConsulting.SysML2Tools.Tool/Lint/LintCommand.cs @@ -20,6 +20,7 @@ using DemaConsulting.SysML2Tools.Cli; using DemaConsulting.SysML2Tools.Parser; +using DemaConsulting.SysML2Tools.Semantic; namespace DemaConsulting.SysML2Tools.Lint; @@ -44,7 +45,7 @@ public static async Task RunAsync(Context context) context.WriteLine($"Linting {files.Count} file(s)..."); - var result = await WorkspaceParser.ParseAsync(files).ConfigureAwait(false); + var result = await WorkspaceLoader.LoadAsync(files).ConfigureAwait(false); foreach (var diagnostic in result.Diagnostics) { diff --git a/src/DemaConsulting.SysML2Tools/Parser/Internal/StdlibLoader.cs b/src/DemaConsulting.SysML2Tools/Parser/Internal/StdlibLoader.cs index 1c2c8b54..2d3954d5 100644 --- a/src/DemaConsulting.SysML2Tools/Parser/Internal/StdlibLoader.cs +++ b/src/DemaConsulting.SysML2Tools/Parser/Internal/StdlibLoader.cs @@ -38,7 +38,7 @@ internal static class StdlibLoader /// /// /// Virtual paths use the [stdlib] prefix to distinguish them from user files - /// in diagnostic messages. + /// in diagnostic messages. Loads all 94 stdlib files (58 .sysml + 36 .kerml). /// internal static IEnumerable<(string VirtualPath, string Content)> LoadAll() { @@ -50,9 +50,9 @@ internal static class StdlibLoader continue; } - if (!name.EndsWith(".sysml", StringComparison.OrdinalIgnoreCase)) + if (!name.EndsWith(".sysml", StringComparison.OrdinalIgnoreCase) && + !name.EndsWith(".kerml", StringComparison.OrdinalIgnoreCase)) { - // .kerml files require the KerML grammar — deferred to Phase 2 continue; } diff --git a/src/DemaConsulting.SysML2Tools/Parser/WorkspaceParser.cs b/src/DemaConsulting.SysML2Tools/Parser/WorkspaceParser.cs index 4cc42676..05812753 100644 --- a/src/DemaConsulting.SysML2Tools/Parser/WorkspaceParser.cs +++ b/src/DemaConsulting.SysML2Tools/Parser/WorkspaceParser.cs @@ -156,6 +156,11 @@ public static IReadOnlyList ParseSource(string filePath, string /// /// Parses the stdlib and returns the aggregate files and diagnostics. /// + /// + /// KerML files are included in the file count but any parse errors they produce are downgraded + /// to Warnings, since the SysML v2 grammar does not fully cover KerML-specific syntax. + /// KerML semantic support is handled at the semantic layer. + /// private static (IReadOnlyList Files, IReadOnlyList Diagnostics) ParseStdlibInternal() { var files = new List(); @@ -163,16 +168,37 @@ private static (IReadOnlyList Files, IReadOnlyList Diag foreach (var (virtualPath, content) in StdlibLoader.LoadAll()) { files.Add(virtualPath); - ParseSource(virtualPath, content, diagnostics); + var fileDiagnostics = new List(); + ParseSource(virtualPath, content, fileDiagnostics); + + // KerML files may produce parse errors with the SysML v2 grammar — downgrade to Warning + if (virtualPath.EndsWith(".kerml", StringComparison.OrdinalIgnoreCase)) + { + foreach (var d in fileDiagnostics) + { + diagnostics.Add(d.Severity == DiagnosticSeverity.Error + ? d with { Severity = DiagnosticSeverity.Warning } + : d); + } + } + else + { + diagnostics.AddRange(fileDiagnostics); + } } return (files, diagnostics); } /// - /// Parses SysML v2 source text and appends any diagnostics to . + /// Parses SysML v2 source text, appends diagnostics, and returns the CST root. /// - private static void ParseSource(string filePath, string content, List diagnostics) + /// Virtual file path used in diagnostics. + /// SysML v2 source text. + /// Mutable list to append parse diagnostics to. + /// The CST root . + internal static SysMLv2Parser.RootNamespaceContext ParseSourceToCst( + string filePath, string content, List diagnostics) { var listener = new SysmlDiagnosticListener(filePath, diagnostics); @@ -188,7 +214,15 @@ private static void ParseSource(string filePath, string content, List + /// Parses SysML v2 source text and appends any diagnostics to . + /// + private static void ParseSource(string filePath, string content, List diagnostics) + { + // Invoke full parse; discard the CST — only diagnostics are retained + _ = ParseSourceToCst(filePath, content, diagnostics); } } diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs new file mode 100644 index 00000000..2d4f1cfd --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs @@ -0,0 +1,291 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Parser.Antlr; + +namespace DemaConsulting.SysML2Tools.Semantic.Internal; + +/// +/// Builds a SysML/KerML AST from an ANTLR4 CST produced by . +/// +internal sealed class AstBuilder : SysMLv2ParserBaseVisitor +{ + private readonly List _namespaceStack = new(); + + /// + /// Gets the current namespace prefix by joining the stack with "::". + /// + private string CurrentPrefix => _namespaceStack.Count > 0 + ? string.Join("::", _namespaceStack) + : string.Empty; + + /// + /// Builds a fully-qualified name from the given simple name and the current namespace stack. + /// + private string QualifyName(string name) + { + var prefix = CurrentPrefix; + return prefix.Length > 0 ? $"{prefix}::{name}" : name; + } + + /// + /// Builds the AST root from the given CST root namespace context. + /// + public SysmlPackageNode? Build(SysMLv2Parser.RootNamespaceContext context) + { + return Visit(context) as SysmlPackageNode; + } + + /// + public override SysmlNode? VisitRootNamespace(SysMLv2Parser.RootNamespaceContext context) + { + var children = CollectBodyElements(context.packageBodyElement()); + return new SysmlPackageNode + { + Children = children, + }; + } + + /// + public override SysmlNode? VisitPackage(SysMLv2Parser.PackageContext context) + { + var name = GetDeclaredName(context.packageDeclaration()?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + + _namespaceStack.Add(name); + var children = CollectBodyElements(context.packageBody()?.packageBodyElement() ?? []); + _namespaceStack.RemoveAt(_namespaceStack.Count - 1); + + return new SysmlPackageNode + { + Name = name, + QualifiedName = qualifiedName, + Children = children, + }; + } + + /// + public override SysmlNode? VisitLibraryPackage(SysMLv2Parser.LibraryPackageContext context) + { + var name = GetDeclaredName(context.packageDeclaration()?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + + _namespaceStack.Add(name); + var children = CollectBodyElements(context.packageBody()?.packageBodyElement() ?? []); + _namespaceStack.RemoveAt(_namespaceStack.Count - 1); + + return new SysmlPackageNode + { + Name = name, + QualifiedName = qualifiedName, + Children = children, + }; + } + + /// + public override SysmlNode? VisitPartDefinition(SysMLv2Parser.PartDefinitionContext context) + { + return BuildDefinitionNode(context.definition(), "part def"); + } + + /// + public override SysmlNode? VisitAttributeDefinition(SysMLv2Parser.AttributeDefinitionContext context) + { + return BuildDefinitionNode(context.definition(), "attribute def"); + } + + /// + public override SysmlNode? VisitItemDefinition(SysMLv2Parser.ItemDefinitionContext context) + { + return BuildDefinitionNode(context.definition(), "item def"); + } + + /// + public override SysmlNode? VisitViewDefinition(SysMLv2Parser.ViewDefinitionContext context) + { + var name = GetDeclaredName(context.definitionDeclaration()?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + var supertypeNames = GetSubclassificationSupertypes( + context.definitionDeclaration()?.subclassificationPart()); + + return new SysmlViewNode + { + Name = name, + QualifiedName = qualifiedName, + SupertypeNames = supertypeNames, + }; + } + + /// + public override SysmlNode? VisitViewpointDefinition(SysMLv2Parser.ViewpointDefinitionContext context) + { + var name = GetDeclaredName(context.definitionDeclaration()?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + var supertypeNames = GetSubclassificationSupertypes( + context.definitionDeclaration()?.subclassificationPart()); + + return new SysmlViewpointNode + { + Name = name, + QualifiedName = qualifiedName, + SupertypeNames = supertypeNames, + }; + } + + /// + /// Builds a definition AST node from the given . + /// + private SysmlDefinitionNode? BuildDefinitionNode( + SysMLv2Parser.DefinitionContext? definition, + string keyword) + { + if (definition is null) + { + return null; + } + + var decl = definition.definitionDeclaration(); + var name = GetDeclaredName(decl?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + + // Collect supertype names from subclassificationPart + var supertypeNames = GetSubclassificationSupertypes(decl?.subclassificationPart()); + + // Collect body children + _namespaceStack.Add(name); + var children = CollectDefinitionBodyItems(definition.definitionBody()?.definitionBodyItem() ?? []); + _namespaceStack.RemoveAt(_namespaceStack.Count - 1); + + return new SysmlDefinitionNode + { + Name = name, + QualifiedName = qualifiedName, + DefinitionKeyword = keyword, + SupertypeNames = supertypeNames, + Children = children, + }; + } + + /// + /// Extracts supertype qualified names from a . + /// + private static IReadOnlyList GetSubclassificationSupertypes( + SysMLv2Parser.SubclassificationPartContext? part) + { + if (part is null) + { + return Array.Empty(); + } + + var names = new List(); + foreach (var owned in part.ownedSubclassification()) + { + var qn = owned.qualifiedName()?.GetText(); + if (qn is { Length: > 0 }) + { + names.Add(qn); + } + } + + return names; + } + + /// + /// Extracts the declared name from an . + /// + /// + /// The grammar has three alternatives: + /// + /// Alt 1: <shortName> declaredName → 2 name() children; declared name is name(1). + /// Alt 2: <shortName> → 1 name() child with LT present; no declared name. + /// Alt 3: declaredName → 1 name() child without LT; declared name is name(0). + /// + /// + private static string? GetDeclaredName(SysMLv2Parser.IdentificationContext? identification) + { + if (identification is null) + { + return null; + } + + var names = identification.name(); + if (names.Length == 0) + { + return null; + } + + // Alt 1 or Alt 2: there is a '<' token + if (identification.LT() != null) + { + // Alt 1: < shortName > declaredName → 2 names; declared name is names[1] + // Alt 2: < shortName > → 1 name; no declared name + return names.Length >= 2 ? names[1].GetText() : null; + } + + // Alt 3: just the declared name + return names[0].GetText(); + } + + /// + /// Collects child nodes from an array of . + /// + private IReadOnlyList CollectBodyElements( + IEnumerable elements) + { + var result = new List(); + foreach (var element in elements) + { + var node = Visit(element); + if (node is not null) + { + result.Add(node); + } + } + + return result; + } + + /// + /// Collects child nodes from an array of . + /// + private IReadOnlyList CollectDefinitionBodyItems( + IEnumerable items) + { + var result = new List(); + foreach (var item in items) + { + var node = Visit(item); + if (node is not null) + { + result.Add(node); + } + } + + return result; + } +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs new file mode 100644 index 00000000..a870e88e --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs @@ -0,0 +1,126 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Parser; + +namespace DemaConsulting.SysML2Tools.Semantic.Internal; + +/// +/// Resolves qualified name references and import chains across all loaded files. +/// +internal sealed class ReferenceResolver +{ + private readonly SymbolTable _symbolTable; + private readonly List _diagnostics; + + public ReferenceResolver(SymbolTable symbolTable, List diagnostics) + { + _symbolTable = symbolTable; + _diagnostics = diagnostics; + } + + public void ResolveAll(IEnumerable<(string FilePath, SysmlNode? Root)> fileRoots) + { + // Build import graph first + var fileRootsList = fileRoots.ToList(); + var importGraph = BuildImportGraph(fileRootsList); + + // Detect circular imports + DetectCircularImports(importGraph); + + // Resolve references in each file + foreach (var (filePath, root) in fileRootsList.Where(r => r.Root is not null)) + { + ResolveNode(root!, filePath, new HashSet()); + } + } + + private static Dictionary> BuildImportGraph( + IEnumerable<(string FilePath, SysmlNode? Root)> fileRoots) + { + var graph = new Dictionary>(StringComparer.Ordinal); + foreach (var (filePath, root) in fileRoots.Where(r => r.Root is not null)) + { + var imports = new HashSet(StringComparer.Ordinal); + CollectImports(root!, imports); + graph[filePath] = imports; + } + + return graph; + } + + private static void CollectImports(SysmlNode node, HashSet imports) + { + if (node is SysmlImportNode importNode) + { + imports.Add(importNode.ImportedNamespace); + } + + foreach (var child in node.Children) + { + CollectImports(child, imports); + } + } + + private void DetectCircularImports(Dictionary> importGraph) + { + var visited = new HashSet(StringComparer.Ordinal); + var inStack = new HashSet(StringComparer.Ordinal); + + foreach (var node in importGraph.Keys.Where(n => !visited.Contains(n))) + { + DetectCycles(node, importGraph, visited, inStack); + } + } + + private void DetectCycles( + string current, + Dictionary> graph, + HashSet visited, + HashSet inStack) + { + visited.Add(current); + inStack.Add(current); + + if (graph.TryGetValue(current, out var neighbors)) + { + foreach (var neighbor in neighbors) + { + if (!visited.Contains(neighbor)) + { + DetectCycles(neighbor, graph, visited, inStack); + } + else if (inStack.Contains(neighbor)) + { + _diagnostics.Add(new SysmlDiagnostic( + current, + 0, 0, + DiagnosticSeverity.Warning, + $"Circular import detected: '{current}' imports '{neighbor}'")); + } + } + } + + inStack.Remove(current); + } + + private void ResolveNode(SysmlNode node, string filePath, HashSet resolvedInFile) + { + // Resolve supertype names + foreach (var supertypeName in node.SupertypeNames.Where( + n => !_symbolTable.Contains(n) && !resolvedInFile.Contains(n))) + { + resolvedInFile.Add(supertypeName); + _diagnostics.Add(new SysmlDiagnostic( + filePath, + 0, 0, + DiagnosticSeverity.Warning, + $"Unresolved reference: '{supertypeName}'")); + } + + foreach (var child in node.Children) + { + ResolveNode(child, filePath, resolvedInFile); + } + } +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs new file mode 100644 index 00000000..ac2af8e7 --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs @@ -0,0 +1,63 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Parser; + +namespace DemaConsulting.SysML2Tools.Semantic.Internal; + +/// +/// Walks specialization chains to validate supertype references and detect cycles. +/// +internal sealed class SupertypeWalker +{ + private readonly SymbolTable _symbolTable; + private readonly List _diagnostics; + + public SupertypeWalker(SymbolTable symbolTable, List diagnostics) + { + _symbolTable = symbolTable; + _diagnostics = diagnostics; + } + + public void WalkAll() + { + var visited = new HashSet(StringComparer.Ordinal); + + foreach (var (name, node) in _symbolTable.Symbols) + { + if (!visited.Contains(name)) + { + WalkNode(node, name, new HashSet(StringComparer.Ordinal), visited); + } + } + } + + private void WalkNode( + SysmlNode node, + string qualifiedName, + HashSet chainVisited, + HashSet globalVisited) + { + globalVisited.Add(qualifiedName); + chainVisited.Add(qualifiedName); + + foreach (var supertypeName in node.SupertypeNames) + { + if (chainVisited.Contains(supertypeName)) + { + _diagnostics.Add(new SysmlDiagnostic( + string.Empty, + 0, 0, + DiagnosticSeverity.Warning, + $"Cyclic specialization detected: '{qualifiedName}' specializes '{supertypeName}'")); + continue; + } + + var supertypeNode = _symbolTable.Lookup(supertypeName); + if (supertypeNode is not null && !globalVisited.Contains(supertypeName)) + { + WalkNode(supertypeNode, supertypeName, new HashSet(chainVisited), globalVisited); + } + } + } +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs new file mode 100644 index 00000000..2dc4d39c --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs @@ -0,0 +1,54 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +namespace DemaConsulting.SysML2Tools.Semantic.Internal; + +/// +/// Registry mapping fully-qualified SysML/KerML names to their declaration nodes. +/// +internal sealed class SymbolTable +{ + private readonly Dictionary _symbols = new(StringComparer.Ordinal); + + /// + /// Gets the registered symbols as a read-only dictionary. + /// + public IReadOnlyDictionary Symbols => _symbols; + + /// + /// Registers all named nodes from the given AST root into the symbol table. + /// + public void RegisterAll(SysmlNode? root) + { + if (root is null) + { + return; + } + + RegisterNode(root); + } + + private void RegisterNode(SysmlNode node) + { + if (node.QualifiedName is { Length: > 0 }) + { + _symbols.TryAdd(node.QualifiedName, node); + } + + foreach (var child in node.Children) + { + RegisterNode(child); + } + } + + /// + /// Looks up a symbol by its fully-qualified name. + /// + public SysmlNode? Lookup(string qualifiedName) => + _symbols.TryGetValue(qualifiedName, out var node) ? node : null; + + /// + /// Returns true if the symbol table contains the given qualified name. + /// + public bool Contains(string qualifiedName) => _symbols.ContainsKey(qualifiedName); +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SysmlNode.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SysmlNode.cs new file mode 100644 index 00000000..74cea61c --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SysmlNode.cs @@ -0,0 +1,90 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +namespace DemaConsulting.SysML2Tools.Semantic.Internal; + +/// +/// Base class for all SysML/KerML AST nodes. +/// +internal abstract class SysmlNode +{ + /// + /// Gets the simple (unqualified) name of this element, or null if anonymous. + /// + public string? Name { get; init; } + + /// + /// Gets the fully-qualified name of this element in its containing namespace. + /// + public string? QualifiedName { get; init; } + + /// + /// Gets the children of this node. + /// + public IReadOnlyList Children { get; init; } = Array.Empty(); + + /// + /// Gets the supertype names referenced by specialization. + /// + public IReadOnlyList SupertypeNames { get; init; } = Array.Empty(); + + /// + /// Gets the imported namespace names. + /// + public IReadOnlyList ImportedNames { get; init; } = Array.Empty(); +} + +/// +/// AST node representing a SysML/KerML package or namespace. +/// +internal sealed class SysmlPackageNode : SysmlNode +{ +} + +/// +/// AST node representing a definition element (part def, attribute def, etc.). +/// +internal sealed class SysmlDefinitionNode : SysmlNode +{ + /// + /// Gets the definition keyword (e.g., "part def", "attribute def"). + /// + public string DefinitionKeyword { get; init; } = string.Empty; +} + +/// +/// AST node representing a usage/feature element (part, attribute, etc.). +/// +internal sealed class SysmlFeatureNode : SysmlNode +{ +} + +/// +/// AST node representing an import declaration. +/// +internal sealed class SysmlImportNode : SysmlNode +{ + /// + /// Gets the imported namespace or qualified name. + /// + public string ImportedNamespace { get; init; } = string.Empty; + + /// + /// Gets a value indicating whether this is a wildcard import (::*). + /// + public bool IsWildcard { get; init; } +} + +/// +/// AST node representing a view definition. +/// +internal sealed class SysmlViewNode : SysmlNode +{ +} + +/// +/// AST node representing a viewpoint definition. +/// +internal sealed class SysmlViewpointNode : SysmlNode +{ +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/SysmlLoadResult.cs b/src/DemaConsulting.SysML2Tools/Semantic/SysmlLoadResult.cs new file mode 100644 index 00000000..a82da942 --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/SysmlLoadResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Parser; + +namespace DemaConsulting.SysML2Tools.Semantic; + +/// +/// Result of loading a SysML/KerML workspace including semantic analysis. +/// +/// The semantic workspace, or null if loading failed entirely. +/// All diagnostics (parse errors, semantic warnings) from the load operation. +public sealed record SysmlLoadResult( + SysmlWorkspace? Workspace, + IReadOnlyList Diagnostics) +{ + /// + /// Gets a value indicating whether the result contains any error-level diagnostics. + /// + public bool HasErrors => Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/SysmlWorkspace.cs b/src/DemaConsulting.SysML2Tools/Semantic/SysmlWorkspace.cs new file mode 100644 index 00000000..c1fc968a --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/SysmlWorkspace.cs @@ -0,0 +1,21 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +namespace DemaConsulting.SysML2Tools.Semantic; + +/// +/// Represents a fully-loaded and semantically-resolved SysML/KerML workspace. +/// +public sealed class SysmlWorkspace +{ + /// + /// Gets the list of loaded source file paths (virtual paths for stdlib, real paths for user files). + /// + public IReadOnlyList Files { get; init; } = Array.Empty(); + + /// + /// Gets the qualified-name registry mapping fully-qualified names to their declaration nodes. + /// + public IReadOnlyDictionary Declarations { get; init; } = + new Dictionary(); +} diff --git a/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs b/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs new file mode 100644 index 00000000..d3736a25 --- /dev/null +++ b/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs @@ -0,0 +1,160 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Parser; +using DemaConsulting.SysML2Tools.Semantic.Internal; + +namespace DemaConsulting.SysML2Tools.Semantic; + +/// +/// Loads SysML/KerML files into a semantic workspace with symbol registration and reference resolution. +/// +public static class WorkspaceLoader +{ + /// + /// Loads the given SysML/KerML source files into a semantic workspace. + /// + /// + /// Paths to the SysML/KerML source files to load. May be empty, in which case only + /// stdlib declarations are available. + /// + /// + /// A containing the workspace and all diagnostics. + /// + public static async Task LoadAsync(IEnumerable filePaths) + { + var allDiagnostics = new List(); + var symbolTable = new SymbolTable(); + var loadedFiles = new List(); + + // Load and register stdlib (async, cached) + var stdlibResult = await GetStdlibAstAsync().ConfigureAwait(false); + allDiagnostics.AddRange(stdlibResult.Diagnostics); + foreach (var (virtualPath, root) in stdlibResult.AstRoots) + { + loadedFiles.Add(virtualPath); + symbolTable.RegisterAll(root); + } + + // Parse user files in parallel + var paths = filePaths.ToList(); + var parseTasks = paths.Select(ParseUserFileAsync).ToList(); + var parseResults = await Task.WhenAll(parseTasks).ConfigureAwait(false); + + // Register user file ASTs + var userAstRoots = new List<(string Path, SysmlNode? Root)>(); + foreach (var (path, root, diagnostics) in parseResults) + { + loadedFiles.Add(path); + allDiagnostics.AddRange(diagnostics); + symbolTable.RegisterAll(root); + userAstRoots.Add((path, root)); + } + + // Run reference resolution and supertype walking + var allAstRoots = stdlibResult.AstRoots + .Select(r => (r.VirtualPath, r.Root)) + .Concat(userAstRoots.Select(r => (r.Path, r.Root))) + .ToList(); + + var resolver = new ReferenceResolver(symbolTable, allDiagnostics); + resolver.ResolveAll(allAstRoots); + + var supertypeWalker = new SupertypeWalker(symbolTable, allDiagnostics); + supertypeWalker.WalkAll(); + + var workspace = new SysmlWorkspace + { + Files = loadedFiles, + Declarations = symbolTable.Symbols.ToDictionary( + kvp => kvp.Key, + kvp => (object)kvp.Value, + StringComparer.Ordinal), + }; + + return new SysmlLoadResult(workspace, allDiagnostics); + } + + // Stdlib AST cache + private static readonly Lazy> StdlibSemanticTask = + new(() => Task.Run(BuildStdlibSemanticAsync)); + + private static Task GetStdlibAstAsync() => StdlibSemanticTask.Value; + + private static async Task BuildStdlibSemanticAsync() + { + var diagnostics = new List(); + var astRoots = new List<(string VirtualPath, SysmlNode? Root)>(); + + // Access the stdlib embedded resources + var assembly = typeof(WorkspaceParser).Assembly; + var resourcePrefix = "DemaConsulting.SysML2Tools.Stdlib."; + var resources = assembly.GetManifestResourceNames() + .Where(n => n.StartsWith(resourcePrefix, StringComparison.Ordinal)) + .Where(n => n.EndsWith(".sysml", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".kerml", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + var builder = new AstBuilder(); + foreach (var resource in resources) + { + using var stream = assembly.GetManifestResourceStream(resource); + if (stream is null) + { + continue; + } + + using var reader = new StreamReader(stream); + var content = await reader.ReadToEndAsync().ConfigureAwait(false); + + var fileDiagnostics = new List(); + var cst = WorkspaceParser.ParseSourceToCst(resource, content, fileDiagnostics); + + // KerML files may produce parse errors with the SysML v2 grammar — downgrade to Warning + if (resource.EndsWith(".kerml", StringComparison.OrdinalIgnoreCase)) + { + foreach (var d in fileDiagnostics) + { + diagnostics.Add(d.Severity == DiagnosticSeverity.Error + ? d with { Severity = DiagnosticSeverity.Warning } + : d); + } + } + else + { + diagnostics.AddRange(fileDiagnostics); + } + + var root = builder.Build(cst); + astRoots.Add((resource, root)); + } + + return new StdlibSemanticResult(astRoots, diagnostics); + } + + private static async Task<(string Path, SysmlNode? Root, List Diagnostics)> ParseUserFileAsync( + string filePath) + { + string content; + try + { + content = await File.ReadAllTextAsync(filePath).ConfigureAwait(false); + } + catch (Exception ex) + { + var diag = new SysmlDiagnostic(filePath, 0, 0, DiagnosticSeverity.Error, + $"Failed to read file: {ex.Message}"); + return (filePath, null, [diag]); + } + + var diagnostics = new List(); + var cst = WorkspaceParser.ParseSourceToCst(filePath, content, diagnostics); + var builder = new AstBuilder(); + var root = builder.Build(cst); + return (filePath, root, diagnostics); + } + + private sealed record StdlibSemanticResult( + IReadOnlyList<(string VirtualPath, SysmlNode? Root)> AstRoots, + IReadOnlyList Diagnostics); +} diff --git a/test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs index c1cd2113..6785f300 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs @@ -102,13 +102,13 @@ public async Task Parse_StdlibOnly_NoErrors() /// /// Stdlib is always loaded — even when no user files are passed. - /// Phase 1 loads the 58 SysML stdlib files; KerML files are embedded but parsed in Phase 2. + /// Phase 2 loads all 94 stdlib files (58 .sysml + 36 .kerml). /// [Fact] public async Task Parse_FilesCount_IncludesStdlib() { var result = await WorkspaceParser.ParseAsync([]); - Assert.True(result.Files.Count >= 58, - $"Expected at least 58 stdlib files, got {result.Files.Count}"); + Assert.True(result.Files.Count >= 94, + $"Expected at least 94 stdlib files, got {result.Files.Count}"); } } diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs new file mode 100644 index 00000000..c3430010 --- /dev/null +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Semantic; + +namespace DemaConsulting.SysML2Tools.Tests.Semantic; + +/// +/// Level 10 gate: all OMG example models and software-structure.sysml resolve with zero errors. +/// +public sealed class SemanticOmgModelsTests +{ + /// + /// Finds the test/SysMLModels directory relative to the test assembly. + /// + private static string? FindSysMLModelsRoot() + { + var dir = AppContext.BaseDirectory; + while (dir is not null) + { + var candidate = Path.Combine(dir, "test", "SysMLModels"); + if (Directory.Exists(candidate)) + { + return candidate; + } + dir = Directory.GetParent(dir)?.FullName; + } + + return null; + } + + /// + /// All OMG reference model files (including software-structure.sysml) should load with zero errors. + /// + [Fact] + public async Task SemanticOmgModels_AllModels_ResolveWithZeroErrors() + { + // Arrange + var modelsRoot = FindSysMLModelsRoot(); + if (modelsRoot is null) + { + return; + } + + var sysmlFiles = Directory.GetFiles(modelsRoot, "*.sysml", SearchOption.AllDirectories); + if (sysmlFiles.Length == 0) + { + return; + } + + // Act + var result = await WorkspaceLoader.LoadAsync(sysmlFiles); + + // Assert — no errors + var errors = result.Diagnostics + .Where(d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Error) + .ToList(); + + if (errors.Count > 0) + { + var messages = string.Join("\n", errors.Select(e => $" {e.FilePath}({e.Line},{e.Column}): {e.Message}")); + Assert.Fail($"Expected zero errors but got {errors.Count}:\n{messages}"); + } + } +} diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs new file mode 100644 index 00000000..bd2ee0e0 --- /dev/null +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -0,0 +1,254 @@ +// Copyright (c) DemaConsulting. All rights reserved. +// Licensed under the MIT License. + +using DemaConsulting.SysML2Tools.Semantic; + +namespace DemaConsulting.SysML2Tools.Tests.Semantic; + +/// +/// Tests for . +/// +public sealed class WorkspaceLoaderTests +{ + // Level 1: Empty file returns non-null workspace without errors + /// + /// An empty SysML file should produce a non-null workspace with no errors. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace() + { + // Arrange + var tempFile = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(tempFile, string.Empty, TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.False(result.HasErrors); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 2: Single package registers declaration + /// + /// A SysML file with a single package should register the package in the declarations. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, "package Foo {}", TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.True(result.Workspace!.Declarations.ContainsKey("Foo"), + "Expected 'Foo' in declarations"); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 3: Nested packages register qualified names + /// + /// Nested packages should register both the parent and child qualified names. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, "package A { package B {} }", TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.True(result.Workspace!.Declarations.ContainsKey("A"), "Expected 'A'"); + Assert.True(result.Workspace!.Declarations.ContainsKey("A::B"), "Expected 'A::B'"); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 4: Part definition registers declaration + /// + /// A part def inside a package should register its qualified name. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, "package P { part def W {} }", TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.True(result.Workspace!.Declarations.ContainsKey("P::W"), + "Expected 'P::W' in declarations"); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 5: No-files load returns non-null workspace (stdlib only) + /// + /// Loading with no user files should still return a non-null workspace with stdlib declarations. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_NoFiles_ReturnsNonNullWorkspace() + { + // Act + var result = await WorkspaceLoader.LoadAsync([]); + + // Assert + Assert.NotNull(result.Workspace); + // Stdlib has many declarations + Assert.NotEmpty(result.Workspace!.Declarations); + } + + // Level 6: Stdlib declarations are registered + /// + /// The stdlib should contribute declarations to the workspace without errors. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered() + { + // Act + var result = await WorkspaceLoader.LoadAsync([]); + + // Assert + Assert.NotNull(result.Workspace); + // Stdlib should register at least some declarations + Assert.True(result.Workspace!.Declarations.Count > 0, + "Expected stdlib declarations to be registered"); + // No errors from stdlib loading + Assert.False(result.HasErrors); + } + + // Level 7: Specializes chain resolves + /// + /// A derived part def that specializes a base def in the same package should resolve without warnings. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_SpecializesChain_Registered() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def Base {} + part def Derived specializes P::Base {} + } + """, TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.True(result.Workspace!.Declarations.ContainsKey("P::Base"), "Expected 'P::Base'"); + Assert.True(result.Workspace!.Declarations.ContainsKey("P::Derived"), "Expected 'P::Derived'"); + // Supertype should resolve — no unresolved warning for P::Base + Assert.DoesNotContain(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning && + d.Message.Contains("P::Base")); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 8: Unresolved reference produces Warning diagnostic + /// + /// A part def that specializes a non-existent type should produce a Warning diagnostic. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def X specializes NonExistentType {} + } + """, TestContext.Current.CancellationToken); + + // Act + var result = await WorkspaceLoader.LoadAsync([tempFile]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.Contains(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning && + d.Message.Contains("NonExistentType")); + } + finally + { + File.Delete(tempFile); + } + } + + // Level 9: Circular import produces Warning and does not loop infinitely + /// + /// Packages that import each other should produce a Warning and complete in finite time. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop() + { + // Arrange — two files that declare packages importing each other by name + var tempFile1 = Path.GetTempFileName() + ".sysml"; + var tempFile2 = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile1, "package A { import B::*; }", TestContext.Current.CancellationToken); + await File.WriteAllTextAsync(tempFile2, "package B { import A::*; }", TestContext.Current.CancellationToken); + + // Act — must complete in finite time + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + var result = await WorkspaceLoader.LoadAsync([tempFile1, tempFile2]) + .WaitAsync(cts.Token); + + // Assert — circular import warning present + Assert.NotNull(result.Workspace); + Assert.Contains(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning); + } + finally + { + File.Delete(tempFile1); + File.Delete(tempFile2); + } + } +} diff --git a/test/SysMLModels/software-structure.sysml b/test/SysMLModels/software-structure.sysml new file mode 100644 index 00000000..565ceaab --- /dev/null +++ b/test/SysMLModels/software-structure.sysml @@ -0,0 +1,41 @@ +// SysML2Tools software structure model +// Describes the SysML2Tools software components using SysML v2 part definitions. + +package SysML2Tools { + + // Core library subsystems + package Core { + + // Parser subsystem: parses SysML/KerML source files + part def WorkspaceParser { + } + + // Semantic subsystem: builds symbol table and resolves references + part def WorkspaceLoader { + } + + // Internal: builds AST from CST + part def AstBuilder { + } + + // Internal: registers qualified names + part def SymbolTable { + } + + // Internal: resolves references and imports + part def ReferenceResolver { + } + + // Internal: walks specialization chains + part def SupertypeWalker { + } + } + + // Tool subsystem: CLI commands + package Tool { + + // Lint command: validates SysML/KerML files + part def LintCommand { + } + } +} From 7413365980acef0b905d2b6232f161029b3c7e98 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 10:32:22 -0400 Subject: [PATCH 2/6] docs: fix quality issues in Phase 2 semantic model documentation and code --- .cspell.yaml | 1 + docs/design/sysml2-tools-core/semantic.md | 14 ++-- .../sysml2-tools-core/semantic/internal.md | 39 +++++++++-- .../semantic/internal/ast-builder.md | 33 +++++++--- .../semantic/internal/reference-resolver.md | 25 +++++-- .../semantic/internal/supertype-walker.md | 25 +++++-- .../semantic/internal/symbol-table.md | 27 ++++++-- .../semantic/internal/sysml-node.md | 37 +++++++++-- .../semantic/workspace-loader.md | 66 +++++++++++++++---- .../sysml2-tools-core/semantic.md | 10 +-- .../sysml2-tools-core/semantic/internal.md | 47 +++++++++---- .../semantic/internal/ast-builder.md | 34 +++++++++- .../semantic/internal/reference-resolver.md | 32 ++++++++- .../semantic/internal/supertype-walker.md | 29 +++++++- .../semantic/internal/symbol-table.md | 33 +++++++++- .../semantic/internal/sysml-node.md | 34 +++++++++- .../semantic/workspace-loader.md | 37 +++++++++-- .../Semantic/Internal/AstBuilder.cs | 3 + .../Semantic/Internal/ReferenceResolver.cs | 31 +++++++++ .../Semantic/Internal/SupertypeWalker.cs | 19 ++++++ .../Semantic/Internal/SymbolTable.cs | 6 ++ .../Semantic/WorkspaceLoader.cs | 20 +++++- 22 files changed, 521 insertions(+), 81 deletions(-) diff --git a/.cspell.yaml b/.cspell.yaml index 155277a0..9c397d8c 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -56,6 +56,7 @@ words: - sysml2tools - kerml - KerML + - supertypes - daltskin - interp - Dlanguage diff --git a/docs/design/sysml2-tools-core/semantic.md b/docs/design/sysml2-tools-core/semantic.md index b4b59c62..1487766d 100644 --- a/docs/design/sysml2-tools-core/semantic.md +++ b/docs/design/sysml2-tools-core/semantic.md @@ -1,12 +1,12 @@ -# DemaConsulting.SysML2Tools — Semantic Subsystem +## DemaConsulting.SysML2Tools — Semantic Subsystem -## Overview +### Overview The Semantic subsystem builds a semantic workspace from the parsed SysML/KerML source files. It operates as a second layer above the Parser subsystem, consuming ANTLR4 CSTs produced by `WorkspaceParser` and transforming them into a structured symbol table with resolved references. -## Architecture +### Architecture The Semantic subsystem contains one public unit (`WorkspaceLoader`) and an internal subsystem (`Internal`) containing `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. @@ -29,7 +29,7 @@ flowchart TD AstBuilder --> SymbolTable ``` -## External Interfaces +### External Interfaces **WorkspaceLoader.LoadAsync**: Loads the embedded stdlib plus every file in the provided collection asynchronously. @@ -56,11 +56,11 @@ collection asynchronously. - *Contract*: Exposes `IReadOnlyList Files` and `IReadOnlyDictionary Declarations` mapping qualified names to declaration nodes. -## Data Flow +### Data Flow 1. `WorkspaceLoader.LoadAsync` awaits the shared `Lazy>` stdlib result. On first call the factory fires `Task.Run(BuildStdlibSemanticAsync)`, which reads each stdlib - resource stream, calls `WorkspaceParser.ParseSourceToCst`, downgradeskerml parse errors to + resource stream, calls `WorkspaceParser.ParseSourceToCst`, downgrades KerML parse errors to Warnings, builds an AST via `AstBuilder`, and registers it into a `SymbolTable`. 2. Concurrently, all caller-supplied file paths are dispatched via `Task.WhenAll`, each parsing its content via `WorkspaceParser.ParseSourceToCst`, building an AST, and registering into @@ -73,7 +73,7 @@ collection asynchronously. 5. A `SysmlWorkspace` is constructed from the loaded file list and symbol table, and returned in a `SysmlLoadResult`. -## Design Constraints +### Design Constraints - KerML stdlib files are parsed with the SysML v2 grammar; any parse errors are downgraded to Warnings since the grammar does not fully support KerML-specific syntax. diff --git a/docs/design/sysml2-tools-core/semantic/internal.md b/docs/design/sysml2-tools-core/semantic/internal.md index 9ed02759..afe4a589 100644 --- a/docs/design/sysml2-tools-core/semantic/internal.md +++ b/docs/design/sysml2-tools-core/semantic/internal.md @@ -1,11 +1,42 @@ -# Semantic Internal Subsystem +### Semantic Internal Subsystem -## Overview +#### Overview The Semantic Internal subsystem provides the implementation details of the semantic loading pipeline. It contains four units: `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. -## Units +#### Interfaces + +**`AstBuilder.Build(RootNamespaceContext)`**: Transforms the ANTLR4 CST root into a typed AST root. + +- *Type*: In-process .NET internal method. +- *Role*: Provider. +- *Contract*: Accepts a `SysMLv2Parser.RootNamespaceContext`; returns `SysmlPackageNode?` — + the root package node, or `null` if the root contains no named elements. + +**`SymbolTable.RegisterAll(SysmlNode?)`**: Registers all named nodes from an AST root. + +- *Type*: In-process .NET internal method. +- *Role*: Provider. +- *Contract*: Traverses the AST depth-first and inserts each non-null `QualifiedName` into + the symbol dictionary. Duplicate names are silently ignored. + +**`ReferenceResolver.ResolveAll(IEnumerable<(string, SysmlNode?)>)`**: Runs import-cycle detection +and supertype reference resolution over all loaded file roots. + +- *Type*: In-process .NET internal method. +- *Role*: Provider. +- *Contract*: Accepts a list of `(FilePath, Root)` pairs; emits Warning diagnostics for + unresolved supertype names and for circular import chains. + +**`SupertypeWalker.WalkAll()`**: Traverses all specialization chains to detect cyclic specialization. + +- *Type*: In-process .NET internal method. +- *Role*: Provider. +- *Contract*: Iterates all symbols in the `SymbolTable`; emits Warning diagnostics for any + cycle detected. + +#### Design | Unit | Responsibility | | --- | --- | @@ -14,7 +45,7 @@ It contains four units: `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `S | `ReferenceResolver` | Checks supertype references; detects circular import chains | | `SupertypeWalker` | Walks specialization chains; detects cyclic specialization | -## Interaction Model +Interaction sequence: 1. `WorkspaceLoader` creates one `AstBuilder` per file and calls `Build(rootNamespaceContext)`. 2. The returned `SysmlPackageNode` root is passed to `SymbolTable.RegisterAll`. diff --git a/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md b/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md index e39b0bac..79790a5d 100644 --- a/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md +++ b/docs/design/sysml2-tools-core/semantic/internal/ast-builder.md @@ -1,17 +1,17 @@ -# AstBuilder +#### AstBuilder -## Overview +##### Overview `AstBuilder` extends `SysMLv2ParserBaseVisitor` and builds a typed AST from the ANTLR4 CST produced by `SysMLv2Parser`. -## Namespace Stack +##### Namespace Stack A `List _namespaceStack` tracks the current nesting path. When entering a named package or definition, the name is pushed; it is popped before returning. `QualifyName(name)` joins the stack with `::` to form the fully-qualified name. -## Key Visit Methods +##### Key Methods | Method | Input Context | Output | | --- | --- | --- | @@ -24,8 +24,6 @@ stack with `::` to form the fully-qualified name. | `VisitViewDefinition` | `ViewDefinitionContext` | `SysmlViewNode` | | `VisitViewpointDefinition` | `ViewpointDefinitionContext` | `SysmlViewpointNode` | -## Name Extraction - `GetDeclaredName(IdentificationContext)` handles the three grammar alternatives: - `< shortName > declaredName` (alt 1): returns `name(1).GetText()`. @@ -34,8 +32,27 @@ stack with `::` to form the fully-qualified name. Elements with no declared name are treated as anonymous and are not registered in the symbol table. -## Supertype Extraction - `GetSubclassificationSupertypes(SubclassificationPartContext)` iterates `ownedSubclassification()` entries and calls `qualifiedName().GetText()` on each to produce the supertype name list. + +##### Error Handling + +Anonymous elements (null declared names) are silently skipped — visitor methods return `null` +and the caller discards the result. `BuildDefinitionNode` returns `null` when passed a `null` +`DefinitionContext`. No exceptions are thrown; malformed CST nodes produce `null` or empty +results without propagating failures. + +##### Dependencies + +- `SysMLv2ParserBaseVisitor` (ANTLR4 runtime) — base class providing visitor + dispatch over the CST. +- `SysMLv2Parser` — provides all CST context types consumed by the visitor methods. +- `SysmlNode` hierarchy (`SysmlPackageNode`, `SysmlDefinitionNode`, `SysmlViewNode`, + `SysmlViewpointNode`) — AST node types constructed by the visitor. + +##### Callers + +`WorkspaceLoader.BuildStdlibSemanticAsync` and `WorkspaceLoader.ParseUserFileAsync` each create +a fresh `AstBuilder` instance and call `Build(RootNamespaceContext)` on the CST root produced +by `WorkspaceParser.ParseSourceToCst`. diff --git a/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md b/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md index e35db2b8..83f50188 100644 --- a/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md +++ b/docs/design/sysml2-tools-core/semantic/internal/reference-resolver.md @@ -1,6 +1,6 @@ -# ReferenceResolver +#### ReferenceResolver -## Overview +##### Overview `ReferenceResolver` performs two analyses over the loaded files: @@ -9,7 +9,7 @@ 2. **Supertype reference resolution** — checks each `SupertypeName` in all AST nodes against the symbol table and emits a Warning for any name not found. -## Import Graph +##### Import Graph `BuildImportGraph` iterates all file roots, collecting `SysmlImportNode.ImportedNamespace` values into a `HashSet` per file. The result is a `Dictionary>` @@ -19,8 +19,25 @@ from file path to imported names. node in the current DFS stack is encountered again. The Warning message names the file and the imported namespace that completes the cycle. -## Supertype Resolution +##### Supertype Resolution `ResolveNode` traverses each AST node's `SupertypeNames`. For each name not found in the symbol table (and not already reported in this file), a Warning diagnostic is emitted. The `resolvedInFile` set prevents duplicate warnings for the same name within a file. + +##### Error Handling + +All issues are reported as `Warning`-severity `SysmlDiagnostic` entries added to the shared +`_diagnostics` list. No exceptions are thrown; the resolver completes even when cycles or +unresolved names are present. + +##### Dependencies + +- `SymbolTable` — `Contains` method used to check whether a supertype name is registered. +- `SysmlNode` hierarchy — traversed to collect `SupertypeNames` and `ImportedNames`. +- `SysmlDiagnostic`, `DiagnosticSeverity` — used to construct and emit Warning diagnostics. + +##### Callers + +`WorkspaceLoader.LoadAsync` constructs a `ReferenceResolver` with the shared `SymbolTable` and +diagnostics list, then calls `ResolveAll` with all stdlib and user file AST roots. diff --git a/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md b/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md index f927fc7a..54980a9d 100644 --- a/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md +++ b/docs/design/sysml2-tools-core/semantic/internal/supertype-walker.md @@ -1,11 +1,11 @@ -# SupertypeWalker +#### SupertypeWalker -## Overview +##### Overview `SupertypeWalker` traverses the specialization chains of all symbols registered in the `SymbolTable` to detect cyclic specialization (e.g., A specializes B, B specializes A). -## Algorithm +##### Algorithm `WalkAll` iterates over all symbols and for each unvisited symbol calls `WalkNode`. `WalkNode` maintains two sets: @@ -20,7 +20,24 @@ For each supertype name in the current node: - If the supertype is registered in the symbol table and not yet globally visited, `WalkNode` is called recursively with a copy of `chainVisited`. -## Warning Format +##### Warning Format Cyclic specialization warnings use `DiagnosticSeverity.Warning` with an empty `FilePath` (since the cycle spans multiple potential source files). + +##### Error Handling + +Cyclic specialization is detected and reported as a `Warning`-severity `SysmlDiagnostic`. +No exceptions are thrown. Supertype names not present in the symbol table are silently skipped +(reference-resolution warnings are already emitted by `ReferenceResolver`). + +##### Dependencies + +- `SymbolTable` — `Symbols` property (all registered names), `Lookup` (supertype node retrieval). +- `SysmlNode` — `SupertypeNames` property traversed on each node. +- `SysmlDiagnostic`, `DiagnosticSeverity` — used to construct and emit Warning diagnostics. + +##### Callers + +`WorkspaceLoader.LoadAsync` constructs a `SupertypeWalker` with the shared `SymbolTable` and +diagnostics list, then calls `WalkAll` after `ReferenceResolver.ResolveAll` completes. diff --git a/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md b/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md index 42261426..84846fe3 100644 --- a/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md +++ b/docs/design/sysml2-tools-core/semantic/internal/symbol-table.md @@ -1,22 +1,39 @@ -# SymbolTable +#### SymbolTable -## Overview +##### Overview `SymbolTable` is a registry mapping fully-qualified SysML/KerML names to their `SysmlNode` declaration nodes. It is populated by calling `RegisterAll` on each AST root after parsing. -## Algorithm +##### Algorithm `RegisterAll(SysmlNode? root)` performs a depth-first traversal of the AST. For each node with a non-null, non-empty `QualifiedName`, it calls `_symbols.TryAdd(QualifiedName, node)`. `TryAdd` is used (not direct assignment) to silently ignore duplicate declarations. -## Lookup +##### Lookup `Lookup(string qualifiedName)` returns the registered node, or null if not found. `Contains(string qualifiedName)` returns a boolean without allocating a node reference. -## Thread Safety +##### Thread Safety `SymbolTable` is not thread-safe. In `WorkspaceLoader`, all `RegisterAll` calls occur on a single thread after the parallel parse tasks complete. + +##### Error Handling + +`RegisterAll(null)` is a no-op; no exception is thrown. Duplicate qualified names are silently +ignored via `TryAdd` — the first registration wins. + +##### Dependencies + +- `SysmlNode` hierarchy — the node types stored in the registry. + +##### Callers + +- `WorkspaceLoader.LoadAsync` — calls `RegisterAll` once per stdlib root and once per user file + root; reads `Symbols` to build the final `SysmlWorkspace.Declarations`. +- `ReferenceResolver` — calls `Contains` to check supertype name resolution. +- `SupertypeWalker` — reads `Symbols` to iterate all registered names; calls `Lookup` to + retrieve supertype nodes for chain traversal. diff --git a/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md b/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md index 0fd1a301..aa29b01e 100644 --- a/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md +++ b/docs/design/sysml2-tools-core/semantic/internal/sysml-node.md @@ -1,11 +1,11 @@ -# SysmlNode — AST Node Hierarchy +#### SysmlNode — AST Node Hierarchy -## Overview +##### Overview `SysmlNode` is the abstract base class for all SysML/KerML AST nodes. Concrete subtypes represent packages, definitions, features, imports, views, and viewpoints. -## Class Hierarchy +##### Class Hierarchy | Class | Purpose | | --- | --- | @@ -17,7 +17,7 @@ packages, definitions, features, imports, views, and viewpoints. | `SysmlViewNode` | View definition | | `SysmlViewpointNode` | Viewpoint definition | -## Properties +##### Properties All nodes carry: @@ -26,3 +26,32 @@ All nodes carry: - `Children` — nested AST nodes. - `SupertypeNames` — qualified names of supertypes referenced via `specializes` / `:>`. - `ImportedNames` — qualified names of imported namespaces. + +##### Key Methods + +All node types use C# `init`-only properties and are constructed via object initializers. +There are no behavioral methods beyond the inherited `object` members. `SysmlImportNode` adds: + +- `ImportedNamespace` — the target namespace string extracted by `ReferenceResolver`. +- `IsWildcard` — `true` if the import ends with `::*`. + +`SysmlDefinitionNode` adds: + +- `DefinitionKeyword` — the grammar keyword string (e.g., `"part def"`, `"attribute def"`). + +##### Error Handling + +N/A — node types are pure data containers with no logic or validation. Invalid or anonymous +elements are filtered out by `AstBuilder` before a node is constructed. + +##### Dependencies + +- No external dependencies. All node types are internal sealed classes or the abstract base class + within the `Semantic.Internal` namespace. + +##### Callers + +- `AstBuilder` — constructs all concrete node instances during CST visitor traversal. +- `SymbolTable` — traverses the node hierarchy via `Children`; reads `QualifiedName`. +- `ReferenceResolver` — reads `SupertypeNames`, `Children`; checks for `SysmlImportNode`. +- `SupertypeWalker` — reads `SupertypeNames` on each node retrieved from `SymbolTable`. diff --git a/docs/design/sysml2-tools-core/semantic/workspace-loader.md b/docs/design/sysml2-tools-core/semantic/workspace-loader.md index 6d7bfc46..f4b66832 100644 --- a/docs/design/sysml2-tools-core/semantic/workspace-loader.md +++ b/docs/design/sysml2-tools-core/semantic/workspace-loader.md @@ -1,14 +1,28 @@ -# WorkspaceLoader +### WorkspaceLoader -## Overview +#### Purpose `WorkspaceLoader` is the public entry point for the Semantic subsystem. It orchestrates parsing, AST building, symbol registration, reference resolution, and supertype walking into a single `SysmlLoadResult`. -## Methods +#### Data Model -### `LoadAsync(IEnumerable filePaths)` +`WorkspaceLoader` is a static class with no instance state. It holds one private static field: + +- **`StdlibSemanticTask`** (`Lazy>`): caches the stdlib parse and AST + build result. The factory executes once on the first call to `LoadAsync`; all subsequent calls + reuse the same `Task`. This ensures the 94 stdlib files are parsed at most once per process. + +`StdlibSemanticResult` is a private sealed record: + +- **`AstRoots`** (`IReadOnlyList<(string VirtualPath, SysmlNode? Root)>`): AST root per stdlib + resource. +- **`Diagnostics`** (`IReadOnlyList`): Collected diagnostics for all stdlib files. + +#### Key Methods + +##### `LoadAsync(IEnumerable filePaths)` 1. Awaits the cached stdlib semantic result (built once via `BuildStdlibSemanticAsync`). 2. Dispatches all user file paths to `ParseUserFileAsync` in parallel via `Task.WhenAll`. @@ -18,14 +32,42 @@ AST building, symbol registration, reference resolution, and supertype walking i 6. Constructs a `SysmlWorkspace` from the file list and symbol table. 7. Returns a `SysmlLoadResult(workspace, allDiagnostics)`. -## Caching +##### `BuildStdlibSemanticAsync()` + +Enumerates embedded `.sysml` and `.kerml` resources, reads each to a string, calls +`WorkspaceParser.ParseSourceToCst`, downgrades KerML parse errors to Warnings, builds an AST +root via `AstBuilder.Build`, and returns all roots and diagnostics in a `StdlibSemanticResult`. + +##### `ParseUserFileAsync(string filePath)` + +Reads the file via `File.ReadAllTextAsync`, calls `WorkspaceParser.ParseSourceToCst`, builds +an AST root via `AstBuilder.Build`, and returns `(path, root, diagnostics)`. File I/O failures +are caught and converted to a single Error diagnostic. + +##### `GetStdlibAstAsync()` + +Returns `StdlibSemanticTask.Value`, triggering the `Lazy` factory on first access. + +#### Error Handling + +- `ParseUserFileAsync` catches any `Exception` from `File.ReadAllTextAsync` and returns an + `Error`-severity `SysmlDiagnostic` rather than propagating the exception. +- KerML stdlib parse errors in `BuildStdlibSemanticAsync` are downgraded from `Error` to + `Warning` severity because the SysML v2 grammar does not fully cover KerML-specific syntax. +- Reference and cycle errors from `ReferenceResolver` and `SupertypeWalker` are `Warning` + severity and do not cause `HasErrors` to be set on the returned `SysmlLoadResult`. + +#### Dependencies + +- **WorkspaceParser** (`ParseSourceToCst`) — parses source text into an ANTLR4 CST. +- **AstBuilder** (`Build`) — transforms CST roots into typed AST nodes. +- **SymbolTable** (`RegisterAll`, `Symbols`) — stores and exposes all named declarations. +- **ReferenceResolver** (`ResolveAll`) — checks supertype references and import cycles. +- **SupertypeWalker** (`WalkAll`) — detects cyclic specialization chains. -The stdlib semantic result is cached via `static readonly Lazy>`. -This ensures the 94 stdlib files are parsed and their ASTs registered at most once per -application lifetime, regardless of how many concurrent callers invoke `LoadAsync`. +#### Callers -## KerML Handling +`WorkspaceLoader.LoadAsync` is a public static method consumed by: -KerML stdlib parse errors are downgraded to Warnings in `BuildStdlibSemanticAsync`. The -SysML v2 grammar does not fully cover KerML-specific syntax (e.g., `return` keyword), so -parse failures in `.kerml` files are expected and non-fatal. +- Tests in `DemaConsulting.SysML2Tools.Tests` (`WorkspaceLoaderTests`). +- Future rendering and tooling layers that require a populated semantic workspace. diff --git a/docs/verification/sysml2-tools-core/semantic.md b/docs/verification/sysml2-tools-core/semantic.md index a9454dee..ff6dd0da 100644 --- a/docs/verification/sysml2-tools-core/semantic.md +++ b/docs/verification/sysml2-tools-core/semantic.md @@ -1,6 +1,6 @@ -# DemaConsulting.SysML2Tools — Semantic Subsystem Verification +## DemaConsulting.SysML2Tools — Semantic Subsystem Verification -## Verification Approach +### Verification Approach Semantic subsystem verification uses unit tests in `DemaConsulting.SysML2Tools.Tests`. Tests exercise the public `WorkspaceLoader` API and validate that the symbol table is @@ -8,12 +8,12 @@ populated correctly, that reference resolution produces expected diagnostics, an the embedded stdlib loads without errors. The xUnit v3 framework discovers and runs all test methods; results are captured in TRX files consumed by ReqStream. -## Test Environment +### Test Environment Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. Temporary files are created in `Path.GetTempPath()` and cleaned up after each test. -## Acceptance Criteria +### Acceptance Criteria - All unit tests pass with zero failures across all three target frameworks. - The stdlib loads without Error-level diagnostics (KerML parse errors are downgraded to Warnings). @@ -24,7 +24,7 @@ Temporary files are created in `Path.GetTempPath()` and cleaned up after each te - Unresolved supertype references produce Warning diagnostics. - Circular imports produce Warning diagnostics without infinite loops. -## Test Scenarios +### Test Scenarios See *Semantic Verification* for the full list of test scenarios. Primary acceptance evidence is provided by `WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered`, which loads all diff --git a/docs/verification/sysml2-tools-core/semantic/internal.md b/docs/verification/sysml2-tools-core/semantic/internal.md index a751a68a..a2cc47be 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal.md +++ b/docs/verification/sysml2-tools-core/semantic/internal.md @@ -1,17 +1,42 @@ -# Semantic Internal Subsystem Verification +### Semantic Internal Subsystem Verification -## Verification Approach +#### Verification Approach -Internal semantic components are verified indirectly through `WorkspaceLoaderTests`. There are -no direct unit tests for `AstBuilder`, `SymbolTable`, `ReferenceResolver`, or `SupertypeWalker` -as these are internal implementation details. Their behavior is observable through the public -`WorkspaceLoader.LoadAsync` API. +Internal semantic components (`AstBuilder`, `SymbolTable`, `ReferenceResolver`, and +`SupertypeWalker`) are verified indirectly through `WorkspaceLoaderTests`. There are no direct +unit tests for these internal classes because they have no public surface. Their behavior is +observable exclusively through the public `WorkspaceLoader.LoadAsync` API. -## Traceability +#### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services, network access, or additional configuration are required beyond a standard +.NET SDK installation. + +#### Acceptance Criteria + +- All `WorkspaceLoaderTests` pass with zero failures across all three target frameworks. +- `AstBuilder` correctly produces qualified names for nested packages and definitions as + confirmed by tests that check `Declarations` contents. +- `SymbolTable` registers all named nodes from the provided AST roots; duplicate names are + silently ignored without error. +- `ReferenceResolver` emits exactly one Warning per unresolved supertype name per file; it + completes without infinite loops when circular imports are present. +- `SupertypeWalker` emits Warning diagnostics for cyclic specialization chains and terminates + in finite time for any reachable graph. + +#### Test Scenarios + +Traceability to `WorkspaceLoaderTests` test methods: | Internal Component | Verified By | | --- | --- | -| `AstBuilder` | All WorkspaceLoaderTests that check Declarations | -| `SymbolTable` | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | -| `ReferenceResolver` | `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning`, `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | -| `SupertypeWalker` | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | +| `AstBuilder` — name extraction | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | +| `AstBuilder` — qualified names | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | +| `AstBuilder` — supertype extraction | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | +| `SymbolTable` — registration | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | +| `SymbolTable` — lookup | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | +| `ReferenceResolver` — unresolved ref | `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning` | +| `ReferenceResolver` — circular import | `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | +| `SupertypeWalker` — chain walking | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md b/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md index 55ac2438..489ebc74 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md +++ b/docs/verification/sysml2-tools-core/semantic/internal/ast-builder.md @@ -1,3 +1,33 @@ -# AstBuilder Verification +#### AstBuilder Verification -Verified indirectly through WorkspaceLoaderTests. AstBuilder name extraction is confirmed by WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames. Supertype extraction is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered. +##### Verification Approach + +`AstBuilder` is an internal class with no public surface and is verified indirectly through +`WorkspaceLoaderTests`. Tests call `WorkspaceLoader.LoadAsync` with controlled `.sysml` source +files and assert that the returned `SysmlLoadResult.Workspace.Declarations` contains the +expected qualified names, confirming that `AstBuilder` correctly extracted names, built +qualified names from the namespace stack, and extracted supertype names from the CST. + +##### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services or additional configuration are required beyond a standard .NET SDK installation. + +##### Acceptance Criteria + +- A single package `package Foo {}` registers `"Foo"` in `Declarations`. +- Nested packages `package Foo { package Bar {} }` register both `"Foo"` and `"Foo::Bar"`. +- A part definition `part def MyPart {}` inside `Foo` registers `"Foo::MyPart"`. +- An element with only a short name `< shortName >` (no declared name) is not registered. +- A definition with `specializes KnownType` produces a `SupertypeNames` entry that resolves + without a Warning when `KnownType` is registered. + +##### Test Scenarios + +| Scenario | Verified By | +| --- | --- | +| Simple name extraction | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | +| Qualified name from namespace stack | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | +| Definition registration | `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | +| Supertype extraction | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md b/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md index 8ced5a14..7cba5df3 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md +++ b/docs/verification/sysml2-tools-core/semantic/internal/reference-resolver.md @@ -1,3 +1,31 @@ -# ReferenceResolver Verification +#### ReferenceResolver Verification -Verified indirectly through WorkspaceLoaderTests. Unresolved reference detection is confirmed by WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning. Circular import detection is confirmed by WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop. +##### Verification Approach + +`ReferenceResolver` is an internal class verified indirectly through `WorkspaceLoaderTests`. +Tests construct files with deliberate unresolved supertype references and circular import +declarations, then call `WorkspaceLoader.LoadAsync` and assert that the returned diagnostics +contain the expected Warning entries. The absence of an infinite loop is verified implicitly by +test completion within the xUnit v3 timeout. + +##### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services or additional configuration are required beyond a standard .NET SDK installation. + +##### Acceptance Criteria + +- An unresolved supertype name produces exactly one `Warning`-severity diagnostic per file + containing that name. +- A circular import chain between two files produces a `Warning`-severity diagnostic and + `LoadAsync` returns (does not hang). +- A resolved supertype name (registered in `SymbolTable`) produces no Warning diagnostic. + +##### Test Scenarios + +| Scenario | Verified By | +| --- | --- | +| Unresolved supertype reference | `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning` | +| Circular import — terminates | `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | +| Resolved reference — no Warning | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md b/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md index aa752b42..4731c45f 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md +++ b/docs/verification/sysml2-tools-core/semantic/internal/supertype-walker.md @@ -1,3 +1,28 @@ -# SupertypeWalker Verification +#### SupertypeWalker Verification -Verified indirectly through WorkspaceLoaderTests. Specialization chain walking is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered, which asserts that no unresolved warning is produced for a resolved supertype. +##### Verification Approach + +`SupertypeWalker` is an internal class verified indirectly through `WorkspaceLoaderTests`. +Tests construct files with specialization chains (resolved and cyclic) and assert on the +diagnostics returned by `WorkspaceLoader.LoadAsync`. A resolved specialization chain with no +cycles produces no Warning from the walker; a cyclic chain produces a Warning diagnostic. + +##### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services or additional configuration are required beyond a standard .NET SDK installation. + +##### Acceptance Criteria + +- A resolved specialization chain (`A specializes B`, both registered) produces no cyclic + specialization Warning diagnostic. +- A cyclic chain (`A specializes B`, `B specializes A`) produces a `Warning`-severity diagnostic + and `LoadAsync` returns in finite time. +- Symbols with no supertypes are processed without producing any diagnostic. + +##### Test Scenarios + +| Scenario | Verified By | +| --- | --- | +| Resolved chain — no Warning | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md b/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md index 7da56c3b..65360562 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md +++ b/docs/verification/sysml2-tools-core/semantic/internal/symbol-table.md @@ -1,3 +1,32 @@ -# SymbolTable Verification +#### SymbolTable Verification -Verified indirectly through WorkspaceLoaderTests. Registration is confirmed by WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration. Lookup behavior is confirmed by WorkspaceLoader_LoadAsync_SpecializesChain_Registered. +##### Verification Approach + +`SymbolTable` is an internal class verified indirectly through `WorkspaceLoaderTests`. Tests +call `WorkspaceLoader.LoadAsync` with controlled source files and assert on +`SysmlLoadResult.Workspace.Declarations`, confirming that `SymbolTable` correctly registered +named nodes and that `Contains` and `Lookup` return the expected results when queried by +`ReferenceResolver` and `SupertypeWalker`. + +##### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services or additional configuration are required beyond a standard .NET SDK installation. + +##### Acceptance Criteria + +- A registered package name appears in `Declarations` after `LoadAsync` returns. +- Both parent and child qualified names appear in `Declarations` for nested packages. +- Duplicate qualified names are silently ignored; no Error diagnostic is produced. +- `Contains` returns `true` for a registered name used by `ReferenceResolver`, preventing a + spurious unresolved-reference Warning for that name. + +##### Test Scenarios + +| Scenario | Verified By | +| --- | --- | +| Single name registration | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | +| Nested qualified name registration | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | +| Lookup used by ReferenceResolver | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | +| Lookup used by SupertypeWalker | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md b/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md index dec8d1d9..245396f9 100644 --- a/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md +++ b/docs/verification/sysml2-tools-core/semantic/internal/sysml-node.md @@ -1,3 +1,33 @@ -# SysmlNode Verification +#### SysmlNode Verification -Verified indirectly through WorkspaceLoaderTests. The correct construction of SysmlPackageNode and SysmlDefinitionNode is confirmed by WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration and WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition. +##### Verification Approach + +The `SysmlNode` class hierarchy is verified indirectly through `WorkspaceLoaderTests`. These are +pure data container classes constructed by `AstBuilder`; their correctness is confirmed by +asserting that `WorkspaceLoader.LoadAsync` returns the expected qualified names and definition +types in `SysmlLoadResult.Workspace.Declarations`. + +##### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services or additional configuration are required beyond a standard .NET SDK installation. + +##### Acceptance Criteria + +- `SysmlPackageNode` is constructed with the correct `Name` and `QualifiedName` for a + single-package source file; the name appears in `Declarations`. +- `SysmlDefinitionNode` is constructed with the correct `QualifiedName` and `DefinitionKeyword` + for a `part def` declaration; its qualified name appears in `Declarations`. +- `SysmlNode.SupertypeNames` is populated correctly for a definition with a `specializes` + clause; the name is checked by `ReferenceResolver`. +- `SysmlImportNode.ImportedNamespace` is extracted and used by `ReferenceResolver` to build + the import graph. + +##### Test Scenarios + +| Scenario | Verified By | +| --- | --- | +| `SysmlPackageNode` construction | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | +| `SysmlDefinitionNode` construction | `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | +| `SupertypeNames` population | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | diff --git a/docs/verification/sysml2-tools-core/semantic/workspace-loader.md b/docs/verification/sysml2-tools-core/semantic/workspace-loader.md index 7d68e9ff..05350c1f 100644 --- a/docs/verification/sysml2-tools-core/semantic/workspace-loader.md +++ b/docs/verification/sysml2-tools-core/semantic/workspace-loader.md @@ -1,12 +1,37 @@ -# WorkspaceLoader Verification +### WorkspaceLoader Verification -## Verification Approach +#### Verification Approach -`WorkspaceLoader` is verified through 9 progressive-level tests in `WorkspaceLoaderTests`: +`WorkspaceLoader` is verified through 9 progressive-level integration tests in +`WorkspaceLoaderTests`. Tests call `WorkspaceLoader.LoadAsync` with controlled inputs and assert +on the returned `SysmlLoadResult` properties. No mocking of internal components is used; each +test exercises the complete semantic pipeline end-to-end. + +#### Test Environment + +Tests run via `dotnet test` against all three target frameworks: net8.0, net9.0, and net10.0. +Temporary `.sysml` files are created in `Path.GetTempPath()` and deleted after each test. No +external services, network access, or additional configuration are required beyond a standard +.NET SDK installation. + +#### Acceptance Criteria + +- All `WorkspaceLoaderTests` pass with zero failures across all three target frameworks. +- An empty `.sysml` file returns a non-null `SysmlWorkspace` with `HasErrors = false`. +- A single-package file registers exactly the package qualified name in `Declarations`. +- Nested packages register both parent and child qualified names. +- Part definitions register their qualified names. +- Calling `LoadAsync` with no files returns a non-null workspace containing stdlib declarations. +- Loading all 94 stdlib files results in `HasErrors = false` and a non-empty `Declarations`. +- A resolved specialization (`specializes` with a known type) produces no unresolved Warning. +- An unresolved supertype reference produces exactly one `Warning`-severity diagnostic. +- A circular import produces a `Warning`-severity diagnostic and completes in finite time. + +#### Test Scenarios | Test | Level | Assertion | | --- | --- | --- | -| `WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace` | 1 | Empty file returns non-null workspace with `HasErrors = false` | +| `WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace` | 1 | Empty file; non-null workspace, HasErrors false | | `WorkspaceLoader_LoadAsync_SinglePackage_RegistersDeclaration` | 2 | Single package name registered | | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | 3 | Nested package qualified names registered | | `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | 4 | Part def qualified name registered | @@ -14,9 +39,9 @@ | `WorkspaceLoader_LoadAsync_StdlibDeclarations_Registered` | 6 | Stdlib contributes declarations without errors | | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | 7 | Resolved supertype produces no unresolved warning | | `WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning` | 8 | Unresolved supertype produces Warning | -| `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | 9 | Circular import produces Warning, completes in finite time | +| `WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop` | 9 | Circular import; Warning emitted | -## Level 10 Gate +#### Level 10 Gate `SemanticOmgModels_AllModels_ResolveWithZeroErrors` confirms that all OMG model files in `test/SysMLModels/` load with zero Error-level diagnostics. diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs index 2d4f1cfd..218aebbb 100644 --- a/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/AstBuilder.cs @@ -10,6 +10,9 @@ namespace DemaConsulting.SysML2Tools.Semantic.Internal; /// internal sealed class AstBuilder : SysMLv2ParserBaseVisitor { + /// + /// Tracks the current nesting path as a stack of simple name segments. + /// private readonly List _namespaceStack = new(); /// diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs index a870e88e..82977d2a 100644 --- a/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/ReferenceResolver.cs @@ -10,15 +10,29 @@ namespace DemaConsulting.SysML2Tools.Semantic.Internal; /// internal sealed class ReferenceResolver { + /// + /// The symbol table used to check whether supertype names are registered. + /// private readonly SymbolTable _symbolTable; + + /// + /// The shared diagnostics list to which Warning entries are appended. + /// private readonly List _diagnostics; + /// + /// Initializes a new instance of with the given symbol + /// table and diagnostics list. + /// public ReferenceResolver(SymbolTable symbolTable, List diagnostics) { _symbolTable = symbolTable; _diagnostics = diagnostics; } + /// + /// Runs import-graph cycle detection and supertype reference resolution over all file roots. + /// public void ResolveAll(IEnumerable<(string FilePath, SysmlNode? Root)> fileRoots) { // Build import graph first @@ -35,6 +49,9 @@ public void ResolveAll(IEnumerable<(string FilePath, SysmlNode? Root)> fileRoots } } + /// + /// Builds an import graph mapping each file path to the set of namespace names it imports. + /// private static Dictionary> BuildImportGraph( IEnumerable<(string FilePath, SysmlNode? Root)> fileRoots) { @@ -49,6 +66,9 @@ private static Dictionary> BuildImportGraph( return graph; } + /// + /// Recursively collects all imported namespace names from an AST node and its descendants. + /// private static void CollectImports(SysmlNode node, HashSet imports) { if (node is SysmlImportNode importNode) @@ -62,6 +82,9 @@ private static void CollectImports(SysmlNode node, HashSet imports) } } + /// + /// Performs a DFS over the import graph to detect and report circular import chains. + /// private void DetectCircularImports(Dictionary> importGraph) { var visited = new HashSet(StringComparer.Ordinal); @@ -73,6 +96,10 @@ private void DetectCircularImports(Dictionary> importGra } } + /// + /// Recursive DFS helper that detects back-edges in the import graph and emits Warning + /// diagnostics for any cycle found. + /// private void DetectCycles( string current, Dictionary> graph, @@ -104,6 +131,10 @@ private void DetectCycles( inStack.Remove(current); } + /// + /// Resolves supertype names in the given AST node and its descendants, emitting a Warning + /// for each name not found in the symbol table. + /// private void ResolveNode(SysmlNode node, string filePath, HashSet resolvedInFile) { // Resolve supertype names diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs index ac2af8e7..3cdcdd32 100644 --- a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SupertypeWalker.cs @@ -10,15 +10,30 @@ namespace DemaConsulting.SysML2Tools.Semantic.Internal; /// internal sealed class SupertypeWalker { + /// + /// The symbol table providing all registered declarations for chain traversal. + /// private readonly SymbolTable _symbolTable; + + /// + /// The shared diagnostics list to which cyclic-specialization Warning entries are appended. + /// private readonly List _diagnostics; + /// + /// Initializes a new instance of with the given symbol + /// table and diagnostics list. + /// public SupertypeWalker(SymbolTable symbolTable, List diagnostics) { _symbolTable = symbolTable; _diagnostics = diagnostics; } + /// + /// Walks all specialization chains for every symbol in the table and emits Warning + /// diagnostics for any cyclic specialization detected. + /// public void WalkAll() { var visited = new HashSet(StringComparer.Ordinal); @@ -32,6 +47,10 @@ public void WalkAll() } } + /// + /// Recursive DFS helper that traverses the specialization chain rooted at the given node, + /// emitting a Warning when a back-edge (cycle) is detected. + /// private void WalkNode( SysmlNode node, string qualifiedName, diff --git a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs index 2dc4d39c..a64da9a8 100644 --- a/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs +++ b/src/DemaConsulting.SysML2Tools/Semantic/Internal/SymbolTable.cs @@ -8,6 +8,9 @@ namespace DemaConsulting.SysML2Tools.Semantic.Internal; /// internal sealed class SymbolTable { + /// + /// The internal dictionary mapping fully-qualified names to their declaration nodes. + /// private readonly Dictionary _symbols = new(StringComparer.Ordinal); /// @@ -28,6 +31,9 @@ public void RegisterAll(SysmlNode? root) RegisterNode(root); } + /// + /// Registers a single node and all of its descendants into the symbol dictionary. + /// private void RegisterNode(SysmlNode node) { if (node.QualifiedName is { Length: > 0 }) diff --git a/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs b/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs index d3736a25..0ab642dd 100644 --- a/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs +++ b/src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs @@ -75,12 +75,22 @@ public static async Task LoadAsync(IEnumerable filePath return new SysmlLoadResult(workspace, allDiagnostics); } - // Stdlib AST cache + /// + /// Cached stdlib parse and AST build task, executed at most once per process lifetime. + /// private static readonly Lazy> StdlibSemanticTask = new(() => Task.Run(BuildStdlibSemanticAsync)); + /// + /// Returns the shared stdlib semantic task, starting it on first access. + /// private static Task GetStdlibAstAsync() => StdlibSemanticTask.Value; + /// + /// Enumerates all embedded stdlib resources, parses each into a CST, builds a typed + /// AST, and returns all roots and diagnostics. KerML parse errors are downgraded to + /// Warnings because the SysML v2 grammar does not fully cover KerML-specific syntax. + /// private static async Task BuildStdlibSemanticAsync() { var diagnostics = new List(); @@ -132,6 +142,11 @@ private static async Task BuildStdlibSemanticAsync() return new StdlibSemanticResult(astRoots, diagnostics); } + /// + /// Reads and parses a single user-supplied SysML/KerML file, returning the file path, + /// AST root, and all collected diagnostics. File I/O failures are caught and returned + /// as an Error-severity diagnostic rather than propagated. + /// private static async Task<(string Path, SysmlNode? Root, List Diagnostics)> ParseUserFileAsync( string filePath) { @@ -154,6 +169,9 @@ private static async Task BuildStdlibSemanticAsync() return (filePath, root, diagnostics); } + /// + /// Internal result record holding all stdlib AST roots and collected diagnostics. + /// private sealed record StdlibSemanticResult( IReadOnlyList<(string VirtualPath, SysmlNode? Root)> AstRoots, IReadOnlyList Diagnostics); From 6f374d872e3c8cc713ce28ad3e1123707763bd76 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 11:11:05 -0400 Subject: [PATCH 3/6] fix: address quality issues - add missing review sets, fix design doc headings, add supertype requirement --- .reviewmark.yaml | 26 ++++++++++++++ docs/design/sysml2-tools-core/semantic.md | 6 ++-- .../reqstream/sysml2-tools-core/semantic.yaml | 11 ++++++ .../Semantic/WorkspaceLoaderTests.cs | 35 +++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/.reviewmark.yaml b/.reviewmark.yaml index 3ef084a9..efecb82d 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -170,6 +170,16 @@ reviews: - "test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs" - "test/DemaConsulting.SysML2Tools.Tests/Parser/OmgModelsTests.cs" + - id: SysML2Tools-Core-Parser-Changes + title: Review of Parser Changes for Phase 2 (StdlibLoader KerML + ParseSourceToCst) + context: + - docs/reqstream/sysml2-tools-core/parser.yaml + - docs/design/sysml2-tools-core/parser.md + paths: + - "src/DemaConsulting.SysML2Tools/Parser/WorkspaceParser.cs" + - "src/DemaConsulting.SysML2Tools/Parser/Internal/StdlibLoader.cs" + - "test/DemaConsulting.SysML2Tools.Tests/Parser/WorkspaceParserTests.cs" + - id: SysML2Tools-Core-Semantic-Design title: Review that DemaConsulting.SysML2Tools Semantic Design is Consistent and Complete context: @@ -216,6 +226,22 @@ reviews: - "test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs" - "test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs" + - id: SysML2Tools-Core-Semantic-WorkspaceLoader + title: Review of DemaConsulting.SysML2Tools Semantic WorkspaceLoader Unit Implementation + context: + - docs/design/sysml2-tools-core.md + - docs/reqstream/sysml2-tools-core.yaml + - docs/design/sysml2-tools-core/semantic.md + - docs/reqstream/sysml2-tools-core/semantic.yaml + paths: + - "docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml" + - "docs/design/sysml2-tools-core/semantic/workspace-loader.md" + - "docs/verification/sysml2-tools-core/semantic/workspace-loader.md" + - "src/DemaConsulting.SysML2Tools/Semantic/WorkspaceLoader.cs" + - "test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs" + - "test/DemaConsulting.SysML2Tools.Tests/Semantic/SemanticOmgModelsTests.cs" + - "test/SysMLModels/software-structure.sysml" + # SysML2Tools SVG Renderer - id: SysML2Tools-Svg-Architecture title: Review that DemaConsulting.SysML2Tools.Svg Architecture Satisfies Requirements diff --git a/docs/design/sysml2-tools-core/semantic.md b/docs/design/sysml2-tools-core/semantic.md index 1487766d..b07c0328 100644 --- a/docs/design/sysml2-tools-core/semantic.md +++ b/docs/design/sysml2-tools-core/semantic.md @@ -6,7 +6,7 @@ The Semantic subsystem builds a semantic workspace from the parsed SysML/KerML s operates as a second layer above the Parser subsystem, consuming ANTLR4 CSTs produced by `WorkspaceParser` and transforming them into a structured symbol table with resolved references. -### Architecture +### Interfaces The Semantic subsystem contains one public unit (`WorkspaceLoader`) and an internal subsystem (`Internal`) containing `AstBuilder`, `SymbolTable`, `ReferenceResolver`, and `SupertypeWalker`. @@ -29,8 +29,6 @@ flowchart TD AstBuilder --> SymbolTable ``` -### External Interfaces - **WorkspaceLoader.LoadAsync**: Loads the embedded stdlib plus every file in the provided collection asynchronously. @@ -56,7 +54,7 @@ collection asynchronously. - *Contract*: Exposes `IReadOnlyList Files` and `IReadOnlyDictionary Declarations` mapping qualified names to declaration nodes. -### Data Flow +### Design 1. `WorkspaceLoader.LoadAsync` awaits the shared `Lazy>` stdlib result. On first call the factory fires `Task.Run(BuildStdlibSemanticAsync)`, which reads each stdlib diff --git a/docs/reqstream/sysml2-tools-core/semantic.yaml b/docs/reqstream/sysml2-tools-core/semantic.yaml index 61325e90..7ab0ba8f 100644 --- a/docs/reqstream/sysml2-tools-core/semantic.yaml +++ b/docs/reqstream/sysml2-tools-core/semantic.yaml @@ -67,6 +67,17 @@ sections: tests: - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop + - id: SysML2Tools-Core-Semantic-Supertype + title: >- + WorkspaceLoader.LoadAsync detects cyclic specialization and produces Warning diagnostics + justification: | + Cyclic specialization (e.g. A specializes B, B specializes A) is semantically + invalid in SysML v2. Reporting these as Warnings allows callers to diagnose invalid + models while still returning a usable workspace. SupertypeWalker.WalkAll performs + a DFS over the specialization graph and emits one Warning per back-edge detected. + tests: + - WorkspaceLoader_LoadAsync_CyclicSpecialization_ProducesWarning + - id: SysML2Tools-Core-Semantic-HasErrors title: >- SysmlLoadResult.HasErrors shall return true if and only if at least one diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index bd2ee0e0..8879604a 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -251,4 +251,39 @@ public async Task WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfi File.Delete(tempFile2); } } + + /// + /// Validates that a cyclic specialization chain (A specializes B, B specializes A) + /// produces a Warning diagnostic and completes in finite time. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_CyclicSpecialization_ProducesWarning() + { + // Arrange — A specializes B, B specializes A (cyclic) + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def A specializes P::B {} + part def B specializes P::A {} + } + """, TestContext.Current.CancellationToken); + + // Act — must complete in finite time + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + var result = await WorkspaceLoader.LoadAsync([tempFile]) + .WaitAsync(cts.Token); + + // Assert — cyclic specialization warning present + Assert.NotNull(result.Workspace); + Assert.Contains(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning && + d.Message.Contains("Cyclic specialization")); + } + finally + { + File.Delete(tempFile); + } + } } From 2c2d01667339915e1e11f0162d86b7a27d8c05e3 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 11:46:00 -0400 Subject: [PATCH 4/6] fix: address quality issues in Phase 2 docs and test coverage --- docs/design/sysml2-tools-core.md | 53 +++++++++++++++++-- .../semantic/internal/supertype-walker.yaml | 2 +- .../semantic/workspace-loader.yaml | 2 +- .../Semantic/WorkspaceLoaderTests.cs | 23 ++++++++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/docs/design/sysml2-tools-core.md b/docs/design/sysml2-tools-core.md index cc2f50ec..c444b5c6 100644 --- a/docs/design/sysml2-tools-core.md +++ b/docs/design/sysml2-tools-core.md @@ -78,6 +78,29 @@ file path. - *Role*: Data type. - *Values*: `Info`, `Warning`, `Error`. +**WorkspaceLoader.LoadAsync**: Loads the embedded stdlib plus every user file into a semantic workspace. + +- *Type*: In-process .NET static async method. +- *Role*: Provider. +- *Contract*: Accepts `IEnumerable filePaths`; returns `Task` containing + the semantic workspace, all collected diagnostics, and a `HasErrors` flag. Stdlib ASTs are + cached; user files are parsed in parallel on the thread pool. +- *Constraints*: `filePaths` must not be null; each path should be a readable file path. + +**SysmlLoadResult**: Aggregate result record returned by `WorkspaceLoader.LoadAsync`. + +- *Type*: Sealed record. +- *Role*: Data transfer object. +- *Contract*: Exposes `SysmlWorkspace? Workspace`, `IReadOnlyList Diagnostics`, + and `bool HasErrors`. + +**SysmlWorkspace**: Semantic workspace containing all registered declarations. + +- *Type*: Sealed class. +- *Role*: Data container. +- *Contract*: Exposes `IReadOnlyList Files` and + `IReadOnlyDictionary Declarations`. + ## Dependencies - **Antlr4.Runtime.Standard** — ANTLR4 C# runtime; provides `AntlrInputStream`, @@ -85,8 +108,9 @@ file path. the pre-generated `SysMLv2Lexer` and `SysMLv2Parser`. See *ANTLR4 Integration Design*. - **Embedded Stdlib resources** — 94 SysML v2 standard library files (58 `.sysml` + 36 `.kerml`) from the Systems-Modeling/SysML-v2-Release tag 2026-04; licensed EPL-2.0 and - committed under `Stdlib/`. Phase 1 loads only the `.sysml` files; `.kerml` files are - embedded but not parsed until Phase 2. + committed under `Stdlib/`. All 94 stdlib files (58 `.sysml` + 36 `.kerml`) are loaded by + WorkspaceLoader; KerML parse errors are downgraded to Warnings because the SysML v2 grammar + does not fully cover KerML-specific syntax. ## Risk Control Measures @@ -112,6 +136,26 @@ N/A — not a safety-classified software item. 6. After all async work completes, `WorkspaceParser.ParseAsync` concatenates stdlib and user-file paths and diagnostics into a `WorkspaceParseResult` and returns it. +### Semantic Data Flow + +1. `WorkspaceLoader.LoadAsync` awaits the shared `Lazy>` stdlib + semantic task. On first call, the factory fires `Task.Run(BuildStdlibSemanticAsync)`, which + enumerates all embedded manifest resources matching both `.sysml` and `.kerml` extensions, + reads each stream, parses to a CST via `WorkspaceParser.ParseSourceToCst`, builds a typed + AST via `AstBuilder.Build`, and collects all diagnostics (KerML errors downgraded to Warnings). +2. Concurrently, all caller-supplied file paths are dispatched to the thread pool via + `Task.WhenAll`, each reading file content and calling `WorkspaceParser.ParseSourceToCst` + followed by `AstBuilder.Build`; file I/O failures are caught and returned as Error-severity + diagnostics. +3. `SymbolTable.RegisterAll` is called for each stdlib and user AST root, building the + qualified-name registry. +4. `ReferenceResolver.ResolveAll` iterates all registered symbols, resolving supertype + references and emitting Warning diagnostics for unresolved names and circular imports. +5. `SupertypeWalker.WalkAll` traverses every specialization chain, detecting cyclic + specialization and emitting Warning diagnostics for detected cycles. +6. A `SysmlWorkspace` is constructed from the loaded file list and symbol table, and wrapped + in a `SysmlLoadResult` with all accumulated diagnostics. + ## Design Constraints - Platform: multi-targets net8.0, net9.0, and net10.0 on Windows, Linux, and macOS. @@ -120,5 +164,6 @@ N/A — not a safety-classified software item. - The ANTLR4-generated C# files under `Parser/Antlr/` are committed to the repository and must not be manually edited; they are regenerated using `antlr-4.13.1-complete.jar` as documented in `Grammar/README.md`. -- Phase 1 performs syntax-only parsing (CST construction). No semantic model, symbol table, - or reference resolution is performed. +- `WorkspaceParser` provides syntax-only parsing (CST construction). Semantic model + construction, symbol table registration, and reference resolution are performed by + `WorkspaceLoader` in the Semantic subsystem. diff --git a/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml b/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml index fbc0a0c9..8b6794c4 100644 --- a/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml +++ b/docs/reqstream/sysml2-tools-core/semantic/internal/supertype-walker.yaml @@ -16,4 +16,4 @@ sections: invalid in SysML v2. The walker must detect and report these cycles without looping infinitely. tests: - - WorkspaceLoader_LoadAsync_SpecializesChain_Registered + - WorkspaceLoader_LoadAsync_CyclicSpecialization_ProducesWarning diff --git a/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml b/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml index e23d1eab..e574499c 100644 --- a/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml +++ b/docs/reqstream/sysml2-tools-core/semantic/workspace-loader.yaml @@ -26,4 +26,4 @@ sections: A file-read failure prevents the workspace from being built for that file. Reporting it as an Error diagnostic allows callers to surface the problem to users. tests: - - WorkspaceLoader_LoadAsync_EmptyFile_ReturnsNonNullWorkspace + - WorkspaceLoader_LoadAsync_UnreadableFile_ProducesErrorDiagnostic diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 8879604a..631c5a11 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -252,6 +252,29 @@ public async Task WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfi } } + // Level 10: Unreadable file produces Error diagnostic + /// + /// A path to a file that cannot be read (non-existent) should produce an Error diagnostic. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_UnreadableFile_ProducesErrorDiagnostic() + { + // Arrange — path to a file that does not exist + var nonExistentPath = Path.Combine( + Path.GetTempPath(), + $"nonexistent_{Guid.NewGuid():N}.sysml"); + + // Act + var result = await WorkspaceLoader.LoadAsync([nonExistentPath]); + + // Assert + Assert.NotNull(result.Workspace); + Assert.True(result.HasErrors, "Expected HasErrors to be true for an unreadable file"); + Assert.Contains(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Error && + d.FilePath == nonExistentPath); + } + /// /// Validates that a cyclic specialization chain (A specializes B, B specializes A) /// produces a Warning diagnostic and completes in finite time. From ef102d0e8805e52a237081e011be73392c86d1c9 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 12:06:40 -0400 Subject: [PATCH 5/6] fix: use xUnit cancellation token in CyclicSpecialization test The 30-second hard timeout fired on cold Linux CI runners where stdlib loading (94 files) takes longer to warm WorkspaceLoader's StdlibSemanticTask. Use TestContext.Current.CancellationToken so CI's per-test timeout controls the limit, not a fixed 30s guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Semantic/WorkspaceLoaderTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 631c5a11..db1fa1f1 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -293,10 +293,11 @@ package P { } """, TestContext.Current.CancellationToken); - // Act — must complete in finite time - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + // Act — cycle detection must terminate (not loop forever). + // Use xUnit's per-test cancellation token rather than a hard 30-second + // limit; stdlib loading on a cold Linux CI runner can take longer than 30s. var result = await WorkspaceLoader.LoadAsync([tempFile]) - .WaitAsync(cts.Token); + .WaitAsync(TestContext.Current.CancellationToken); // Assert — cyclic specialization warning present Assert.NotNull(result.Workspace); From 94f9f52c0b16faf8338ddd84935781eac2445a2f Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Thu, 25 Jun 2026 13:04:56 -0400 Subject: [PATCH 6/6] fix: use xUnit cancellation token in CircularImport test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as CyclicSpecialization — the 30-second hard timeout fires on cold Linux/Windows CI runners where stdlib loading takes longer than 30s. Defer to TestContext.Current.CancellationToken. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Semantic/WorkspaceLoaderTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index db1fa1f1..eee3af0f 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -235,10 +235,11 @@ public async Task WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfi await File.WriteAllTextAsync(tempFile1, "package A { import B::*; }", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(tempFile2, "package B { import A::*; }", TestContext.Current.CancellationToken); - // Act — must complete in finite time - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + // Act — cycle detection must terminate (not loop forever). + // Use xUnit's per-test cancellation token rather than a hard 30-second + // limit; stdlib loading on a cold Linux CI runner can take longer than 30s. var result = await WorkspaceLoader.LoadAsync([tempFile1, tempFile2]) - .WaitAsync(cts.Token); + .WaitAsync(TestContext.Current.CancellationToken); // Assert — circular import warning present Assert.NotNull(result.Workspace);