diff --git a/.agents/skills/testing-pilot-corpora-gate/SKILL.md b/.agents/skills/testing-pilot-corpora-gate/SKILL.md index a2ae6fc9ad..e953d351a9 100644 --- a/.agents/skills/testing-pilot-corpora-gate/SKILL.md +++ b/.agents/skills/testing-pilot-corpora-gate/SKILL.md @@ -183,8 +183,8 @@ gate's own helpers are package-private but reusable (`pilotCorporaGate.files(t)` `actionlint`, `shellcheck`, `python3 scripts/check-doc-links.py`, `gofmt`, `go vet`, `go run -C tools ./cmd/pilot-diff` (validators pre-downloaded; ~4min, prints e.g. -the headline the committed baseline holds — `384 file(s), 345 fully agreeing; 49 agreed -diagnostic(s), 43 only ours, 1640 only the pilot's` at the `2026-08` pin, so read it from +the headline the committed baseline holds — `384 file(s), 347 fully agreeing; 49 agreed +diagnostic(s), 43 only ours, 1634 only the pilot's` at the `2026-08` pin, so read it from `docs/project/pilot-differential-baseline.json` rather than from this line) and `make lint` (staticcheck+gosec, ~2min) all work. There is **no** `yamllint` and **no** `circleci` CLI, so `.circleci/config.yml` can only be parsed as YAML, not schema-validated — say so diff --git a/.agents/skills/testing-pilot-differential/SKILL.md b/.agents/skills/testing-pilot-differential/SKILL.md index f40029f817..07f5ac9b6d 100644 --- a/.agents/skills/testing-pilot-differential/SKILL.md +++ b/.agents/skills/testing-pilot-differential/SKILL.md @@ -23,8 +23,8 @@ GNU-format diagnostics **relative to `--root`**. Consequences for testing: - `-validator /nonexistent` now says `run ./scripts/download-pilot-sysml-validator.sh`. - Measured at the `2026-08` pin after bare parameters took their effective range `[0..*]`, removing the adjudicated `Behaviors.kerml:14` multiplicity warning (the `[1]` `RocketEquation` inputs keep - its warning at `delta-v-budget.sysml:93`): `384 file(s), 345 fully agreeing; 49 agreed, 43 only - ours, 1640 only the pilot's`, JSON totals `openSysMLDiagnostics 94 / pilotDiagnostics 1691 / + its warning at `delta-v-budget.sysml:93`): `384 file(s), 347 fully agreeing; 49 agreed, 43 only + ours, 1634 only the pilot's`, JSON totals `openSysMLDiagnostics 94 / pilotDiagnostics 1685 / severityMismatch 2`; the two new only-ours rows are the expected `action-step-multiplicity-not-fixed` warnings on `training/18. Action Performance/Action Performance Example.sysml:10` and `pilot-examples/Camera Example/Camera.sysml:4`. ~2 min wall, byte-identical across runs *and* after a from-scratch rebuild of @@ -146,8 +146,8 @@ parameters took their effective range `[0..*]` and removed the adjudicated `Beha warning (the `[1]` `RocketEquation` inputs still produce the warning at `delta-v-budget.sysml:93`), is current: the action-step multiplicity rule adds the two expected `action-step-multiplicity-not-fixed` warnings on `takePhoto[*]` in the training corpus and -`takePicture[*]` in `Camera Example/Camera.sysml`; a live run gives `384 file(s), 345 fully -agreeing; 49 agreed, 43 only ours, 1640 only the pilot's`, byte-identical to the committed baseline, and +`takePicture[*]` in `Camera Example/Camera.sysml`; a live run gives `384 file(s), 347 fully +agreeing; 49 agreed, 43 only ours, 1634 only the pilot's`, byte-identical to the committed baseline, and `docs/project/pilot-differential.md`'s "Results" table matches. The prior rebaseline, when the Legend of the Red Dragon example left for its own repository, gave `380 file(s), 344 fully agreeing; 38 agreed, 42 only ours, 1614 only the pilot's`. diff --git a/.agents/skills/testing-pilot-execution-referee/SKILL.md b/.agents/skills/testing-pilot-execution-referee/SKILL.md index 59f3f8f884..487b6412a0 100644 --- a/.agents/skills/testing-pilot-execution-referee/SKILL.md +++ b/.agents/skills/testing-pilot-execution-referee/SKILL.md @@ -148,8 +148,8 @@ pilot answers the representation's own. See `pilot-exec-diff: :: model no/such/model.sysml: stat : no such file or directory`. - **Additivity.** `go run -C tools ./cmd/pilot-diff` must still print the headline the - committed baseline holds (`384 file(s), 345 fully agreeing; 49 agreed - diagnostic(s), 43 only ours, 1640 only the pilot's` at the `2026-08` pin — read it from the baseline JSON, not from this line, since each + committed baseline holds (`384 file(s), 347 fully agreeing; 49 agreed + diagnostic(s), 43 only ours, 1634 only the pilot's` at the `2026-08` pin — read it from the baseline JSON, not from this line, since each fix round moves it) and `jq -S` diff clean against `docs/project/pilot-differential-baseline.json`; `git status --porcelain` empty at the end. diff --git a/.agents/skills/testing-pilot-rejection/SKILL.md b/.agents/skills/testing-pilot-rejection/SKILL.md index 6cb2c1dd44..db3f258650 100644 --- a/.agents/skills/testing-pilot-rejection/SKILL.md +++ b/.agents/skills/testing-pilot-rejection/SKILL.md @@ -9,7 +9,7 @@ Sibling of `testing-pilot-differential` and `testing-pilot-xpect` (same pin `scripts/pilot-pin.sh`, same committed-baseline shape), but pointed the other way: the differential measures what the reference accepts and we reject; this oracle measures what the reference **rejects and we accept** — permissiveness gaps. Its corpus is committed under -`tools/referee/reject/testdata/negative/` (312 hand-written invalid models, one violated rule + citation +`tools/referee/reject/testdata/negative/` (317 hand-written invalid models, one violated rule + citation in each file's mandatory `// Invalid: ...` first line), so no corpus download exists. Method and findings: `docs/project/pilot-rejection.md`. @@ -36,7 +36,7 @@ succession rules, the feature-value overriding rule, the enumeration-variation r the metadata typing, annotated-element and body rules, the trigger-argument typing rules, the owning-body member rules, the cross-subsetting rules, the variant port rule, the association arity, binary-link end count and multiplicity-bound typing rules, the result-expression ownership rules, and the annotation-ownership, binding-arity, feature-chain conformance, conjugated typing and end-feature rules, the end-multiplicity, return-owner and single-conjugator rules, the keyword-first relationship end kinds, the prefix and body-context cases, the redefinition name-resolution cases, the transition accept usage-typing rule, and the connector-end accessibility of a qualified definition feature), with a fresh library cache: -`312 case(s): 303 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept`, +`317 case(s): 308 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept`, byte-identical to the committed baseline. Any `both accept` case is a bug in the corpus (the case is not actually invalid under the loaded standard library) — fix the case, never ignore it. A candidate the pilot accepts because it does not enforce the named constraint is not a case either: diff --git a/.agents/skills/testing-pilot-xpect/SKILL.md b/.agents/skills/testing-pilot-xpect/SKILL.md index 3eb263d618..e6e6089824 100644 --- a/.agents/skills/testing-pilot-xpect/SKILL.md +++ b/.agents/skills/testing-pilot-xpect/SKILL.md @@ -423,8 +423,8 @@ census in `w5c_census_test.go` is live two ways: perturb one pinned triple (e.g. ## Regression neighbour `go run -C tools ./cmd/pilot-diff` (~1m12s) must still print the headline the *committed* baseline holds — -at the `2026-08` pin that is `384 file(s), 345 fully agreeing; 49 agreed diagnostic(s), 43 -only ours, 1640 only the pilot's`. Read the number out of +at the `2026-08` pin that is `384 file(s), 347 fully agreeing; 49 agreed diagnostic(s), 43 +only ours, 1634 only the pilot's`. Read the number out of `docs/project/pilot-differential-baseline.json` rather than trusting this line, since a landing fix round moves it. When the baseline is itself stale (it was at `19a3ce03`, holding 273 / 281 / 317), a failing `cmp` against it is *not* evidence of an Xpect regression — compare the summary line, and see diff --git a/.agents/skills/testing-sysml-repl/SKILL.md b/.agents/skills/testing-sysml-repl/SKILL.md index 1d5532d7ea..a43f741167 100644 --- a/.agents/skills/testing-sysml-repl/SKILL.md +++ b/.agents/skills/testing-sysml-repl/SKILL.md @@ -4223,7 +4223,7 @@ Read-only printer of the session buffer or one element: `%view Demo::report` prints `exposes` then `viewpoint conformance` with `satisfy structure (from Demo::StructureView): violated` and one line per concern -(`conforms` / `violated (framed by the viewpoint but not by the view)` / `unevaluable` + reason). +(`conforms` / `violated` / `unevaluable`, each with its reason where it has one). A satisfy target that is a `requirementUsage` is diagnosed at load *and* reported as `unevaluable (satisfy target spec is a requirementUsage, not a viewpoint)` — never a silent pass. `%view` registers no object: `%instances` afterwards says none created, a repeat is identical, and a diff --git a/README.md b/README.md index cb222c99f5..cc8d2c3606 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,3 @@ -

- - - OpenSysML - -

- # Open Source SysML v2 Implementation [![CircleCI](https://dl.circleci.com/status-badge/img/gh/Open-MBEE/OpenSysML/tree/main.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/Open-MBEE/OpenSysML/tree/main) @@ -61,23 +54,7 @@ certification is claimed. ### Install -**Install script** — picks the release build for this machine, verifies it against the -release's `SHA256SUMS.txt`, and installs `sysml` and `sysml-lsp`: -```bash -# Linux and macOS: /usr/local/bin when writable, otherwise ~/.local/bin -curl -fsSL https://opensysml.org/install.sh | sh -``` -```powershell -# Windows: %LOCALAPPDATA%\Programs\OpenSysML, added to the user PATH -irm https://opensysml.org/install.ps1 | iex -``` -Both scripts take a release (`--version v0.9.1`, `nightly`), a tool list (`--tools sysml`, -`sysml-lsp`, `sysml-grpc`, `all`) and a destination (`--prefix`, or `-InstallDir` on Windows), -and `--dry-run` shows the choice without installing; `sh -s -- --help` lists the rest. The -scripts are [`install.sh`](install.sh) and [`install.ps1`](install.ps1) in this repository, -so a checkout runs them as `./install.sh`. - -**Download pre-built binaries by hand:** +**Download pre-built binaries:** ```bash # Linux x64 (use opensysml-linux-arm64.tar.gz on arm64) wget https://github.com/Open-MBEE/OpenSysML/releases/latest/download/opensysml-linux-amd64.tar.gz @@ -99,11 +76,10 @@ make build ./bin/sysml ``` -> **macOS — use the install script or Homebrew.** The released binaries are not Developer ID -> signed or notarized, so a tarball downloaded *in a browser* carries `com.apple.quarantine` -> and Gatekeeper shows "cannot be opened because the developer cannot be verified". The -> install script and Homebrew both download with `curl`, which never sets that attribute, so -> neither meets the prompt. +> **macOS — use Homebrew.** The released binaries are not Developer ID signed or notarized, +> so a tarball downloaded *in a browser* carries `com.apple.quarantine` and Gatekeeper shows +> "cannot be opened because the developer cannot be verified". Homebrew downloads with +> `curl`, which never sets that attribute, so `brew install` avoids the prompt entirely. > When the tarball is downloaded directly (`curl -fL ... opensysml-darwin-arm64.tar.gz`, followed > by `xattr -d com.apple.quarantine`), see > [the guide](docs/guide/01-install.md#macos-gatekeeper). Signing and notarization are the @@ -319,7 +295,7 @@ The project is under active development, with the core infrastructure operationa | Model save to notation (`%save model.sysml`, `sysml -convert sysml`) | ✅ Complete — writes the source through the formatter, so comments and spacing survive | | SysML ↔ RDF Turtle conversion (`%save model.ttl`, `sysml -convert ttl`) | 🧪 **Experimental** — packages, definitions, usages, ports, connections, values, documentation, and the nodes an action or state body states (every one of the 346 models under `examples/` converts and round-trips; what is not mapped is refused with the construct named), but the vocabulary may change without a compatibility path. Every run says so; see [the RDF mapping's status](docs/reference/rdf-mapping.md#status-experimental) and [worked example](examples/rdf-interop-demo.sysml) | | SysML v1 → v2 migration (`sysml Model.xmi -migrate sysml`, `.uml` and `.mdzip` too; migrated, not converted — `-convert` refuses a v1 model) | 🧪 **Experimental** — OMG UML 2.5 XMI with the SysML 1.x profile is read and written as v2 notation or RDF: packages, blocks, value types, properties, ports and connectors, requirements with satisfy/verify/derive, constraint blocks, instances and allocations, with a per-element migration report (mapped, approximated, unmapped, skipped). Behaviors, operations and units are not migrated yet, and the mapping may change without a compatibility path. Every run says so; see [the migration's status](docs/reference/sysml-v1-migration.md#status-experimental) | -| View rendering (`%render `, `sysml -render`) | ✅ Complete for the kinds produced — containment tree, interconnection diagram, state machine, action flow, sequence diagram and table, as indented text or in the kind's machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`, [D2](https://d2lang.com) with `-render-form d2`). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) | +| View rendering (`%render `, `sysml -render`) | ✅ Complete for the kinds produced — containment tree, interconnection diagram, state machine, action flow, sequence diagram and table, as indented text or in the kind's machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) | | Constraint solving (`%check`, `%explain`, `%solve`, `%configure`, `%optimize`) | 🧪 **Experimental** — an external SMT-LIB 2 solver decides whether conditions *can* be satisfied, explains an `unsat` with a minimal unsat core, synthesises satisfying values, enumerates the variant selections a model permits and optimizes an `analysis def`'s objectives (optimization needs z3, which implements it). The solver is optional and discovered on `PATH` or through `OPENSYSML_SMT`; a build with none reports that rather than a verdict — see [installing a solver](docs/guide/01-install.md#installing-a-solver-optional) | | Source-preserving model edits (`ApplyEdits`, `model.edit()`) | ✅ Complete for four operations — set a feature's value, rename a declaration, add a member, and delete a declaration — rewriting the bytes of the model's own source so every untouched byte is identical. A rename rewrites the references to the renamed element too, and a non-cascade deletion of a referenced element is refused rather than approximated | | Standard library bundling | ✅ Complete | @@ -336,11 +312,11 @@ The project is under active development, with the core infrastructure operationa **Measured against the pinned reference** (`PILOT_TAG=2026-08`, artifact `0.62.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 345 of 384 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1640 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 347 of 384 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1634 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 512 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 245 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](docs/project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). -- **Permissiveness gaps:** of 312 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 300 both reject; 3 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](docs/project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 346 of 384 files agreeing, 42 diagnostics ours alone and 1640 the reference's alone, 0 declared rows we are silent on, and 0 of 312 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Permissiveness gaps:** of 317 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 305 both reject; 3 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](docs/project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). +- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 348 of 384 files agreeing, 42 diagnostics ours alone and 1634 the reference's alone, 0 declared rows we are silent on, and 0 of 317 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](docs/project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. @@ -352,8 +328,8 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a **Test coverage:** top-level `Test` functions (counted from the `_test.go` files, as `go test ./...` runs them) covering parsers, semantics, runtime (actions, states, instances, operators, validation), behind golden ASTs, negatives, execution conformance cases, golden traces, runtime robustness cases and gRPC conformance and robustness cases. The figures are counted from the tree when the documentation site is built into the test inventory of [spec compliance](docs/project/spec-compliance.md), never committed, so a branch adding a test does not rewrite this page. A test skips only for want of something the run did not provide, and says what: the held-image round trip declines a conformance case that creates no instance, a few gate on a PDF or Mermaid toolchain, a pinned pilot artifact, the PSSM suite, a locale, a case-insensitive filesystem or a live Flexo stack, and the OMG corpus gates skip until the corpora are downloaded unless asked to fail. **Parser coverage:** 105/105 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the non-normative `OpenSysML Libraries/OpenSysMLMathFunctions.kerml`, `OpenSysML Libraries/DocumentQueries.sysml`, `OpenSysML Libraries/IdentityMetadata.sysml`, `OpenSysML Libraries/DiagramLayout.sysml`, `OpenSysML Libraries/OOSEM.sysml`, `OpenSysML Libraries/MOSA.sysml`, `OpenSysML Libraries/StateSpaceIntegration.sysml`, `OpenSysML Libraries/Stochastic.sysml`, `OpenSysML Libraries/RandomFunctions.kerml`, `OpenSysML Libraries/Simulation.sysml` and `OpenSysML Libraries/MigrationMetadata.sysml` extensions. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). **Behavioral execution:** Calc/constraint/requirement/satisfy functional. Action/state executors handle nested invocation, control flow keywords, loop and conditional statements and the send statement (every conformance case passing). Coverage is self-assessed against the specification text and the normative library: the pinned OMG pilot implementation evaluates expressions but does not execute actions or state machines headlessly, so no external implementation currently adjudicates these rows. See [spec compliance](docs/project/spec-compliance.md). -**Reference differential:** 384 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 345 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`. -**Rejection oracle:** the reverse direction — do we reject what the reference rejects? 312 hand-written invalid models validated by both implementations, 303 rejected by both, 0 the pinned pilot rejects and we accept; the remainder only we reject — the control-node succession rules the pinned pilot leaves unimplemented and a non-Boolean succession guard it accepts once the standard library types it — and every permissiveness gap is enumerated with a reproducer and likely root cause in [the rejection oracle](docs/project/pilot-rejection.md), reproducible with `go run -C tools ./cmd/pilot-reject`. We wrote every case, so the count measures our coverage of the rejection surface, not our conformance — a sample, not a proof. +**Reference differential:** 384 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 347 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`. +**Rejection oracle:** the reverse direction — do we reject what the reference rejects? 317 hand-written invalid models validated by both implementations, 308 rejected by both, 0 the pinned pilot rejects and we accept; the remainder only we reject — the control-node succession rules the pinned pilot leaves unimplemented and a non-Boolean succession guard it accepts once the standard library types it — and every permissiveness gap is enumerated with a reproducer and likely root cause in [the rejection oracle](docs/project/pilot-rejection.md), reproducible with `go run -C tools ./cmd/pilot-reject`. We wrote every case, so the count measures our coverage of the rejection surface, not our conformance — a sample, not a proof. **Training examples:** 100/100 files report no semantic errors, gated by `tests/corpus/testdata/training_examples_expected.txt`; the gate does not count execution-scope warnings. Download with `./scripts/download-training-examples.sh` (from the [OMG training directory](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/sysml/src/training)). See [training examples](docs/project/training-examples.md) for analysis. **Semantic layer:** a complete implementation of runtime operators, feature chains and validation rules. See [examples/semantic-layer/](examples/semantic-layer/) for a full demonstration. diff --git a/changes/unreleased/extension-libraries-non-standard.changed.md b/changes/unreleased/extension-libraries-non-standard.changed.md new file mode 100644 index 0000000000..db6020e264 --- /dev/null +++ b/changes/unreleased/extension-libraries-non-standard.changed.md @@ -0,0 +1 @@ +- **Declare the bundled OpenSysML extension libraries as non-standard.** `DiagramLayout`, `IdentityMetadata`, `MigrationMetadata` and `SysMLValidation` were written `standard library package`, which both asserted membership of the normative Kernel/SysML libraries and drew a pilot warning (`User library packages should not be marked as standard`); they are project extensions and now read `library package`. RDF export no longer marks them `sysml:isStandard true`. diff --git a/changes/unreleased/view-body-member-grammar.fixed.md b/changes/unreleased/view-body-member-grammar.fixed.md new file mode 100644 index 0000000000..47fe77a0b5 --- /dev/null +++ b/changes/unreleased/view-body-member-grammar.fixed.md @@ -0,0 +1,3 @@ +- **Reject `frame`, `filter` and `accept` members written outside the bodies that own them.** A `frame` is a RequirementBodyItem — a requirement, concern or viewpoint body, never a view's; a `filter` is a PackageBodyElement and a view/view-def body item — a package or the file root admits it, a viewpoint, rendering or part-def body does not; an `accept` is an ActionBodyItem — an action, calculation or case body (and the state body that already read it), not a view or part def. Each is now reported at the member instead of parsing silently, matching the reference grammar. +- **Report `then ;` written outside an action or state body.** A target succession sequences members of an action, calculation or case body, and a state body reads it as a target transition; anywhere else it is OpenSysML extension notation, warned by default and rejected under `-strict`. +- **`%view` no longer asks a view to frame the concerns its satisfied viewpoint frames.** Viewpoint conformance is evaluated over the concern the *viewpoint* frames, applied to the elements the view exposes; `frame` was never legal in a view body, so a view with a valid viewpoint now reports `conforms`/`violated` instead of a framing mismatch it could never satisfy. diff --git a/docs/guide/04-repl.md b/docs/guide/04-repl.md index b724e2a628..786eba629f 100644 --- a/docs/guide/04-repl.md +++ b/docs/guide/04-repl.md @@ -216,11 +216,12 @@ conforms to the viewpoints it satisfies. Conformance checking is read-only and r declaration order. Each `satisfy` gets a verdict of `conforms`, `violated` or `unevaluable`, followed by a verdict for each concern the viewpoint frames; a concern whose condition fails names the exposed element it failed for and the reason. A `satisfy` inherited from a -specialized view is marked `(from )`. A concern the viewpoint frames but the view does -not is reported as `violated`. A concern that states no condition, or names one that does not -resolve, is reported as `unevaluable` with the reason, not as a pass. These verdicts, and -the way a nested view's framing is treated, are this implementation's own decisions, because -SysML v2 leaves verification verdict semantics non-normative. +specialized view is marked `(from )`. A view frames nothing of its own — a view body +admits no `frame` member — so each concern the viewpoint frames is checked against the exposed +elements directly. A concern that states no condition, or names one that does not +resolve, is reported as `unevaluable` with the reason, not as a pass. These verdicts are this +implementation's own decisions, because SysML v2 leaves verification verdict semantics +non-normative. ``` sysml> package Demo { @@ -256,8 +257,6 @@ sysml> package Demo { ...> viewpoint structure : StructurePerspective; ...> view def StructureView { ...> satisfy structure; - ...> frame concern budget : MassBudget; - ...> frame concern modularity : Modularity; ...> } ...> view report : StructureView { ...> expose vehicle; diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index 080db97383..0e5e59adcb 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -841,11 +841,11 @@ Every behavioral feature must have: **Measured against the pinned reference** (`PILOT_TAG=2026-08`, artifact `0.62.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 345 of 384 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1640 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 347 of 384 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1634 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 512 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 245 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](../project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). -- **Permissiveness gaps:** of 312 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 300 both reject; 3 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](../project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 346 of 384 files agreeing, 42 diagnostics ours alone and 1640 the reference's alone, 0 declared rows we are silent on, and 0 of 312 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Permissiveness gaps:** of 317 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 305 both reject; 3 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](../project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). +- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 348 of 384 files agreeing, 42 diagnostics ours alone and 1634 the reference's alone, 0 declared rows we are silent on, and 0 of 317 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](../project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. diff --git a/docs/project/diagram-layout-annotations.md b/docs/project/diagram-layout-annotations.md index 67a38cada0..7a67ef041d 100644 --- a/docs/project/diagram-layout-annotations.md +++ b/docs/project/diagram-layout-annotations.md @@ -50,7 +50,7 @@ Libraries/DiagramLayout.sysml`, counted by the stdlib conformance gate with the extensions): ```sysml -standard library package DiagramLayout { +library package DiagramLayout { metadata def Layout { attribute x : ScalarValues::Real; attribute y : ScalarValues::Real; diff --git a/docs/project/element-identity-annotations.md b/docs/project/element-identity-annotations.md index b49d0e6b43..98d774df6d 100644 --- a/docs/project/element-identity-annotations.md +++ b/docs/project/element-identity-annotations.md @@ -54,7 +54,7 @@ Two metadata definitions, shipped as a non-normative OpenSysML library extension stdlib already carries two such files; the conformance gate counts them separately): ```sysml -standard library package IdentityMetadata { +library package IdentityMetadata { doc /* Binds notation to repository identity. Non-normative OpenSysML * extension, proposed for standardization; see the design record. */ diff --git a/docs/project/omg-issues.md b/docs/project/omg-issues.md index 16533f2986..8408bc3809 100644 --- a/docs/project/omg-issues.md +++ b/docs/project/omg-issues.md @@ -2145,7 +2145,7 @@ form, implementable today because user-defined metadata is already conforming notation: ```sysml -standard library package IdentityMetadata { +library package IdentityMetadata { metadata def ElementId { attribute id : ScalarValues::String; } @@ -2207,7 +2207,7 @@ notation — or as dedicated surface syntax if the taskforce prefers. A minimal library form: ```sysml -standard library package DiagramLayout { +library package DiagramLayout { metadata def Layout { attribute x : ScalarValues::Real; attribute y : ScalarValues::Real; diff --git a/docs/project/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index 9149d66e8d..d4c76ac9b0 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -61,7 +61,7 @@ "dir": "examples", "origin": "ours", "files": 46, - "digest": "sha256:7d4322cf546559c3c82fe63bb450753c3f0634288c21b59a38aa66ea66cd0638" + "digest": "sha256:996fc4d2a899a2e697edd64b0ae496c55839360b7aaed757bd2bc7d38f2d2aaf" }, { "name": "probes", @@ -71,17 +71,17 @@ "digest": "sha256:b0153c55bbfcdacabab911725dc44e0e7f4d3a501a281b1f3f2a13cda19737c6" } ], - "recorded": "2026-10-04" + "recorded": "2026-10-05" }, "totals": { "files": 384, - "filesFullyAgreeing": 345, + "filesFullyAgreeing": 347, "agreement": 49, "severityMismatch": 2, "openSysMLOnly": 43, - "pilotOnly": 1640, + "pilotOnly": 1634, "openSysMLDiagnostics": 94, - "pilotDiagnostics": 1691 + "pilotDiagnostics": 1685 }, "roots": [ { @@ -961,13 +961,13 @@ "dir": "examples", "totals": { "files": 46, - "filesFullyAgreeing": 31, + "filesFullyAgreeing": 33, "agreement": 4, "severityMismatch": 1, "openSysMLOnly": 6, - "pilotOnly": 1596, + "pilotOnly": 1590, "openSysMLDiagnostics": 11, - "pilotDiagnostics": 1601 + "pilotDiagnostics": 1595 }, "files": [ { @@ -2723,18 +2723,6 @@ "severity": "error", "category": "unmapped", "count": 1 - }, - { - "line": 625, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 627, - "severity": "error", - "category": "syntax", - "count": 1 } ] }, @@ -7761,26 +7749,6 @@ } ] }, - { - "path": "self-model/views.sysml", - "agreement": [], - "severityMismatch": [], - "openSysMLOnly": [], - "pilotOnly": [ - { - "line": 197, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 199, - "severity": "error", - "category": "syntax", - "count": 1 - } - ] - }, { "path": "solver-demo.sysml", "agreement": [], @@ -8084,26 +8052,6 @@ "count": 1 } ] - }, - { - "path": "views-demo.sysml", - "agreement": [], - "severityMismatch": [], - "openSysMLOnly": [], - "pilotOnly": [ - { - "line": 176, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 178, - "severity": "error", - "category": "syntax", - "count": 1 - } - ] } ] }, @@ -8591,13 +8539,13 @@ ], "totals": { "files": 384, - "filesFullyAgreeing": 346, + "filesFullyAgreeing": 348, "agreement": 49, "severityMismatch": 2, "openSysMLOnly": 42, - "pilotOnly": 1640, + "pilotOnly": 1634, "openSysMLDiagnostics": 93, - "pilotDiagnostics": 1691 + "pilotDiagnostics": 1685 }, "findings": [ { diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index ddf6cc012e..a60110cb95 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -237,9 +237,9 @@ nor double-counted as two independent disagreements. | `examples/pilot-corpora/sysml-validation` | 56 | 56 | 0 | 0 | 0 | 0 | 0 | 0 | | `examples/pilot-corpora/kerml-examples` | 58 | 56 | 9 | 0 | 0 | 0 | 9 | 0 | | `tests/testdata` | 21 | 11 | 55 | 90 | 45 | 1 | 9 | 44 | -| `examples` | 46 | 31 | 11 | 1601 | 4 | 1 | 6 | 1596 | +| `examples` | 46 | 33 | 11 | 1595 | 4 | 1 | 6 | 1590 | | `tools/referee/diff/testdata` (probes) | 4 | 1 | 6 | 0 | 0 | 0 | 6 | 0 | -| **Total** | **384** | **345** | **94** | **1691** | **49** | **2** | **43** | **1640** | +| **Total** | **384** | **347** | **94** | **1685** | **49** | **2** | **43** | **1634** | **Read the `only ours` total by root, never as one number.** Step 2 removes nine resolver false positives from the reference's **own** corpora: `pilot-examples` 16 → **7** and @@ -281,7 +281,7 @@ Per category, the only-ours totals are: `training` 1 `multiplicity`; `pilot-exam `unmapped`, 2 `units`, 5 `kind-mismatch`, 1 `multiplicity`; `kerml-examples` 9 `unmapped`; `testdata` 8 `unmapped`, 1 `multiplicity`; `examples` 4 `unmapped`, 2 `multiplicity`; `probes` 6 `unmapped`. Only-pilot: `testdata` 20 `kind-mismatch`, 14 `unmapped`, 3 `syntax`, 7 -`unresolved-reference`; `examples` 6 `syntax`, 29 `unmapped`, 673 `kind-mismatch`, 888 +`unresolved-reference`; `examples` 29 `unmapped`, 673 `kind-mismatch`, 888 `unresolved-reference`. ### Legend of the Red Dragon departure round @@ -742,9 +742,9 @@ no parser, validator or runtime code — every demo's `%`-command output is unch |---|---|---:| | `action-executor-demo.sysml` | `then done;` in place of a standalone `done;` declaration | 3 → **0** | | `phase-c-behavioral-bodies.sysml` | `entry`/`do`/`exit ` and named effect actions; declared Boolean features accepted with `accept when` | 3 → **0** | -| `views-demo.sysml` | the descent flow as `first`/`fork`/`join`/`decide` with successions; `Descender::mass` declared without a default the two landers rebind; the framing view declared last | 8 → **2** | +| `views-demo.sysml` | the descent flow as `first`/`fork`/`join`/`decide` with successions; `Descender::mass` declared without a default the two landers rebind | 8 → **0** | | `solver-demo.sysml` | `assert constraint` for an analysis case's own conditions; each objective redefines the subject it inherits | 15 → **5** | -| `disposal-robot-demo/robot.sysml` | the same objective subject redefinition; the framing view declared last | 17 → **7** | +| `disposal-robot-demo/robot.sysml` | the same objective subject redefinition | 17 → **1** | | `pseudostates-demo.sysml` | a state named `ready` rather than one shadowing the library's `start` | 8 → **7** | `require ` outside a requirement body and a standalone `done;` are the two the audit @@ -771,11 +771,11 @@ What remains is adjudicated as extension notation this project supports delibera The exemption is the analysis case alone; a `case`, `verification` or `use case` declaration with a second objective is reported as the reference reports it, since no other case kind has the lexicographic semantics. -- **`frame concern` in a view usage** (`views-demo.sysml`, `robot.sysml`) — `FramedConcernMember` is - a requirement-body member in the pilot grammar, not a view-body one, and the demos frame a concern - in the view because that is what `%view` evaluates the exposed elements against. Declaring the - framing view last confines the reference's recovery to the file's closing lines, 2 syntax rows - each instead of the whole view package. +The `frame concern` member the demos once wrote in a view usage is retired, not adjudicated: +`FramedConcernMember` is a requirement-body member in the pilot grammar, not a view-body one, so we +now reject it the way the reference always did, and `%view` evaluates the exposed elements against +the concern the satisfied viewpoint frames instead. `views-demo.sysml` and `robot.sysml` dropped the +member, and the 2 syntax rows each are gone with it. The `testdata` fixtures the same notation appears in are unchanged: `passes/import_no_visibility.sysml` and `parse/namespaces.sysml` exist to exercise the diagnostics they carry, so their rows stay @@ -827,8 +827,8 @@ cascades through the rest of the file. The movement is entirely one file, | Count | Before the initializer rewrite | Now | |---|---:|---:| -| only pilot | 82 | **1640** | -| pilot diagnostics | 123 | **1691** | +| only pilot | 82 | **1634** | +| pilot diagnostics | 123 | **1685** | | severity-only | 9 | **2** | The rewrite itself took only-pilot to 61 and pilot diagnostics to 101; the `Now` column states @@ -853,7 +853,7 @@ view and analysis packages, and each is a construct the pinned artifact does not | What the demo writes | Rows | What the pilot reports | |---|---:|---| -| `frame concern` in a view usage | 2 syntax | the member is not in its view grammar, and the cascade takes the file's closing brace | +| `frame concern` in a view usage (removed since — a view frames nothing; the satisfied viewpoint does, and we reject the member too) | 2 → **0** syntax | the member is not in its view grammar, and the cascade takes the file's closing brace | | `view … : StateTransitionView` / `: ActionFlowView`, `render asElementTable` | 3 `unresolved-reference`, 4 `kind-mismatch` | our standard view definitions and rendering, which its libraries do not publish | | `objective … { require constraint … }` with `attribute :>> best` | 6 `unmapped` | one subject per requirement, no rebinding of `best`, one objective per analysis case (the rebinding rows retired with the objective-evaluation round, which states the value as the library's `eval`) | | a second objective for a lexicographic optimum | 2 `unmapped` | `Only one objective is allowed` | @@ -1051,14 +1051,14 @@ page's history. | Count | Now | |---|---:| -| overall: fully agreeing / only ours / our diagnostics | **345 / 43 / 94** | -| only pilot | **1640** | -| pilot diagnostics | **1691** | +| overall: fully agreeing / only ours / our diagnostics | **347 / 43 / 94** | +| only pilot | **1634** | +| pilot diagnostics | **1685** | | severity-only | **2** | | unmapped, our side | **46** | | kerml-examples: only ours | **9** | | pilot-examples: only ours | **12** | -| examples: only pilot | **1596** | +| examples: only pilot | **1590** | The KerML root is now the *cleanest* of the three OMG roots in proportion: **9** only-ours against 6 only-pilot, with 56 of 58 files fully agreeing (439 / 6 and 10 / 58 when the root was added, and @@ -2856,11 +2856,11 @@ fetches. Together they bound how long a stale figure can survive to about a day. ### Multiplicity bound result types round -`validateMultiplicityRangeResultTypes` (KerML 1.1 8.3.4.11.2 `MultiplicityRange`) is a -constraint-tier rule (`passes/w8c_multiplicity_bounds.go`): a model-level-evaluable bound must -evaluate to a non-negative integer or `*`; a bound that is not model-level evaluable is judged by -whether its result type conforms to Integer. The rule moves no row of the reference corpora — the -only non-literal bounds in the four OMG roots (`Simple Tests/MultiplicityTest.sysml`, +`validateMultiplicityRangeResultTypes` (KerML 1.1 8.3.3.6) is a constraint-tier rule +(`passes/w8c_multiplicity_bounds.go`): a model-level-evaluable bound must evaluate to a +non-negative integer or `*`; a bound that is not model-level evaluable is judged by whether its +result type conforms to Integer. The rule moves no row of the reference corpora — the only +non-literal bounds in the four OMG roots (`Simple Tests/MultiplicityTest.sysml`, `Geometry Examples/VehicleGeometryAndCoordinateFrames.sysml`) name Integer- or Natural-typed sibling features, which both sides accept — and moves `semantic/k37-multiplicity-bound-not-natural.kerml` to both-reject. The package-level bound @@ -2868,17 +2868,17 @@ disagreement is now adjudicated toward the pilot; the exponentiation difference adjudicated toward the specification: - **A package-level feature without a value is not a valid evaluable bound.** The maintainer - ruled the pilot correct, not buggy: the prose in KerML 1.1 §8.3.4.11.2 requires a model-level- + ruled the pilot correct, not buggy: the prose in KerML 1.1 §8.3.3.6 requires a model-level- evaluable bound to evaluate to a non-negative value. Under §8.3.4.8.5, a package-level feature without a value is model-level evaluable and evaluates to itself, so `[k]` is rejected by both validators. A type member is not model-level evaluable and is judged by its result type. An - evaluable bound the evaluator does not fold (for example, a feature whose value is a cast) is - also judged by its result type; only a folded value other than a non-negative integer or `*`, or - an evaluation that reaches a feature with no value, directly or through another feature's value, - is rejected. The pilot also rejects - `feature k : Natural = 2 as Natural; feature d [k];`, whose evaluation does not yield a literal - there; OpenSysML accepts it, since the cast evaluates to 2. The constraint's OCL - (`value <> null implies value >= 0` over `valueOf`, §8.3.3.1.9) cannot + evaluable bound the evaluator does not fold (for example, a cast) is also judged by its result + type; only a folded value other than a non-negative integer or `*`, or an evaluation that + reaches a feature with no value, directly or through another feature's value, is rejected. The + pilot also rejects `feature k : Natural = 2 as Natural; feature d [k];`, whose evaluation does + not yield a literal there; OpenSysML accepts it, since the cast evaluates to 2. The constraint's + OCL (`value <> null implies value >= 0` over + `valueOf`, §8.3.3.1.9) cannot distinguish a non-literal result from a negative value because `valueOf` returns null for both. OpenSysML follows the prose; [omg-issues.md](omg-issues.md#a-bound-naming-a-package-level-feature-is-rejected-whatever-its-type-pilot-2026-07) @@ -2888,11 +2888,9 @@ adjudicated toward the specification: passes its check. `IntegerFunctions::'**'` is declared `in y : Natural`, and an Integer exponent resolves to `RationalFunctions::'**'`, whose result is Rational; we accept the exponentiation only when the exponent is Natural-conforming (`k : Natural`, `p : Positive`, a - literal, or `+`/`*`/`%` over such). Both grammars admit only a literal or a feature - reference as a bound (`MultiplicityExpressionMember`): an arithmetic, cast, - parenthesised, chained or invoked bound such as `[2 ** n]` is a syntax error on both sides, so - the exponent typing above applies only to the tree recovered from that error and the difference - has no referee row; it is a reading of the library. + literal, or `+`/`*`/`%` over such). The pilot's grammar admits only a literal or a feature + reference as a bound (`MultiplicityExpressionMember`), so no arithmetic bound reaches its + validator and the difference has no referee row; it is a reading of the library. ### Binary-link specialization round @@ -3239,15 +3237,6 @@ redefinitions of the metadata definition's features (`MetadataBodyUsage` in the second members of those names. Fixtures: `testdata/passes/inherited_name_library_base.sysml` (positive, two warnings) and `..._clean.sysml` (negative, silent on both sides). -`testdata/passes/distinguishable_by_metaclass.sysml` (positive) and `..._clean.sysml` (negative) -join the corpus: 11 agreed rows on the first, where same or specializing metaclasses warn in both -tools, and 11 only-pilot's rows on the second, each a `Duplicate of …` warning between members -whose metaclasses conform in neither direction. Adjudicated against KerML §8.3.2.4.3 -`Membership::isDistinguishableFrom`: specification clear, pilot short (its -`// TODO: Add member element metaclass check`); see -[gap register #13](spec-pilot-gap-register.md#13-indistinguishable-memberships-severity-and-anonymous-performed-actions). -No other row moved. - Movement, against the clean-cache run this branch merges (the census `main` records): | Count | Control | This round | diff --git a/docs/project/pilot-rejection-baseline.json b/docs/project/pilot-rejection-baseline.json index 008a7910e6..64dca0299f 100644 --- a/docs/project/pilot-rejection-baseline.json +++ b/docs/project/pilot-rejection-baseline.json @@ -27,15 +27,15 @@ "name": "negative-corpus", "dir": "tools/referee/reject/testdata/negative", "origin": "ours", - "files": 312, - "digest": "sha256:1ecdd699da76c53d081e958a6c57be668f8f98d10bfe3e809f0189741253533a" + "files": 317, + "digest": "sha256:bfbaaaa8dcd73173f98774ddbd20f6124788fc7f377fb45a419f4dca3a8ef272" } ], - "recorded": "2026-10-04" + "recorded": "2026-10-05" }, "totals": { - "cases": 312, - "bothReject": 303, + "cases": 317, + "bothReject": 308, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 @@ -48,16 +48,16 @@ "sources": [ { "source": "extensions", - "cases": 10, - "bothReject": 10, + "cases": 11, + "bothReject": 11, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 }, { "source": "grammar", - "cases": 108, - "bothReject": 108, + "cases": 112, + "bothReject": 112, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 @@ -187,6 +187,17 @@ "pilotErrors": 3, "defaultBucket": "pilot-only-rejects" }, + { + "path": "extensions/x11-target-succession-outside-action-body.sysml", + "source": "extensions", + "rule": "TargetSuccessionMember is an ActionBodyItem; a view body admits no 'then \u003ctarget\u003e;' succession (SysML.xtext:1367 ActionBodyItem, :1693 TargetSuccessionMember, :2359 ViewBodyItem).", + "mode": "strict", + "bucket": "both-reject", + "oursErrors": 2, + "pilotErrors": 2, + "defaultBucket": "both-reject", + "defaultErrors": 1 + }, { "path": "grammar/g01-unclosed-package-body.sysml", "source": "grammar", @@ -916,6 +927,42 @@ "oursErrors": 1, "pilotErrors": 3 }, + { + "path": "grammar/g82-frame-in-view-body.sysml", + "source": "grammar", + "rule": "FramedConcernMember is a RequirementBodyItem; a view body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g83-frame-in-view-def-body.sysml", + "source": "grammar", + "rule": "FramedConcernMember is a RequirementBodyItem; a view def body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2329 ViewDefinitionBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g84-filter-outside-package-or-view-body.sysml", + "source": "grammar", + "rule": "ElementFilterMember is a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem; a rendering def body admits no 'filter' (SysML.xtext:227 ElementFilterMember, :2329 ViewDefinitionBodyItem, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g85-accept-outside-action-body.sysml", + "source": "grammar", + "rule": "AcceptNode is an ActionBodyItem; a view body admits no 'accept' (SysML.xtext:1367 ActionBodyItem, :1434 AcceptNode, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, { "path": "grammar/k01-specializes-without-target.kerml", "source": "grammar", @@ -3037,8 +3084,8 @@ } ], "totals": { - "cases": 312, - "bothReject": 303, + "cases": 317, + "bothReject": 308, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 diff --git a/docs/project/pilot-rejection.md b/docs/project/pilot-rejection.md index 20ff07ed47..9020adf8fb 100644 --- a/docs/project/pilot-rejection.md +++ b/docs/project/pilot-rejection.md @@ -116,7 +116,7 @@ systematically from four sources, one subdirectory each: pilot's grammar rejects before its validator would), and a constructed payload whose `new` names a package rather than a type (`send-constructor-non-type`). -What this corpus cannot see: it tests the invalid models we thought to write. **We authored all 312 +What this corpus cannot see: it tests the invalid models we thought to write. **We authored all 317 cases ourselves**, so the denominator measures our coverage of the rejection surface, not our conformance: it is a **sample, not a proof** — a clean bucket here does not mean OpenSysML rejects everything the reference rejects, and no official conformance suite exists to make that claim @@ -164,14 +164,14 @@ measured at their own round and are not the current baseline. Under the default `-conformance auto`: ``` -312 case(s): 303 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept +317 case(s): 308 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept of which 3 agree only because we were asked strictly (the default mode accepts them, by design) ``` | Source | Cases | Both reject | Pilot only | Ours only | Both accept | | --- | --- | --- | --- | --- | --- | -| extensions | 10 | 10 | 0 | 0 | 0 | -| grammar | 108 | 108 | 0 | 0 | 0 | +| extensions | 11 | 11 | 0 | 0 | 0 | +| grammar | 112 | 112 | 0 | 0 | 0 | | semantic | 159 | 150 | 0 | 9 | 0 | | xpect | 35 | 35 | 0 | 0 | 0 | @@ -236,6 +236,9 @@ The action-body parameter prefix-order cases `g80`–`g81` then raised the corpu `allocate ToastBread::applyHeat to Toaster::heating`; a connector with no featuring type reaches only features that have none, so both validators reject each end under `validateConnectorTypeFeaturing`) to 311, and `x10` (KerML's `connector` declared in a `.sysml` file, which the pinned SysML grammar has no production for — `no viable alternative at input 'c'` — and which we warn as `kerml-notation` by default and reject under strict) to 312. +The owning-body members `g82`–`g85` (`frame` in a view and a view def body, `filter` in a +rendering body, `accept` in a view body — each a syntax error under the pinned grammar) and `x11` +(`then ;` in a view body, a succession member no non-action body admits) brought it to 317. The KerML constraints in that source reopened 14 gaps — all of them semantic rules the pilot enforces and we did not; the named-argument validation that landed alongside closed one of them (`k33`), the constructor @@ -280,7 +283,7 @@ when it was first written, six were closed by the validation work itself — `p0 Read those two as agreement *when asked strictly*, not as gaps that disappeared. An opt-in check is weaker evidence than a default one: it says the strict question has an answer we agree on, not that the pipeline a user gets by default rejects the notation — by design it does not. And -because we authored all 312 cases ourselves, a small gap count means we ran out of questions we +because we authored all 317 cases ourselves, a small gap count means we ran out of questions we thought to ask, not that we stopped being permissive: the denominator measures our coverage of the rejection surface, not our conformance. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 15858e34ab..bec23419b8 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2111,7 +2111,7 @@ semantics layer over the conjugation parity of the typing/specialization chain. | A diagram node's label follows the graphical notation's header (SysML v2 §8.2.2.2): the element's name first, with ` : Type` after it for a typed usage, the kind on the next line in guillemets, and notes after that; an anonymous element leads with its kind and has no keyword line. The declared type is carried separately as `Node.Type`, the detail carries notes alone, and no writer parses type out of detail. Mermaid flowchart nodes and subgraph titles use Markdown only when every label line is safe: head lines are bold, the keyword is italic, details are plain, separated by real newlines; punctuation, list-like prefixes, non-multiplicity `*` and non-intraword `_` fall back to the escaped plain label. State, sequence and edge labels remain unchanged; flowchart titles add `subGraphTitleMargin` frontmatter when necessary. DOT uses HTML-like labels, PlantUML uses creole, and text keeps keyword-leading declaration order | `view/label.go` `labelHead`, `labelLines`; `view/mermaid.go` `mermaidLabel`, `writeFlowchartFrontmatter`; `view/dot.go` `dotLabel`, `dotEscape`, `dotLabelExtent`; `view/plantuml.go` `plantumlLabel`, `plantumlText`; `view/text.go` `writeNodeText`; `view/view.go` `Node.Type`; `lsp/render.go` `renderNode.Type`; `editors/vscode/src/protocol.ts` `RenderNode.type` | `view/label_test.go`; `view/mermaid_test.go:TestMermaidMarkdownLabelsFallBackWhenUnsafe`; `view/render_test.go:TestMermaidLabelsAreEscaped`; `view/testdata/*.{mermaid,dot,plantuml,text}.golden`; `docrender/testdata/telescope_report.*.golden.*` | ✅ Faithful to the notation; Mermaid Markdown labels fall back when unsafe | | `%render [text\|mermaid\|markdown\|dot\|plantuml\|d2\|csv\|tsv [palette]]` renders a declared or pseudo-view at the prompt; `sysml -render [-render-form
] [-render-palette ] [-o file]` renders one outside it, and `sysml -render-all [-render-form ]` writes every declared view separately | `repl/view.go` `Session.ViewRendering`, `renderPseudoView`, `Views`, `renderLines`, `viewRenderer` (a semantic model and resolver of its own over the session's symbol index, so no runtime is built); `model/selection.go` shares declaration-order view enumeration and targetless top-level selection across model and session paths; `repl/meta.go` (the `%render` arm, help entry), `repl/complete.go` (the form completion); `cmd/sysml/render.go` `runRender`, `runRenderAll`, `loadRenderingModel`, `writeArtifactFile` (one artifact on stdout or `-o`, or one file per declared view with render-all reporting on stderr); `cmd/sysml/main.go` (rendering modes are mutually exclusive with conversion and checks) | `repl/view_render_test.go` `TestRenderDefaultsToATree`, `TestRenderWritesMermaidWhenAskedFor`, `TestRenderOfATabularView`, `TestRenderOfAnUnknownNameReports`, `TestPseudoViewsRenderThroughTheSession`, `TestPseudoViewErrorsUseTheSessionLookupAndListAlternatives`, `TestTargetlessPseudoViewSpansLoadedDocuments`, `TestViewsListsSessionViewsInDeclarationOrder`, `TestRenderMisuseShowsUsage`, `TestRenderIsInHelpAndCompletion`, `TestRenderBetweenStepsDisturbsNothing`; `cmd/sysml/render_test.go` `TestRenderWritesTheArtifactOnStdout`, `TestRenderTextFormAndOutputFile`, `TestRenderOfATabularView`, `TestRenderAllWritesOneMachineArtifactPerView`, `TestRenderAllForcedTextUsesTxtAndUnboundedWidth`, `TestRenderAllWithoutViewsIsUnevaluable`, `TestRenderAllMutualExclusions`, `TestRenderOfATableAsDelimitedValues`, `TestRenderAllForcedDelimitedFormWritesTablesOnly`; `view/delimited_test.go` | ✅ Faithful (`%render` is a read: it materializes nothing, adds no object to the session, leaves the submission buffer alone and leaves an `%action`/`%state` debugging session stepping the same graph and objects — pinned by `TestRenderBetweenStepsDisturbsNothing`. A targeted pseudo-view uses the session's normal simple or qualified lookup; a targetless one renders loaded documents in order. `%view`'s report is unchanged) | | Diagram geometry in notation — an OpenSysML extension, not a specification rule: `DiagramLayout::Layout` (`x`, `y`, optional `width`, `height`, `collapsed`) positions the element it annotates, `DiagramLayout::Route` (`points`, a flattened `x0, y0, x1, y1, …` sequence, `ordered nonunique` since a segment repeats a coordinate) steers the edge whose declaring element it annotates, `DiagramLayout::Canvas` (`unit`, `width`, `height`) sizes a view's drawing surface; pixels, y down, origin top-left. For a (view, element) pair an `about` annotation declared in the view's body applies first, the element's own inline annotation second, none third; a view's second `about` annotation of one kind for one element is ignored in favor of the first. The geometry rides the rendering tree as values (`view.Node.Geometry`, `view.Edge.Route`, `view.Rendering.Canvas`) in every graph-shaped rendering — tree, interconnection, state, action — through `Render` and `RenderExposed` (no view, so element fallback only); the sequence diagram and the table carry none. The writers keep it visible: Mermaid as `%% canvas:`, `%% layout:` and `%% route:` comments after the header, text as `at (x, y)`, `size w×h`, `collapsed` and `via (x, y) …` suffixes, `opensysml/render` as optional `x`/`y`/`width`/`height`/`collapsed`/`route`/`canvas`; a model without annotations renders byte-identically | `libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml`; `semantics/layout.go` `Model.LayoutSitesOf`, `LayoutOf`, `RouteOf`, `CanvasOf` (lazy, memoized, over the metadata side table `ElementMetadataOf`/`AnnotationSitesOf`); `semantics/masking.go` `directRedefinedFeatures` → `implicitMetadataBodyRedefinitions` (a metadata body's `points = (…)` is judged by the redefined `nonunique` feature); `ast/metadata.go` `DeclaredMetadata` (transition and succession bodies); `lower/state_graph.go` `StateGraph.DeclOf`, `lower/action_graph.go` (a succession body of annotations only); `view/geometry.go` `Point`, `Geometry`, `Canvas`, `Renderer.geometryOf`/`routeOf`/`canvasOf`, `view/place.go` `Renderer.Draws`/`DrawsAnywhere`, `view/clone.go`, `view/mermaid.go`, `view/text.go`; `lsp/render.go`; `passes/diagram_layout.go` `DiagramLayoutPass` at `LevelConstraint` (`diagram-layout-unplaced` warning for a `Layout`/`Route` on an element the judged rendering draws no node/edge for, `diagram-layout-value` error for odd `points` or a non-constant binding, `diagram-layout-canvas` error outside a view, `diagram-layout-duplicate` warning; source `constraint`) | `parse/view_layout_metadata.golden`; `semantics/layout_test.go` `TestLayoutOfPrefersTheViewBodyOverTheInlineAnnotation`, `TestLayoutOfPlacesAnElementInOneViewOnly`, `TestLayoutOfReadsAnAboutAnnotationNestedInTheViewBody`, `TestLayoutOfFirstViewLocalAnnotationWins`, `TestRouteOfReadsWaypointPairs`, `TestRouteOfReportsAnOddPointCount`, `TestLayoutOfReportsANonConstantBinding`, `TestCanvasOfReadsTheViewsCanvas`; `semantics/uniqueness_test.go` `TestIsUniqueInheritsIntoMetadataBody`; `view/geometry_test.go` (view-local over inline, one view of two, connection/transition/succession routes, canvas, writers, `RenderExposed`, clone, no-annotation byte identity) with `view/testdata/layout.{text,mermaid}.golden`; `lower/action_succession_test.go`; `lsp/render_test.go` `TestRenderCarriesLayoutGeometry`; `passes/diagram_layout_test.go` (one test per diagnostic); `cmd/sysml/check_test.go` `TestCheckReportsDiagramLayoutFindings`; `repl/view_layout_test.go` | ⚠️ Approximate (self-assessed: nothing in the specification to be faithful to — the notation is standard user-defined metadata, the meaning is OpenSysML's, proposed for standardization in `omg-issues.md`. No writer of this build lays a diagram out from the positions: Mermaid and text keep them visible, a layout-honoring form is pending, as is write-back from a graphical editor. Positions are absolute, so a container's children are not repositioned with it) | -| A view's conformance to the viewpoints it satisfies is evaluated (SysML v2 7.24 Views and Viewpoints; a concern is a requirement, so its conditions are evaluated by the requirement engine) | `semantics/conformance.go` `Model.ViewConformance` (per satisfy claiming conformance — `IsViewpointSatisfy`, since a satisfy stating a subject asserts its requirement of that subject instead, as the stdlib `View` does: `Model.SatisfyTarget`, then every concern `Model.FramedConcernsOf` reports for the viewpoint, matched against the view's own, inherited and nested framings, and evaluated against the exposed elements the concern's subject admits through the `ConcernEvaluator` the caller supplies — implemented in `repl/view.go` `concernEvaluator` over `runtime.Context.CheckSatisfactionOn`, so condition evaluation is not reimplemented); `passes/constraint.go` `checkViewSatisfyTarget` (code `view-satisfy-viewpoint`) | `semantics/conformance_test.go` (framing direct, inherited, nested and inherited-from-a-viewpoint; a missing concern; a false condition; an unevaluable one; no admissible subject; an unresolved stakeholder; a satisfy that is no viewpoint; determinism), `passes/constraint_test.go` `TestConstraintViewSatisfyNonViewpointRequirement`, `TestConstraintViewSatisfyViewpointOK`, `TestConstraintSatisfyOutsideAViewIsNotChecked`, `TestConstraintViewSatisfyRequirementBySubjectOK`, `conformance/viewpoint_framed_concern_conditions.sysml`, `viewpoint_concern_without_condition.sysml` | ⚠️ Approximate (the structural question — is every framed concern framed by the view — and the conditions are evaluated faithfully; the **verdict rules are tool-defined**, since SysML v2 leaves verification verdicts non-normative: a concern must hold of *every* exposed element its subject admits, a concern framed by a nested view counts for its container, and what cannot be evaluated — no condition, no admissible subject, an unresolved party, a framing whose concern reference does not resolve, a viewpoint framing no concern at all — is reported as unevaluable with a reason, never as a pass. **Adjudicated: left as found, deliberately.** This row states no name-resolution rule; its verdict rules are non-normative, so no referee can promote it, and its implementation is in `semantics/`, which this change did not own) | +| A view's conformance to the viewpoints it satisfies is evaluated (SysML v2 7.24 Views and Viewpoints; a concern is a requirement, so its conditions are evaluated by the requirement engine) | `semantics/conformance.go` `Model.ViewConformance` (per satisfy claiming conformance — `IsViewpointSatisfy`, since a satisfy stating a subject asserts its requirement of that subject instead, as the stdlib `View` does: `Model.SatisfyTarget`, then every concern `Model.FramedConcernsOf` reports for the viewpoint, evaluated against the exposed elements the concern's subject admits through the `ConcernEvaluator` the caller supplies — a view frames nothing of its own, a view body admitting no `frame` member — implemented in `repl/view.go` `concernEvaluator` over `runtime.Context.CheckSatisfactionOn`, so condition evaluation is not reimplemented); `passes/constraint.go` `checkViewSatisfyTarget` (code `view-satisfy-viewpoint`) | `semantics/conformance_test.go` (a framed concern evaluated against the exposed elements; a false condition; an unevaluable one; no admissible subject; a framing inherited from a supertype viewpoint; an unresolved stakeholder; a satisfy inherited from a view def; a satisfy that is no viewpoint; determinism), `passes/constraint_test.go` `TestConstraintViewSatisfyNonViewpointRequirement`, `TestConstraintViewSatisfyViewpointOK`, `TestConstraintSatisfyOutsideAViewIsNotChecked`, `TestConstraintViewSatisfyRequirementBySubjectOK`, `conformance/viewpoint_framed_concern_conditions.sysml`, `viewpoint_concern_without_condition.sysml` | ⚠️ Approximate (every concern the viewpoint frames is evaluated against the exposed elements; the **verdict rules are tool-defined**, since SysML v2 leaves verification verdicts non-normative: a concern must hold of *every* exposed element its subject admits, and what cannot be evaluated — no condition, no admissible subject, an unresolved party, a framing whose concern reference does not resolve, a viewpoint framing no concern at all — is reported as unevaluable with a reason, never as a pass. **Adjudicated: left as found, deliberately.** This row states no name-resolution rule; its verdict rules are non-normative, so no referee can promote it, and its implementation is in `semantics/`, which this change did not own) | | A filter condition is a model-level predicate over one candidate element, with the candidate as the implicit `self` (`@Safety`, `@@Safety`, `and`/`or`/`xor`/`not`/`implies`, and a comparison of an annotation's feature) | `semantics/filter.go` `Model.CompileElementFilter` / `EvalElementFilter` / `SatisfiesElementFilter` (compiled once per condition, memoized per candidate) over the annotations `semantics/annotations.go` collects — prefix metadata (`#Safety part def P;`), a `metadata` member of the element's body, and `metadata m about X;` — with conformance through `Model.AllSupertypes`, so `@Safety` matches a metadata type specializing `Safety`, and a metaclass classification (`@SysML::PartUsage`) reads the candidate's own kind | `semantics/filter_test.go`, `model/element_filter_test.go`, `semantics/cached_library_test.go` `TestNamespaceFilterOverALibraryIsTheSameParsedAndRestored` (the condition as parsed and the same condition compiled into an index-cache record classify alike), `TestAnnotationFactsOfALibraryElementSurviveTheCache` | ✅ Faithful (evaluated against a *symbol*, not a value: there is no instance at name-resolution time, which is what a model-level condition is defined over. A KerML declaration keyword implies its metaclass (`struct` → `Structure`, `class` → `Class`, `assoc` → `Association`, `datatype` → `DataType`, and the rest of KerML §8.2: `semantics/annotations.go` `kermlMetaclassNames`), and a metaclass *feature* (`Element::name`, `Type::isAbstract`, `Feature::isComposite`) is answered from the candidate element itself (`reflectiveFeatureValue`), from an annotation only when one binds it. A feature of a metaclass the candidate *is* an instance of but whose value no declaration gives is `ErrFilterUnevaluable` — reported through `filter-not-evaluable`, never decided false, which is the distinction that was dropping candidates and reporting their names unresolved (`kerml-examples` `Simple Tests/Filtering.kerml:38,47,48`, now clean, as the pinned `validate-kerml` is). Refereed: the pinned validators are silent on `testdata/passes/f93_element_filter.{kerml,sysml}` and on `Filtering.kerml`, and so are we (`passes/f93_element_filter_scope_test.go`). `@`/`@@` in the *runtime* value evaluator remains unimplemented, line 214 — a separate layer, `runtime/`) | | A filter condition that is not boolean-valued, or that OpenSysML cannot evaluate | `passes/filter.go` `ElementFilterPass` at `LevelType`, over `semantics/filter.go` `Model.CheckElementFilter`; codes `filter-not-boolean` (error — a condition that is not a predicate can select nothing) and `filter-not-evaluable` (warning). Each fault is reported once, on the membership stating the condition (KerML §8.2.4), however many operands carry it | `passes/filter_test.go` `TestFilterNotBooleanIsReported`, `TestFilterNotEvaluableIsReported`, `TestFilterConditionsInTheSupportedSubsetAreClean` | ✅ Faithful (an unevaluable condition is **reported and not applied**, so the elements it would have selected from all stay visible: a verdict OpenSysML could not reach never silently hides model content — maintainer decision, recorded here because the spec does not say). A feature bound to nothing is *not* that case: reading it yields the empty sequence, which an ordering propagates and `==`/`!=` decide against any value (KerML `DataFunctions`, where an ordering takes `DataValue[1]` and equality `[0..1]`), so `@Safety and Safety::isMandatory == true` does not hold of an element annotated `@Safety` alone and does not surface it — a verdict, not a reported failure. An annotation inherits the values its metadata type declares, so a default the type gives the feature is what the comparison reads (`semantics/annotations.go` `addTypeDefaults`; `semantics/filter_test.go` `TestFilterUnsetAnnotationFeature`, `TestFilterAnnotationFeatureDefault`, `model/element_filter_test.go` `TestFilteredImportRejectsAnUnboundAnnotationFeature`) | | A KerML declaration specializes the library type its keyword implies (`class` → `Occurrences::Occurrence`, `struct` → `Objects::Object`, `assoc`/`association` → `Links::Link`, `behavior` → `Performances::Performance`, `function`/`predicate` → the matching evaluation, `interaction` → `Links::Link` as an association (KerML 1.1 §8.3.5.4; `Links::BinaryLink` with two ends), `metaclass` → `Metaobjects::Metaobject`, `datatype` → `Base::DataValue`, `classifier`/`type` → `Base::Anything`), so the library members it implies are inherited (KerML 1.0 §8.4.2) | `semantics/implicit.go` `implicitKerMLBases`, read by `implicitBase` when `source.KindOf` says the document is KerML (the same file-kind mechanism, no second notion) — a bare `feature` is left with no base rather than being given the SysML `attribute` one; `libs/record.go` (cache format 17) stores the semantic supertype edges so a restored library symbol inherits alike | `semantics/implicit_test.go:TestKerMLImplicitDefinitionBases`, `:TestSysMLImplicitDefinitionBasesRemainKindBased`, `semantics/cached_library_test.go:TestInheritedImplicitBaseIsTheSameParsedAndRestored`, `libs/loader_cache_test.go:TestLoaderCachePreservesRedefinitionMemberEdges` | ✅ Faithful, self-assessed (the keyword table covers the KerML §8.2 declaration kinds, and the implied *feature* bases §8.4.2 also defines are now modelled: `implicitKerMLFeatureBases` maps a feature keyword to the base feature it subsets — `step`/`behavior` → `Performances::performances`, `expr`/`function` → `Performances::evaluations`, `bool`/`predicate` → `booleanEvaluations`, `inv` → `trueEvaluations`, `class` → `Occurrences::occurrences`, `struct` → `Objects::objects`, `datatype` → `Base::dataValues`, `assoc`/`connector` → `Links::links`, `binding` → `Links::selfLinks`, `succession` → `Occurrences::happensBeforeLinks`, `interaction` → `Transfers::transfers`, `flow` → `Transfers::flowTransfers`, `metaclass` → `Metaobjects::metaobjects` — contributed as members, like the base usage, not as conformance. Every name in both tables is checked against what the bundled library declares (`semantics/w7a_library_bases_test.go:TestW7AImplicitBaseTablesNameLibraryElements`) and the member contribution by `implicit_test.go:TestW7AKerMLFeatureBaseContributesMembers`. **Evidence is KerML §8.4.2 plus these tests, not a referee**: an implicit base surfaces in no reference diagnostic — a probe declaring the keyword forms is accepted by `validate-kerml` and by us alike — so no matched run can settle it. The feature table is read for feature declarations only: a keyword that declares a *type* — `class`, `struct`, `assoc`, `behavior`, `predicate`, `interaction` — takes its base from the type table above and nothing from this one) | diff --git a/docs/reference/repl-commands.md b/docs/reference/repl-commands.md index 7760225361..f9650dd421 100644 --- a/docs/reference/repl-commands.md +++ b/docs/reference/repl-commands.md @@ -377,8 +377,6 @@ sysml> package Demo { ...> viewpoint structure : StructurePerspective; ...> view def StructureView { ...> satisfy structure; - ...> frame concern budget : MassBudget; - ...> frame concern modularity : Modularity; ...> } ...> view report : StructureView { ...> expose vehicle; diff --git a/examples/disposal-robot-demo/robot.sysml b/examples/disposal-robot-demo/robot.sysml index 801fc9747e..8bed02ef94 100644 --- a/examples/disposal-robot-demo/robot.sysml +++ b/examples/disposal-robot-demo/robot.sysml @@ -615,13 +615,13 @@ package RobotViews { } // States no rendering, so a tree renders it, with the interconnection view - // nested. `%view` checks the robots exposed against the concern framed here. + // nested. `%view` checks the robots exposed against the concern the + // satisfied viewpoint frames. view overview { expose Robot::fielded; expose Robot::heavyMockup; view interfaceSubview :> interfaces; satisfy operationsPerspective; - frame concern mass : MassBudget; } } diff --git a/examples/self-model/views.sysml b/examples/self-model/views.sysml index 8aeebd4d43..1dc6842319 100644 --- a/examples/self-model/views.sysml +++ b/examples/self-model/views.sysml @@ -194,6 +194,5 @@ package OpenSysMLViews { view pipelineSubview :> pipelineStructure; satisfy maintainerPerspective; - frame concern latency : OpenSysMLInvariants::KeystrokeLatency; } } diff --git a/examples/views-demo.sysml b/examples/views-demo.sysml index df096022af..8ae6f9cc38 100644 --- a/examples/views-demo.sysml +++ b/examples/views-demo.sysml @@ -170,9 +170,8 @@ package LanderViews { expose Lander::heavyDescender; view interfaceSubview :> interfaces; - // What `%view` checks the exposed landers against: the concern the - // viewpoint frames, framed by this view too. + // `%view` checks the exposed landers against the concern the + // satisfied viewpoint frames. satisfy massPerspective; - frame concern mass : Lander::MassBudget; } } diff --git a/internal/check/passes/nonstandard_notation.go b/internal/check/passes/nonstandard_notation.go index 590022fb5a..062315b657 100644 --- a/internal/check/passes/nonstandard_notation.go +++ b/internal/check/passes/nonstandard_notation.go @@ -96,6 +96,10 @@ type notationWalker struct { // inActionBody records that the body being walked admits ActionBodyItem // members (SysML.xtext:1367). inActionBody bool + // inStateBody records that the body being walked is a state body, whose + // one-name `then ;` is a target transition rather than a target + // succession (SysML.xtext TargetTransitionUsage). + inStateBody bool // inViewDefBody records that the body being walked is a ViewDefinitionBody // (SysML.xtext ViewDefinitionBodyItem), which admits no Expose. inViewDefBody bool @@ -238,13 +242,14 @@ func (w *notationWalker) walk(members []ast.Node) { // walkDeclaration walks the body of a declaration under the body kind that // declaration opens. func (w *notationWalker) walkDeclaration(members []ast.Node, declaration ast.Node) { - action, viewDef := w.inActionBody, w.inViewDefBody + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody w.inActionBody = admitsActionBodyItems(declaration) w.inViewDefBody = isViewDefinition(declaration) + w.inStateBody = isStateDeclaration(declaration) w.bodies = append(w.bodies, bodyFrame{members: members}) w.walk(members) w.bodies = w.bodies[:len(w.bodies)-1] - w.inActionBody, w.inViewDefBody = action, viewDef + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state } // walkPackageMembers walks a namespace or package body, the member lists a fix @@ -272,6 +277,29 @@ func isViewDefinition(node ast.Node) bool { return ok && def.Kind == ast.DefView } +// walkStateBody walks a state body's members, where a one-name `then +// ;` spells the target of an entry transition. +func (w *notationWalker) walkStateBody(members []ast.Node) { + if len(members) == 0 { + return + } + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody + w.inActionBody, w.inViewDefBody, w.inStateBody = false, false, true + w.walk(members) + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state +} + +// isStateDeclaration reports whether the declaration opens a state body. +func isStateDeclaration(node ast.Node) bool { + switch n := node.(type) { + case *ast.Definition: + return n.Kind == ast.DefState + case *ast.Usage: + return n.Kind == ast.UsageState + } + return false +} + // admitsActionBodyItems reports whether the body a declaration opens is an // ActionBody (SysML.xtext:1361) or one that includes ActionBodyItem: a // CalculationBody (:1947) or a CaseBody (:2183). @@ -305,13 +333,25 @@ func admitsActionBodyItems(node ast.Node) bool { // usage the pilot has no production for it and its source is unstated. func (w *notationWalker) targetSuccession(n, previous ast.Node) { keyword := targetSuccessionKeyword(n) - if keyword == "" || !w.sysml || previous == nil || admitsTargetSuccession(previous) { + if keyword == "" || !w.sysml { return } notation := "`" + keyword + " ;`" if keyword == "if" { notation = "`if then ;`" } + // TargetSuccessionMember is an ActionBodyItem (SysML.xtext:1367), and a + // state body reads the same notation as a target transition; anywhere else + // the member sequences nothing the grammar admits. + if !w.inActionBody && !w.inStateBody { + w.extension(keywordSpan(n, keyword), notation+" outside an action or state body", + "a target succession sequences members of an action, calculation or case body, "+ + "and a state body reads it as a target transition; this body admits neither") + return + } + if previous == nil || admitsTargetSuccession(previous) { + return + } w.extension(keywordSpan(n, keyword), notation+" after a member that is not an action node", "a target succession sequences from the member written right before it, so only an action node, "+ "a behavior usage, a one-ended `first ;` or another target succession may precede it") @@ -409,9 +449,9 @@ func (w *notationWalker) stateNode(n *ast.StateNode) { w.walkActionBody(n.Entry) w.walkActionBody(n.Do) w.walkActionBody(n.Exit) - w.walk(n.Substates) + w.walkStateBody(n.Substates) for _, region := range n.Regions { - w.walk([]ast.Node{region}) + w.walkStateBody(region.States) } } diff --git a/internal/check/passes/nonstandard_notation_test.go b/internal/check/passes/nonstandard_notation_test.go index f9538829e8..97966bd1d7 100644 --- a/internal/check/passes/nonstandard_notation_test.go +++ b/internal/check/passes/nonstandard_notation_test.go @@ -267,6 +267,29 @@ func TestTargetSuccessionAfterANonActionMemberIsAnExtension(t *testing.T) { wantSilent(t, "a.kerml", "behavior A { step a; then a; }") } +// TargetSuccessionMember is an ActionBodyItem, and a state body reads +// `then ;` as a target transition; in a body that admits neither it is +// an extension whatever member precedes it. +func TestTargetSuccessionOutsideAnActionOrStateBodyIsAnExtension(t *testing.T) { + const outside = "`then ;` outside an action or state body" + wantNotation(t, "a.sysml", "view v { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "view def V { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "part def P { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "viewpoint def V { frame f; then y; }", CodeNonstandardNotation, outside) + + got := notationDiags(t, "a.sysml", "view v { action x; then y; }", diag.ConformanceStrict) + if len(got) != 1 || got[0].Severity != diag.SeverityError || !got[0].Notation { + t.Fatalf("strict got %+v, want one notation error", got) + } + + wantSilent(t, "a.sysml", "action def A { action x; then y; }") + wantSilent(t, "a.sysml", "action def A { action x; if true then y; else y; }") + wantSilent(t, "a.sysml", "state def S { state x; then y; state y; }") + wantSilent(t, "a.sysml", "state s { state x; then t; state t; }") + wantSilent(t, "a.sysml", "action def A { action x; then action y; }") + wantSilent(t, "a.sysml", "action def A { action x; action y; first x then y; }") +} + // RequirementConstraintMember belongs to a RequirementBody; anywhere else the // parser rejects `assume`/`require` outright (see the negative parser tests). func TestRequirementConstraintInsideARequirementBodyIsSilent(t *testing.T) { diff --git a/internal/exec/runtime/signal_test.go b/internal/exec/runtime/signal_test.go index 98628f6ce5..75bd93c0e4 100644 --- a/internal/exec/runtime/signal_test.go +++ b/internal/exec/runtime/signal_test.go @@ -1685,9 +1685,9 @@ func TestAddressedSendToAnObjectNeedsThatObject(t *testing.T) { func TestAddressedSendToReceiverOfAnotherObjectCarriesItsIdentity(t *testing.T) { idx, _, ctx := buildRuntime(t, "", parseAndBuild(t, `package test { private import ScalarValues::*; - part def Node { action reader accept n : Integer; } + part def Node { action reader { accept n : Integer; } } part def Talker { - action reader accept n : Integer; + action reader { accept n : Integer; } action talk { first start; done; succession first start then done; } } part alpha : Node; @@ -1987,7 +1987,7 @@ func TestAddressedSendToQualifiedElementOfATwinObject(t *testing.T) { port def PingPort { in item ping : Integer; } part def Node { port inPort : PingPort; - action reader accept n : Integer; + action reader { accept n : Integer; } action listen { first start; done; succession first start then done; } } part alpha : Node; diff --git a/internal/frontend/repl/view_test.go b/internal/frontend/repl/view_test.go index 6a3545cbe5..14390ac31f 100644 --- a/internal/frontend/repl/view_test.go +++ b/internal/frontend/repl/view_test.go @@ -158,8 +158,6 @@ const conformanceModel = `package Demo { view report : StructureView { expose Demo::vehicle; expose Demo::wheel; - frame concern modularity : Modularity; - frame concern documented : Documented; } }` @@ -176,7 +174,7 @@ func conformanceSession(t *testing.T) *Session { } // %view reports the conformance of a view to the viewpoint it satisfies: a -// concern that holds, one that does not, one the view never framed, and where +// concern that holds, one that does not, one stating no condition, and where // the satisfy came from. func TestViewReportsViewpointConformance(t *testing.T) { out, _, err := conformanceSession(t).RunMeta("%view Demo::report") @@ -187,7 +185,7 @@ func TestViewReportsViewpointConformance(t *testing.T) { for _, want := range []string{ "viewpoint conformance", "satisfy structure (from Demo::StructureView)", - "concern budget: violated (framed by the viewpoint but not by the view)", + "concern budget: violated", "concern modularity: conforms", "concern documented: unevaluable", "states no condition to evaluate", @@ -209,9 +207,6 @@ func TestViewReportsAViolatedConcernPerElement(t *testing.T) { private import Demo::*; view budgeted : StructureView { expose Demo::vehicle; - frame concern budget : MassBudget; - frame concern modularity : Modularity; - frame concern documented : Documented; } }`) for _, d := range res.Diagnostics { @@ -315,7 +310,6 @@ func TestViewLeavesNoAmbiguityForALaterCheck(t *testing.T) { view carView : StructureView { expose Checked::car; expose Checked::spare; - frame concern modularity : Modularity; } }`) for _, d := range res.Diagnostics { @@ -373,7 +367,7 @@ func TestViewSharesTheObjectOfAQuotedName(t *testing.T) { res := s.Submit(`package Quoted { private import Demo::*; part 'road car' : Vehicle; - view quotedView : StructureView { expose Quoted::'road car'; frame concern modularity : Modularity; } + view quotedView : StructureView { expose Quoted::'road car'; } }`) for _, d := range res.Diagnostics { if d.Severity == diag.SeverityError { @@ -421,7 +415,7 @@ func TestViewChecksAConcernWhoseSubjectIsAnything(t *testing.T) { concern def Named { subject s : Base::Anything; require constraint { 1.0 > 2.0 } } viewpoint def AnyPerspective { frame concern named : Named; } viewpoint anything : AnyPerspective; - view anyView { expose Demo::wheel; satisfy anything; frame concern named : Named; } + view anyView { expose Demo::wheel; satisfy anything; } }`) for _, d := range res.Diagnostics { if d.Severity == diag.SeverityError { diff --git a/internal/semantic/resolve/accept_payload_test.go b/internal/semantic/resolve/accept_payload_test.go index 4ffbe9d703..834e6b6dae 100644 --- a/internal/semantic/resolve/accept_payload_test.go +++ b/internal/semantic/resolve/accept_payload_test.go @@ -184,14 +184,14 @@ func TestAcceptPayloadDoesNotEscapeBody(t *testing.T) { } } -// Only a behavior body shares a feature space: a part declaring an action node -// does not read that node's payload. +// Only a behavior body shares a feature space: a part whose action body +// declares an action node does not read that node's payload. func TestAcceptPayloadNotSharedByAPartBody(t *testing.T) { r := resolveDoc(t, "d.sysml", ` package P { item def Warning; part p { - action wait accept msg : Warning; + action a { action wait accept msg : Warning; } attribute seen = msg; } } diff --git a/internal/semantic/resolve/event_feature_test.go b/internal/semantic/resolve/event_feature_test.go index 50c7d06c98..4e4d726cd5 100644 --- a/internal/semantic/resolve/event_feature_test.go +++ b/internal/semantic/resolve/event_feature_test.go @@ -10,7 +10,7 @@ func TestResolveEventOccurrenceFeature(t *testing.T) { "occurrence def O; part def P { event occurrence o : O; }", "occurrence def O; part def P { event occurrence o : O; attribute a = o; }", "occurrence def O; part def P { occurrence o : O; event o; }", - "occurrence def O; part def P { event occurrence o : O; state s; state t; accept o then s; }", + "occurrence def O; part def P { event occurrence o : O; state s { state t; accept o then t; } }", "occurrence def O; action def A { event occurrence o : O; accept o; }", } { r := resolveDoc(t, "", src) diff --git a/internal/semantic/semantics/conformance.go b/internal/semantic/semantics/conformance.go index 072f6cdbc5..5482606c98 100644 --- a/internal/semantic/semantics/conformance.go +++ b/internal/semantic/semantics/conformance.go @@ -26,13 +26,13 @@ type Verdict int const ( // VerdictConforms is a question answered in the affirmative. VerdictConforms Verdict = iota - // VerdictViolated is a question answered in the negative: a concern the view - // does not frame, or a condition that evaluated to false. + // VerdictViolated is a question answered in the negative: a condition that + // evaluated to false. VerdictViolated // VerdictUnevaluable is a question no answer could be reached for. VerdictUnevaluable - // VerdictNotEvaluated is a structurally conforming concern whose conditions - // were not evaluated, because the caller supplied no evaluator. + // VerdictNotEvaluated is a concern whose conditions were not evaluated, + // because the caller supplied no evaluator. VerdictNotEvaluated ) @@ -78,10 +78,6 @@ type ConcernConformance struct { Target *symbols.Symbol // Name is how the framed concern is written, for reporting. Name string - // FramedBy is the view's framing of the same concern and FramedIn the view - // declaring it. Both are nil when the view does not frame the concern. - FramedBy *symbols.Symbol - FramedIn *symbols.Symbol // Checks are the per-element verdicts, in exposed-element order. Checks []ConcernCheck Verdict Verdict @@ -124,9 +120,9 @@ type ViewConformance struct { } // ViewConformance evaluates whether view conforms to the viewpoints its body -// satisfies: every concern a viewpoint frames must be framed by the view and -// must hold of what the view exposes. A nil evaluator answers the structural -// question alone; a non-view is ErrNotAView, a view satisfying nothing no error. +// satisfies: every concern a viewpoint frames must hold of what the view +// exposes. A nil evaluator answers the structural question alone; a non-view +// is ErrNotAView, a view satisfying nothing no error. func (m *Model) ViewConformance(view *symbols.Symbol, eval ConcernEvaluator) (*ViewConformance, error) { if view == nil || !IsView(view) { return nil, ErrNotAView @@ -136,9 +132,8 @@ func (m *Model) ViewConformance(view *symbols.Symbol, eval ConcernEvaluator) (*V return nil, err } out := &ViewConformance{View: view, Exposed: exposed, Verdict: VerdictConforms} - framings := m.viewFramings(view) for _, sat := range m.SatisfyMembersOf(view) { - out.Viewpoints = append(out.Viewpoints, m.viewpointConformance(view, sat, framings, exposed, eval)) + out.Viewpoints = append(out.Viewpoints, m.viewpointConformance(view, sat, exposed, eval)) } for _, vp := range out.Viewpoints { out.Verdict = worse(out.Verdict, vp.Verdict) @@ -169,8 +164,8 @@ func (m *Model) SatisfyMembersOf(view *symbols.Symbol) []*symbols.Symbol { return out } -// FramedConcernsOf returns the `frame` concern usages a viewpoint or view -// declares followed by those it inherits, in declaration order and once each. +// FramedConcernsOf returns the `frame` concern usages a viewpoint declares +// followed by those it inherits, in declaration order and once each. func (m *Model) FramedConcernsOf(sym *symbols.Symbol) []*symbols.Symbol { if sym == nil { return nil @@ -261,61 +256,8 @@ func (m *Model) declaredConcernTarget(sym *symbols.Symbol) *symbols.Symbol { return nil } -// viewFraming is one framing a view makes, and the view that declared it. -type viewFraming struct { - frame *symbols.Symbol - target *symbols.Symbol - in *symbols.Symbol -} - -// viewFramings collects a view's own framings, then its supertypes', then those -// of the views nested in it — a nested view frames for its container, since the -// tree as a whole is what the container's conformance addresses (tool-defined). -func (m *Model) viewFramings(view *symbols.Symbol) []viewFraming { - var out []viewFraming - seen := map[*symbols.Symbol]bool{} - var walk func(sym *symbols.Symbol, depth int) - walk = func(sym *symbols.Symbol, depth int) { - if sym == nil || seen[sym] || depth > 32 { - return - } - seen[sym] = true - out = append(out, m.framingsDeclaredBy(sym)...) - nested, err := m.NestedViews(sym) - if err != nil { - return - } - for _, child := range nested { - walk(child, depth+1) - } - } - walk(view, 0) - return out -} - -// framingsDeclaredBy returns the framings of a view and of the views it -// specializes, each attributed to the view that declares it. -func (m *Model) framingsDeclaredBy(view *symbols.Symbol) []viewFraming { - var out []viewFraming - seen := map[*symbols.Symbol]bool{} - add := func(owner *symbols.Symbol) { - for _, fc := range usageMembersOfKind(owner, ast.UsageFramedConcern) { - if seen[fc] { - continue - } - seen[fc] = true - out = append(out, viewFraming{frame: fc, target: m.FramedConcernTarget(fc), in: owner}) - } - } - add(view) - for _, super := range m.AllSupertypes(view) { - add(super) - } - return out -} - // viewpointConformance evaluates one satisfy member of a view. -func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewFraming, exposed []*symbols.Symbol, eval ConcernEvaluator) ViewpointConformance { +func (m *Model) viewpointConformance(view, sat *symbols.Symbol, exposed []*symbols.Symbol, eval ConcernEvaluator) ViewpointConformance { out := ViewpointConformance{Satisfy: sat, SatisfiedIn: sat.Owner()} target, ref := m.SatisfyTarget(sat) out.Ref = ref @@ -334,7 +276,7 @@ func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewF out.Parties = append(out.Parties, m.partyBindings(view)...) out.Verdict = VerdictConforms for _, fc := range m.FramedConcernsOf(target) { - out.Concerns = append(out.Concerns, m.concernConformance(fc, framings, exposed, eval)) + out.Concerns = append(out.Concerns, m.concernConformance(fc, exposed, eval)) } // A viewpoint framing no concern asks nothing of the view, so conforming to // it would be a verdict reached by checking nothing. @@ -354,27 +296,17 @@ func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewF return out } -// concernConformance answers whether the view frames one concern of the -// viewpoint and, when an evaluator is supplied, whether it holds of what the -// view exposes. -func (m *Model) concernConformance(fc *symbols.Symbol, framings []viewFraming, exposed []*symbols.Symbol, eval ConcernEvaluator) ConcernConformance { +// concernConformance answers whether one concern of the viewpoint holds of +// what the view exposes. A view frames nothing of its own — a view body admits +// no FramedConcernMember — so the concern is reported and, when an evaluator +// is supplied, evaluated. +func (m *Model) concernConformance(fc *symbols.Symbol, exposed []*symbols.Symbol, eval ConcernEvaluator) ConcernConformance { out := ConcernConformance{Concern: fc, Target: m.FramedConcernTarget(fc), Name: framedConcernName(fc)} if ref := m.unresolvedConcernRef(fc); ref != "" { out.Verdict = VerdictUnevaluable out.Reason = fmt.Sprintf("the framing names %s, which does not resolve", ref) return out } - for _, f := range framings { - if m.framesTheSame(out, f) { - out.FramedBy, out.FramedIn = f.frame, f.in - break - } - } - if out.FramedBy == nil { - out.Verdict = VerdictViolated - out.Reason = "framed by the viewpoint but not by the view" - return out - } if eval == nil { out.Verdict = VerdictNotEvaluated return out @@ -402,17 +334,6 @@ func (m *Model) unresolvedConcernRef(fc *symbols.Symbol) string { return "" } -// framesTheSame reports whether a view's framing frames the concern the -// viewpoint's does: the same concern, or one specializing the other. Where -// either side names no concern — an untyped `frame concern mass;` — the written -// name decides, which is all the two framings share (tool-defined). -func (m *Model) framesTheSame(c ConcernConformance, f viewFraming) bool { - if c.Target != nil && f.target != nil { - return c.Target == f.target || m.Conforms(f.target, c.Target) || m.Conforms(c.Target, f.target) - } - return c.Target == f.frame || (c.Name != "" && c.Name == framedConcernName(f.frame)) -} - // evaluateConcern evaluates a framed concern against the exposed elements its // subject admits, and must hold of every one of them (tool-defined). An exposed // set with no element the subject admits is unevaluable, never a pass. diff --git a/internal/semantic/semantics/conformance_test.go b/internal/semantic/semantics/conformance_test.go index 4f02604bdc..846cda676e 100644 --- a/internal/semantic/semantics/conformance_test.go +++ b/internal/semantic/semantics/conformance_test.go @@ -86,13 +86,13 @@ const conformanceLib = ` concern def CostConcern { subject s : Vehicle; require constraint { s.mass > 1.0 } } ` -// A view framing every concern its viewpoint frames conforms, and each concern -// is evaluated against the exposed elements its subject admits. +// A view satisfying a viewpoint conforms when each concern the viewpoint +// frames holds of the exposed elements its subject admits. func TestViewConformanceFramedAndHolding(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; expose wheel; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; expose wheel; satisfy vp; } `) eval := &fakeEvaluator{} report := conformance(t, m, sym(t, root, "v"), eval) @@ -101,22 +101,24 @@ func TestViewConformanceFramedAndHolding(t *testing.T) { wantNames(t, "concerns evaluated", eval.asked, []string{"mass/vehicle"}) } -// A concern the viewpoint frames and the view does not is a non-conformance, -// reported as such rather than left silent. -func TestViewConformanceMissingConcernIsViolated(t *testing.T) { +// A view frames nothing of its own — a view body admits no `frame` member — +// so without an evaluator every framed concern is not evaluated, and with one +// each conforms or is violated per its condition alone. +func TestViewConformanceViewFramesNothing(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) - report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) - vp := onlyViewpoint(t, report) + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), nil)) wantNames(t, "concerns of vp", concernNames(vp), []string{"mass", "cost"}) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictNotEvaluated, vp.Concerns[0].Reason) + wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictNotEvaluated, vp.Concerns[1].Reason) + + eval := &fakeEvaluator{fails: map[string]bool{"cost": true}} + vp = onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) - if !strings.Contains(vp.Concerns[1].Reason, "not by the view") { - t.Fatalf("reason for cost = %q, want it to say the view does not frame it", vp.Concerns[1].Reason) - } - wantVerdict(t, "verdict of v", report.Verdict, VerdictViolated, "a concern is not framed") } // A concern whose condition is false of an exposed element is a violation, one @@ -126,7 +128,7 @@ func TestViewConformanceFalseConditionIsViolated(t *testing.T) { viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; part other : Vehicle; - view v { expose vehicle; expose other; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; expose other; satisfy vp; } `) eval := &fakeEvaluator{fails: map[string]bool{"mass": true}} report := conformance(t, m, sym(t, root, "v"), eval) @@ -144,7 +146,7 @@ func TestViewConformanceUnevaluableConcern(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) eval := &fakeEvaluator{broken: map[string]bool{"mass": true}} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -160,7 +162,7 @@ func TestViewConformanceNoSubjectIsUnevaluable(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose wheel; satisfy vp; frame concern mass : MassConcern; } + view v { expose wheel; satisfy vp; } `) eval := &fakeEvaluator{} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -178,7 +180,7 @@ func TestViewConformanceExposingNothingIsUnevaluable(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { satisfy vp; frame concern mass : MassConcern; } + view v { satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictUnevaluable, vp.Concerns[0].Reason) @@ -194,7 +196,7 @@ func TestViewConformanceInheritedSatisfy(t *testing.T) { viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; view def StructureView { satisfy vp; } - view v : StructureView { expose vehicle; frame concern mass : MassConcern; } + view v : StructureView { expose vehicle; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) if vp.SatisfiedIn != sym(t, root, "StructureView") { @@ -203,46 +205,17 @@ func TestViewConformanceInheritedSatisfy(t *testing.T) { wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) } -// A framing inherited from a view definition frames for the view. -func TestViewConformanceInheritedFraming(t *testing.T) { - m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; - view def StructureView { frame concern mass : MassConcern; } - view v : StructureView { expose vehicle; satisfy vp; } - `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) - wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) - if vp.Concerns[0].FramedIn != sym(t, root, "StructureView") { - t.Fatalf("mass framed in %v, want the view definition StructureView that declares it", vp.Concerns[0].FramedIn) - } -} - -// A concern framed by a nested view frames for its container (tool-defined: the -// container's conformance is what the view tree as a whole addresses). -func TestViewConformanceNestedViewFraming(t *testing.T) { - m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; - view v { expose vehicle; satisfy vp; view inner { frame concern mass : MassConcern; } } - `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) - wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) - if vp.Concerns[0].FramedIn == nil || localName(vp.Concerns[0].FramedIn.Name) != "inner" { - t.Fatalf("mass framed in %v, want the nested view inner", vp.Concerns[0].FramedIn) - } -} - // A concern a viewpoint inherits is framed by the viewpoint too, so the view -// must frame it. +// answers it the way it answers the declared ones. func TestViewConformanceInheritedViewpointFraming(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def BaseVP { frame concern mass : MassConcern; } viewpoint def VP :> BaseVP { frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern cost : CostConcern; } + view v { expose vehicle; satisfy vp; } `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) + eval := &fakeEvaluator{fails: map[string]bool{"mass": true}} + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) wantNames(t, "concerns of vp", concernNames(vp), []string{"cost", "mass"}) wantVerdict(t, "verdict of mass", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) } @@ -255,7 +228,7 @@ func TestViewConformanceReferenceFramingMatches(t *testing.T) { part vehicle : Vehicle; concern modularity { subject s : Vehicle; require constraint { true } } viewpoint perspective { frame modularity; } - view v { expose vehicle; satisfy perspective; frame modularity; } + view v { expose vehicle; satisfy perspective; } `) eval := &fakeEvaluator{} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -287,7 +260,7 @@ func TestViewConformanceUnresolvedStakeholder(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { stakeholder se : Missing; frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) var reasons []string @@ -309,7 +282,7 @@ func TestViewConformanceResolvedStakeholdersAreQuiet(t *testing.T) { part def Engineer; viewpoint def VP { stakeholder se : Engineer; stakeholder anyone; frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) for _, p := range vp.Parties { @@ -320,17 +293,17 @@ func TestViewConformanceResolvedStakeholdersAreQuiet(t *testing.T) { wantVerdict(t, "verdict of vp", vp.Verdict, VerdictConforms, vp.Reason) } -// Without an evaluator only the structural question is answered: a framed -// concern is not evaluated, and a missing one is still a violation. +// Without an evaluator only the structural question is answered: every framed +// concern is not evaluated. func TestViewConformanceWithoutEvaluator(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), nil)) wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictNotEvaluated, vp.Concerns[0].Reason) - wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) + wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictNotEvaluated, vp.Concerns[1].Reason) } // A viewpoint framing no concern asks nothing of the view, so no conformance @@ -365,28 +338,24 @@ func TestViewConformanceUnresolvedFramedConcern(t *testing.T) { } } -// A framing referencing a concern usage frames that usage, not the definition it -// is typed by, whose values the usage may mask. Two usages of one definition are -// therefore different concerns. +// A framing referencing a concern usage frames that usage, not the definition +// it is typed by, whose values the usage may mask — so it is the usage whose +// conditions are evaluated. func TestViewConformanceFramesTheNamedConcernUsage(t *testing.T) { m, root := buildModel(t, conformanceLib+` concern tight : MassConcern; concern loose : MassConcern; viewpoint def VP { frame tight; } viewpoint vp : VP; - view framingTheUsage { expose vehicle; satisfy vp; frame tight; } - view framingAnother { expose vehicle; satisfy vp; frame loose; } + view v { expose vehicle; satisfy vp; } `) eval := &fakeEvaluator{} - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "framingTheUsage"), eval)) + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) if got := vp.Concerns[0].Target; got != sym(t, root, "tight") { t.Fatalf("framed concern target = %v, want the tight usage", got) } wantVerdict(t, "verdict of tight", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) wantNames(t, "concerns evaluated", eval.asked, []string{"tight/vehicle"}) - - other := onlyViewpoint(t, conformance(t, m, sym(t, root, "framingAnother"), &fakeEvaluator{})) - wantVerdict(t, "verdict of tight", other.Concerns[0].Verdict, VerdictViolated, other.Concerns[0].Reason) } // A satisfy stating a subject asserts a requirement of that subject, as the @@ -403,7 +372,6 @@ func TestViewConformanceIgnoresSatisfyWithASubject(t *testing.T) { satisfy requirement conformance : VehicleSpecification by vehicle; satisfy spec by vehicle; satisfy vp; - frame concern mass : MassConcern; } `) report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) @@ -414,24 +382,18 @@ func TestViewConformanceIgnoresSatisfyWithASubject(t *testing.T) { wantVerdict(t, "verdict of v", report.Verdict, VerdictConforms, vp.Reason) } -// A view framing a concern without naming its type frames it under that name: -// the framings share no resolved concern, so the name is what matches, either -// way round. -func TestViewConformanceUntypedFramingMatchesByName(t *testing.T) { +// An untyped `frame concern mass;` in the viewpoint declares a concern under +// that name, which evaluation then answers. +func TestViewConformanceUntypedViewpointFraming(t *testing.T) { m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; viewpoint def UntypedVP { frame concern mass; } viewpoint untypedVp : UntypedVP; - view untypedByTheView { expose vehicle; satisfy vp; frame concern mass; } - view untypedByTheViewpoint { expose vehicle; satisfy untypedVp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy untypedVp; } `) - for _, name := range []string{"untypedByTheView", "untypedByTheViewpoint"} { - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, name), &fakeEvaluator{})) - if vp.Concerns[0].FramedBy == nil { - t.Errorf("%s: mass reported as not framed (%s), want it framed under its name", name, vp.Concerns[0].Reason) - } - } + eval := &fakeEvaluator{} + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) + wantNames(t, "concerns evaluated", eval.asked, []string{"mass/vehicle"}) } // Satisfies and framings come back in declaration order even where a named @@ -448,9 +410,6 @@ func TestViewConformanceOrdersMixedNamedAndAnonymousMembers(t *testing.T) { expose vehicle; satisfy vp; satisfy requirement named : OtherVP; - frame alpha; - frame concern middle : MassConcern; - frame omega; } `) report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) @@ -491,9 +450,6 @@ func TestViewConformanceIsDeterministic(t *testing.T) { view v { expose vehicle; expose second; satisfy vp; satisfy other; - frame concern mass : MassConcern; - frame concern cost : CostConcern; - frame concern cost2 : CostConcern; } `) view := sym(t, root, "v") diff --git a/internal/syntax/parser/behavior.go b/internal/syntax/parser/behavior.go index e83ed4935a..a37096a37c 100644 --- a/internal/syntax/parser/behavior.go +++ b/internal/syntax/parser/behavior.go @@ -661,9 +661,14 @@ func (p *Parser) parseInitialNode(tok lexer.Token) ast.Node { } // The succession a `first … then …` states ends in a body of its own - // (SysML.xtext ActionTargetSuccession, which ends in UsageBody); a - // one-ended `first a { … }` ends in a RelationshipBody, no ActionBody. - members, hasBody := p.parseNodeBodyContext(start, "initial node", bodyOther) + // (SysML.xtext ActionTargetSuccession, whose UsageBody holds the action + // members of the body the edge is written in); a one-ended `first a { … }` + // ends in a RelationshipBody, no ActionBody. + ctx := bodyOther + if successor != nil && p.bodyContext().admitsAcceptNode() { + ctx = bodySuccession + } + members, hasBody := p.parseNodeBodyContext(start, "initial node", ctx) node := &ast.InitialNode{ First: first, @@ -1031,12 +1036,19 @@ func (p *Parser) parseSuccessionEdgeWithMultiplicity(tok lexer.Token, allowBody return node } - // An action target succession ends in a UsageBody (SysML.xtext:1698). + // An action target succession ends in a UsageBody (SysML.xtext:1698), + // which holds the action members of the body the edge is written in: a + // `then { accept … }` in an action body holds accept nodes like the + // statements around it. var members []ast.Node hasBody := false if allowBody && p.accept2(lexer.LBrace) { hasBody = true - leave := p.pushBodyContext(bodyOther) + ctx := bodyOther + if p.bodyContext().admitsAcceptNode() { + ctx = bodySuccession + } + leave := p.pushBodyContext(ctx) members = p.parseMixedBody() leave() } else { diff --git a/internal/syntax/parser/defusage.go b/internal/syntax/parser/defusage.go index 678be314d5..5a505c75c5 100644 --- a/internal/syntax/parser/defusage.go +++ b/internal/syntax/parser/defusage.go @@ -2026,6 +2026,12 @@ var ownedMembers = map[string]ownedMember{ "do": {"the do action of a state", "state", []bodyContext{bodyState}}, "exit": {"the exit action of a state", "state", []bodyContext{bodyState}}, "render": {"the rendering of a view", "view", []bodyContext{bodyViewDef, bodyView}}, + // FramedConcernMember is a RequirementBodyItem only (SysML.xtext): a view + // frames nothing, which the conformance rules now reflect too. + "frame": {"the concern a requirement or viewpoint frames", "requirement", []bodyContext{bodyRequirement}}, + // ElementFilterMember is a PackageBodyElement, a ViewDefinitionBodyItem and + // a ViewBodyItem (SysML.xtext), so a package, the root or a view admits it. + "filter": {"an element filter", "package or view", []bodyContext{bodyPackage, bodyViewDef, bodyView}}, // TransitionUsageMember is a StateBodyItem only (SysML.xtext); a transition between // action nodes is an extension the notation pass reports, so those bodies read it too. "transition": {"a transition between states", "state", []bodyContext{bodyState, bodyAction, bodyCalc, bodyCase}}, @@ -2048,8 +2054,9 @@ func memberKeyword(notation string) string { } // BodyAdmitsMember reports whether the body of owner — a Definition or Usage; -// any other node, the document root included, opens a plain namespace body — -// offers the member keyword kw introduces; as for MemberOwner, kw may be a notation. +// a package or the document root opens a package body, anything else a plain +// namespace body — offers the member keyword kw introduces; as for MemberOwner, +// kw may be a notation. func BodyAdmitsMember(owner ast.Node, kw string) bool { m, ok := ownedMembers[memberKeyword(kw)] if !ok { @@ -2094,6 +2101,8 @@ func declarationBodyContext(owner ast.Node) bodyContext { body = usageBodyContext(d.Kind) case *ast.SubstateMember: body = usageBodyContext(ast.UsageState) + case *ast.Package, *ast.RootNamespace: + body = bodyPackage } return body } @@ -2871,6 +2880,11 @@ func (p *Parser) parseBodyMember() ast.Node { return p.parseActionMember() } if p.atAcceptNodeAt(p.prefixLookahead()) { + if !p.bodyContext().admitsAcceptNode() { + en := p.errorNodeSkip(start, msgExpectedBodyMember) + en.SetLeadingTrivia(trivia) + return en + } return p.parseAcceptNode(start, vis, trivia, p.parsePrefixMetadata()) } // Delegate to parseDefUsage which handles prefixes; a prefixed @@ -3046,6 +3060,14 @@ func (p *Parser) parseBodyMember() ast.Node { // (`accept when x > 1`) — and is parsed by the one payload parser triggers // also use, so every spelling reaches lowering the same way. if p.atAcceptNode() { + // AcceptNode is an ActionBodyItem (SysML.xtext): outside an + // action-admitting body it stands where no member may, like the + // behavioral keywords. A state body keeps reading it as today. + if !p.bodyContext().admitsAcceptNode() { + en := p.errorNodeSkip(start, msgExpectedBodyMember) + en.SetLeadingTrivia(trivia) + return en + } return p.parseAcceptNode(start, vis, trivia, nil) } diff --git a/internal/syntax/parser/namespace.go b/internal/syntax/parser/namespace.go index 5e407adcc8..f45ae681af 100644 --- a/internal/syntax/parser/namespace.go +++ b/internal/syntax/parser/namespace.go @@ -389,7 +389,16 @@ func (p *Parser) parseDeclaration(start int) ast.Node { case p.atKeyword("multiplicity"): return p.parseMultiplicityDecl(start) case p.atKeyword("filter"): - return p.parseFilter(start) + var misplaced *ast.ErrorNode + if !p.bodyAdmitsMember("filter") { + misplaced = p.misplacedMember(p.peek()) + } + f := p.parseFilter(start) + if misplaced != nil { + misplaced.NodeSpan = f.Span() + return misplaced + } + return f case p.atKeyword("locale"): // An anonymous comment carrying a locale (SysML.xtext Comment: the // `comment` keyword is optional). @@ -426,15 +435,17 @@ func (p *Parser) parseDeclaration(start int) ast.Node { } } -// parseNamespaceBody parses `{ member* }` or `;`. Returns (members, hasBody). -// The caller has already consumed the declaration head up to this point. -func (p *Parser) parseNamespaceBody() ([]ast.Node, bool) { +// parseNamespaceBody parses `{ member* }` or `;` under the body notation ctx +// (a package's own body, or the plain namespace body of an import, alias, +// namespace, dependency or multiplicity). Returns (members, hasBody). The +// caller has already consumed the declaration head up to this point. +func (p *Parser) parseNamespaceBody(ctx bodyContext) ([]ast.Node, bool) { if p.accept2(lexer.Semicolon) || !p.expectBodyOrEnd("declaration") { return nil, false } // A package/namespace body has its own notation; it never inherits the // enclosing body's (e.g. an interface's default-end allowance). - defer p.pushBodyContext(bodyOther)() + defer p.pushBodyContext(ctx)() var members []ast.Node for !p.atEOF() && !p.at(lexer.RBrace) { p.memberStart() @@ -665,7 +676,7 @@ func (p *Parser) parseImport(start int, vis ast.Visibility) *ast.Import { p.parseImportTail(imp) - imp.Body, imp.HasBody = p.parseNamespaceBody() + imp.Body, imp.HasBody = p.parseNamespaceBody(bodyOther) imp.NodeSpan = p.spanFrom(start) return imp } @@ -731,7 +742,7 @@ func (p *Parser) parseAlias(start int, vis ast.Visibility) *ast.Alias { } else { al.For = p.parseQualifiedName() } - al.Body, al.HasBody = p.parseNamespaceBody() + al.Body, al.HasBody = p.parseNamespaceBody(bodyOther) al.NodeSpan = p.spanFrom(start) return al } @@ -773,7 +784,7 @@ func (p *Parser) parseDependency(start int) ast.Node { } else { dep.Suppliers = p.parseQualifiedNameList() } - dep.Body, dep.HasBody = p.parseNamespaceBody() + dep.Body, dep.HasBody = p.parseNamespaceBody(bodyOther) dep.NodeSpan = p.spanFrom(start) return dep } @@ -844,7 +855,7 @@ func (p *Parser) parsePackage(start int) ast.Node { return p.errorNodeSkip(start, "expected 'package'") } id := p.parseIdentification() - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyPackage) pkg := &ast.Package{ Prefixes: prefixes, Ident: id, @@ -864,7 +875,7 @@ func (p *Parser) parseNamespace(start int) ast.Node { return p.errorNodeSkip(start, "expected 'namespace'") } id := p.parseIdentification() - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyOther) ns := &ast.Namespace{Prefixes: prefixes, Ident: id, Members: members, HasBody: hasBody} ns.NodeSpan = p.spanFrom(start) return ns @@ -967,7 +978,7 @@ func (p *Parser) parseMultiplicityDecl(start int) ast.Node { } // Parse body or semicolon - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyOther) md := &ast.MultiplicityDecl{ Ident: ident, diff --git a/internal/syntax/parser/owned_member_test.go b/internal/syntax/parser/owned_member_test.go index ca99505b72..56e72e5169 100644 --- a/internal/syntax/parser/owned_member_test.go +++ b/internal/syntax/parser/owned_member_test.go @@ -114,6 +114,17 @@ func TestMemberOutsideOwningBodyIsRejected(t *testing.T) { {"render in part usage under view", "view def V { part p { render r; } }", "render", `kind="render"`}, {"render in typed usage under nested view", "view def V { view v { x { render r; } } }", "render", `kind="render"`}, {"render in typed usage under view usage", "view v { x : T { render r; } }", "render", `kind="render"`}, + // FramedConcernMember is a RequirementBodyItem and ElementFilterMember a + // package/view-body one (SysML.xtext), so neither parses in these bodies. + {"frame in view", "view V { frame concern c : C; }", "frame", `kind="frame"`}, + {"frame reference in view", "view V { frame c; }", "frame", `kind="frame"`}, + {"frame in view def", "view def V { frame concern c : C; }", "frame", `kind="frame"`}, + {"frame in rendering def", "rendering def R { frame c; }", "frame", `kind="frame"`}, + {"frame in part def", "part def P { frame c; }", "frame", `kind="frame"`}, + {"filter in viewpoint def", "viewpoint def V { filter true; }", "filter", "FilterMember"}, + {"filter in rendering def", "rendering def R { filter true; }", "filter", "FilterMember"}, + {"filter in part def", "part def P { filter true; }", "filter", "FilterMember"}, + {"filter in namespace", "package P { namespace N { filter true; } }", "filter", "FilterMember"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -154,7 +165,7 @@ func TestMemberOutsideOwningBodyIsRejected(t *testing.T) { // KerML reserves none of the member keywords, so each names an ordinary feature // in a `.kerml` file, in every shape the KerML Feature production offers. func TestOwnedMemberKeywordsAreKerMLNames(t *testing.T) { - for _, word := range []string{"actor", "subject", "stakeholder", "objective", "entry", "do", "exit", "render"} { + for _, word := range []string{"actor", "subject", "stakeholder", "objective", "entry", "do", "exit", "render", "frame"} { for _, shape := range []string{ "feature %s : A;", "feature %s;", @@ -203,6 +214,19 @@ func TestMemberInsideOwningBodyStaysClean(t *testing.T) { {"viewpoint usage", "viewpoint vp { subject v : V; actor a : Person; stakeholder s : Person; }"}, {"satisfy body", "part p { satisfy r by p { subject v : V; actor a : Person; } }"}, {"frame body", "requirement def R { frame c { stakeholder s : Person; actor a : Person; } }"}, + {"frame in requirement", "concern def C; requirement r { frame concern c : C; frame c; }"}, + {"frame in concern", "concern def C; concern k { frame concern c : C; frame c; }"}, + {"frame in viewpoint def", "concern def C; viewpoint def V { frame concern c : C; frame c; }"}, + {"frame in viewpoint usage", "concern def C; viewpoint v { frame concern c : C; frame c; }"}, + {"filter in root", "filter true;"}, + {"filter in package", "package P { filter true; }"}, + {"filter in library package", "library package L { filter true; }"}, + {"filter in view", "view v { filter true; }"}, + {"filter in view def", "view def V { filter true; }"}, + {"accept in action body", "action def A { item def S; action a { accept S; } }"}, + {"accept in calc body", "item def S; calc c { accept S; }"}, + {"accept in case body", "item def S; case c { accept S; }"}, + {"accept in state body", "state s { state t; accept after 5 then t; }"}, {"objective body", "case def C { objective o { subject v : V; actor a : Person; stakeholder s : Person; } }"}, {"case def", "case def C { subject v : V; actor a : Person; objective o; }"}, {"case usage", "case c { subject v : V; actor a : Person; objective { require constraint { x > 0 } } }"}, diff --git a/internal/syntax/parser/parser.go b/internal/syntax/parser/parser.go index 04c58a88e0..03b6ac9f9d 100644 --- a/internal/syntax/parser/parser.go +++ b/internal/syntax/parser/parser.go @@ -112,6 +112,12 @@ const ( // only the view usage admits `expose`. bodyViewDef bodyView + // bodyPackage is a PackageBodyElement body: a package's or the file root's. + bodyPackage + // bodySuccession is the UsageBody an action target succession ends in + // (SysML.xtext:1698): it takes the action members of the body around it — + // accept nodes included — but no initial node or target succession of its own. + bodySuccession ) // carriesActions reports whether the body admits ActionBodyItem (action, calc, @@ -124,6 +130,14 @@ func (c bodyContext) carriesActions() bool { return false } +// admitsAcceptNode reports whether the body admits an AcceptNode: the +// action-admitting bodies (SysML.xtext ActionBodyItem), a state body and the +// usage body an action target succession carries, whose own members are read +// the way they always have been. +func (c bodyContext) admitsAcceptNode() bool { + return c.carriesActions() || c == bodyState || c == bodySuccession +} + // pushBodyContext enters a body of the given notation and returns the function // that leaves it. func (p *Parser) pushBodyContext(c bodyContext) func() { @@ -132,10 +146,11 @@ func (p *Parser) pushBodyContext(c bodyContext) func() { return func() { p.bodyCtx = p.bodyCtx[:depth] } } -// bodyContext returns the notation of the innermost body being parsed. +// bodyContext returns the notation of the innermost body being parsed; the +// file root reads as a package body (SysML.xtext PackageBodyElement). func (p *Parser) bodyContext() bodyContext { if len(p.bodyCtx) == 0 { - return bodyOther + return bodyPackage } return p.bodyCtx[len(p.bodyCtx)-1] } diff --git a/internal/workspace/libs/stdlib.snapshot b/internal/workspace/libs/stdlib.snapshot index 1f58368f8d..68c4f6ef78 100644 Binary files a/internal/workspace/libs/stdlib.snapshot and b/internal/workspace/libs/stdlib.snapshot differ diff --git a/internal/workspace/libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml b/internal/workspace/libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml index 3fc5f80129..fdf0b1a32f 100644 --- a/internal/workspace/libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml +++ b/internal/workspace/libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml @@ -1,4 +1,4 @@ -standard library package DiagramLayout { +library package DiagramLayout { doc /* Diagram geometry in notation: where a view draws an element, the * waypoints an edge is drawn through and the extent of the drawing * surface. Units are pixels, y down, origin at the top left. diff --git a/internal/workspace/libs/stdlib/OpenSysML Libraries/IdentityMetadata.sysml b/internal/workspace/libs/stdlib/OpenSysML Libraries/IdentityMetadata.sysml index 815716a1c1..e11b564589 100644 --- a/internal/workspace/libs/stdlib/OpenSysML Libraries/IdentityMetadata.sysml +++ b/internal/workspace/libs/stdlib/OpenSysML Libraries/IdentityMetadata.sysml @@ -1,4 +1,4 @@ -standard library package IdentityMetadata { +library package IdentityMetadata { doc /* Binds notation to repository identity. Non-normative OpenSysML * extension, proposed for standardization; see the design record. */ diff --git a/internal/workspace/libs/stdlib/OpenSysML Libraries/MigrationMetadata.sysml b/internal/workspace/libs/stdlib/OpenSysML Libraries/MigrationMetadata.sysml index 1537f13755..169dd88931 100644 --- a/internal/workspace/libs/stdlib/OpenSysML Libraries/MigrationMetadata.sysml +++ b/internal/workspace/libs/stdlib/OpenSysML Libraries/MigrationMetadata.sysml @@ -1,4 +1,4 @@ -standard library package MigrationMetadata { +library package MigrationMetadata { doc /* What a migration from another modeling language had to make up * because its source left it unstated. Non-normative OpenSysML * extension, proposed for standardization; see the design record. */ diff --git a/internal/workspace/libs/stdlib/OpenSysML Libraries/SysMLValidation.sysml b/internal/workspace/libs/stdlib/OpenSysML Libraries/SysMLValidation.sysml index 49c5bd5535..13ae533f3e 100644 --- a/internal/workspace/libs/stdlib/OpenSysML Libraries/SysMLValidation.sysml +++ b/internal/workspace/libs/stdlib/OpenSysML Libraries/SysMLValidation.sysml @@ -1,4 +1,4 @@ -standard library package SysMLValidation { +library package SysMLValidation { doc /* * Validation constraints of the KerML and SysML abstract syntax, stated over diff --git a/tests/export/testdata/convert/imported_references.canonical.golden.sysml b/tests/export/testdata/convert/imported_references.canonical.golden.sysml index 6e0d826b4a..c39c30b2e6 100644 --- a/tests/export/testdata/convert/imported_references.canonical.golden.sysml +++ b/tests/export/testdata/convert/imported_references.canonical.golden.sysml @@ -57,6 +57,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/export/testdata/convert/imported_references.golden.sysml b/tests/export/testdata/convert/imported_references.golden.sysml index 621c024dbd..32b2d9fa6f 100644 --- a/tests/export/testdata/convert/imported_references.golden.sysml +++ b/tests/export/testdata/convert/imported_references.golden.sysml @@ -56,6 +56,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/export/testdata/convert/imported_references.golden.ttl b/tests/export/testdata/convert/imported_references.golden.ttl index 62c27f9081..88964bd9b0 100644 --- a/tests/export/testdata/convert/imported_references.golden.ttl +++ b/tests/export/testdata/convert/imported_references.golden.ttl @@ -1622,13 +1622,14 @@ elmt:Filtered sysx:sourceLanguage "sysml" ; sysml:declaredName "Filtered" ; sysx:hasBody "true"^^xsd:boolean ; - sysml:ownedRelationship elmt:Filtered___400, elmt:Filtered__C_om ; + sysml:ownedRelationship elmt:Filtered___400, elmt:Filtered__C_om, elmt:Filtered___402 ; sysml:ownedImport elmt:Filtered___400 ; sysml:ownedMember elmt:Filtered__C ; - sysml:ownedMembership elmt:Filtered__C_om ; + sysml:ownedMembership elmt:Filtered__C_om, elmt:Filtered___402 ; sysx:sourceText "package Filtered {\n" ; sysx:sourceTail "}\n" ; - json:ownedRelationship "[{\"@id\":\"Filtered___400\"},{\"@id\":\"Filtered__C_om\"}]" . + json:ownedRelationship "[{\"@id\":\"Filtered___400\"},{\"@id\":\"Filtered__C_om\"},{\"@id\":\"Filtered___402\"}]" ; + json:ownedMembership "[{\"@id\":\"Filtered__C_om\"},{\"@id\":\"Filtered___402\"}]" . elmt:Filtered___400 a sysml:NamespaceImport ; @@ -1765,7 +1766,7 @@ elmt:Filtered__C sysml:qualifiedName "Filtered::C" ; sysx:sourceLine "57"^^xsd:integer ; sysx:sourceColumn "5"^^xsd:integer ; - sysx:sourceEndLine "60"^^xsd:integer ; + sysx:sourceEndLine "59"^^xsd:integer ; sysx:sourceEndColumn "6"^^xsd:integer ; sysml:elementId "Filtered__C" ; sysx:memberIndex "1"^^xsd:integer ; @@ -1776,14 +1777,12 @@ elmt:Filtered__C sysml:declaredName "C" ; sysx:hasBody "true"^^xsd:boolean ; sysml:ownedMember elmt:Filtered__C__other ; - sysml:ownedMembership elmt:Filtered__C__other_om, elmt:Filtered__C___401 ; - sysml:ownedRelationship elmt:Filtered__C__other_om, elmt:Filtered__C___401 ; + sysml:ownedMembership elmt:Filtered__C__other_om ; + sysml:ownedRelationship elmt:Filtered__C__other_om ; sysml:ownedFeature elmt:Filtered__C__other ; sysml:ownedFeatureMembership elmt:Filtered__C__other_om ; sysx:sourceText " part def C {\n" ; - sysx:sourceTail " }\n" ; - json:ownedMembership "[{\"@id\":\"Filtered__C__other_om\"},{\"@id\":\"Filtered__C___401\"}]" ; - json:ownedRelationship "[{\"@id\":\"Filtered__C__other_om\"},{\"@id\":\"Filtered__C___401\"}]" . + sysx:sourceTail " }\n" . elmt:Filtered__C_om a sysml:OwningMembership ; @@ -1828,104 +1827,104 @@ elmt:Filtered__C__other_om sysml:ownedMemberFeature elmt:Filtered__C__other ; sysml:owningType elmt:Filtered__C . -elmt:Filtered__C___401 +elmt:Filtered___402 a sysml:ElementFilterMembership ; - sysml:qualifiedName "Filtered::C::@1" ; - sysx:sourceLine "59"^^xsd:integer ; - sysx:sourceColumn "9"^^xsd:integer ; - sysx:sourceEndLine "59"^^xsd:integer ; - sysx:sourceEndColumn "24"^^xsd:integer ; - sysml:elementId "Filtered__C___401" ; - sysx:memberIndex "1"^^xsd:integer ; - sysml:owningNamespace elmt:Filtered__C ; - sysml:owner elmt:Filtered__C ; - sysml:owningRelatedElement elmt:Filtered__C ; - sysml:membershipOwningNamespace elmt:Filtered__C ; - sysml:condition expr:Filtered__C___401_pfilter ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter ; - sysx:sourceText " filter @Safety;\n" . - -expr:Filtered__C___401_pfilter + sysml:qualifiedName "Filtered::@2" ; + sysx:sourceLine "60"^^xsd:integer ; + sysx:sourceColumn "5"^^xsd:integer ; + sysx:sourceEndLine "60"^^xsd:integer ; + sysx:sourceEndColumn "20"^^xsd:integer ; + sysml:elementId "Filtered___402" ; + sysx:memberIndex "2"^^xsd:integer ; + sysml:owningNamespace elmt:Filtered ; + sysml:owner elmt:Filtered ; + sysml:owningRelatedElement elmt:Filtered ; + sysml:membershipOwningNamespace elmt:Filtered ; + sysml:condition expr:Filtered___402_pfilter ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter ; + sysml:ownedMemberElement expr:Filtered___402_pfilter ; + sysx:sourceText " filter @Safety;\n" . + +expr:Filtered___402_pfilter a sysml:OperatorExpression ; sysx:sourceText "@Safety" ; - sysml:elementId "Filtered__C___401_pfilter" ; + sysml:elementId "Filtered___402_pfilter" ; sysml:operator "@" ; sysx:typeArgument elmt:Meta__Safety ; - sysml:ownedRelationship expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedMembership expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedFeatureMembership expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedFeature expr:Filtered__C___401_pfilter_pin0, expr:Filtered__C___401_pfilter_pout ; - sysml:parameter expr:Filtered__C___401_pfilter_pin0, expr:Filtered__C___401_pfilter_pout ; - sysml:input expr:Filtered__C___401_pfilter_pin0 ; - sysml:result expr:Filtered__C___401_pfilter_pout ; - sysml:owner elmt:Filtered__C___401 ; - sysml:owningRelationship elmt:Filtered__C___401 ; - sysml:owningMembership elmt:Filtered__C___401 ; - json:ownedRelationship "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedMembership "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedFeatureMembership "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedFeature "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Filtered__C___401_pfilter_pout\"}]" ; - json:parameter "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Filtered__C___401_pfilter_pout\"}]" . - -expr:Filtered__C___401_pfilter_pin0 + sysml:ownedRelationship expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedMembership expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedFeatureMembership expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedFeature expr:Filtered___402_pfilter_pin0, expr:Filtered___402_pfilter_pout ; + sysml:parameter expr:Filtered___402_pfilter_pin0, expr:Filtered___402_pfilter_pout ; + sysml:input expr:Filtered___402_pfilter_pin0 ; + sysml:result expr:Filtered___402_pfilter_pout ; + sysml:owner elmt:Filtered___402 ; + sysml:owningRelationship elmt:Filtered___402 ; + sysml:owningMembership elmt:Filtered___402 ; + json:ownedRelationship "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedMembership "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedFeatureMembership "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedFeature "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Filtered___402_pfilter_pout\"}]" ; + json:parameter "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Filtered___402_pfilter_pout\"}]" . + +expr:Filtered___402_pfilter_pin0 a sysml:Feature ; - sysml:elementId "Filtered__C___401_pfilter_pin0" ; + sysml:elementId "Filtered___402_pfilter_pin0" ; sysml:direction "in" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:owningRelationship expr:Filtered__C___401_pfilter_pin0_om ; - sysml:owningMembership expr:Filtered__C___401_pfilter_pin0_om ; - sysml:ownedTyping expr:Filtered__C___401_pfilter_pin0_ft0 ; - sysml:ownedSpecialization expr:Filtered__C___401_pfilter_pin0_ft0 ; - sysml:ownedRelationship expr:Filtered__C___401_pfilter_pin0_ft0 . - -expr:Filtered__C___401_pfilter_pin0_om + sysml:owner expr:Filtered___402_pfilter ; + sysml:owningRelationship expr:Filtered___402_pfilter_pin0_om ; + sysml:owningMembership expr:Filtered___402_pfilter_pin0_om ; + sysml:ownedTyping expr:Filtered___402_pfilter_pin0_ft0 ; + sysml:ownedSpecialization expr:Filtered___402_pfilter_pin0_ft0 ; + sysml:ownedRelationship expr:Filtered___402_pfilter_pin0_ft0 . + +expr:Filtered___402_pfilter_pin0_om a sysml:ParameterMembership ; - sysml:elementId "Filtered__C___401_pfilter_pin0_om" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter ; - sysml:membershipOwningNamespace expr:Filtered__C___401_pfilter ; - sysml:ownedMemberFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedMemberParameter expr:Filtered__C___401_pfilter_pin0 . - -expr:Filtered__C___401_pfilter_pin0_ft0 + sysml:elementId "Filtered___402_pfilter_pin0_om" ; + sysml:owner expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter_pin0 ; + sysml:ownedMemberElement expr:Filtered___402_pfilter_pin0 ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter_pin0 ; + sysml:owningRelatedElement expr:Filtered___402_pfilter ; + sysml:membershipOwningNamespace expr:Filtered___402_pfilter ; + sysml:ownedMemberFeature expr:Filtered___402_pfilter_pin0 ; + sysml:ownedMemberParameter expr:Filtered___402_pfilter_pin0 . + +expr:Filtered___402_pfilter_pin0_ft0 a sysml:FeatureTyping ; - sysml:elementId "Filtered__C___401_pfilter_pin0_ft0" ; - sysml:typedFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:specific expr:Filtered__C___401_pfilter_pin0 ; - sysml:source expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter_pin0 ; + sysml:elementId "Filtered___402_pfilter_pin0_ft0" ; + sysml:typedFeature expr:Filtered___402_pfilter_pin0 ; + sysml:owningFeature expr:Filtered___402_pfilter_pin0 ; + sysml:specific expr:Filtered___402_pfilter_pin0 ; + sysml:source expr:Filtered___402_pfilter_pin0 ; + sysml:owningRelatedElement expr:Filtered___402_pfilter_pin0 ; sysml:type elmt:Meta__Safety ; sysml:general elmt:Meta__Safety ; sysml:target elmt:Meta__Safety ; - sysml:owner expr:Filtered__C___401_pfilter_pin0 ; - sysml:relatedElement expr:Filtered__C___401_pfilter_pin0, elmt:Meta__Safety ; - json:relatedElement "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Meta__Safety\"}]" . + sysml:owner expr:Filtered___402_pfilter_pin0 ; + sysml:relatedElement expr:Filtered___402_pfilter_pin0, elmt:Meta__Safety ; + json:relatedElement "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Meta__Safety\"}]" . -expr:Filtered__C___401_pfilter_pout +expr:Filtered___402_pfilter_pout a sysml:Feature ; - sysml:elementId "Filtered__C___401_pfilter_pout" ; + sysml:elementId "Filtered___402_pfilter_pout" ; sysml:direction "out" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:owningRelationship expr:Filtered__C___401_pfilter_pout_om ; - sysml:owningMembership expr:Filtered__C___401_pfilter_pout_om . + sysml:owner expr:Filtered___402_pfilter ; + sysml:owningRelationship expr:Filtered___402_pfilter_pout_om ; + sysml:owningMembership expr:Filtered___402_pfilter_pout_om . -expr:Filtered__C___401_pfilter_pout_om +expr:Filtered___402_pfilter_pout_om a sysml:ReturnParameterMembership ; - sysml:elementId "Filtered__C___401_pfilter_pout_om" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter_pout ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter_pout ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter_pout ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter ; - sysml:membershipOwningNamespace expr:Filtered__C___401_pfilter ; - sysml:ownedMemberFeature expr:Filtered__C___401_pfilter_pout ; - sysml:ownedMemberParameter expr:Filtered__C___401_pfilter_pout . + sysml:elementId "Filtered___402_pfilter_pout_om" ; + sysml:owner expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter_pout ; + sysml:ownedMemberElement expr:Filtered___402_pfilter_pout ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter_pout ; + sysml:owningRelatedElement expr:Filtered___402_pfilter ; + sysml:membershipOwningNamespace expr:Filtered___402_pfilter ; + sysml:ownedMemberFeature expr:Filtered___402_pfilter_pout ; + sysml:ownedMemberParameter expr:Filtered___402_pfilter_pout . elmt:OtherPkg__Inner__wheels_ft0 a sysml:FeatureTyping ; diff --git a/tests/export/testdata/convert/imported_references.sysml b/tests/export/testdata/convert/imported_references.sysml index 621c024dbd..32b2d9fa6f 100644 --- a/tests/export/testdata/convert/imported_references.sysml +++ b/tests/export/testdata/convert/imported_references.sysml @@ -56,6 +56,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/parser/negative_test.go b/tests/parser/negative_test.go index e37403a16d..a5340ab50e 100644 --- a/tests/parser/negative_test.go +++ b/tests/parser/negative_test.go @@ -268,6 +268,21 @@ func TestNegative(t *testing.T) { // ValuePart, unlike the performed action reference that shares its shape. {"render_reference_value", "view def V { render r = 3; }"}, + // FramedConcernMember is a RequirementBodyItem, ElementFilterMember a + // package/view-body item, and AcceptNode an ActionBodyItem (SysML.xtext): + // each is rejected by the bodies its grammar omits. + {"frame_in_view_body", "view V { frame concern c : C; }"}, + {"frame_ref_in_view_body", "view V { frame c; }"}, + {"frame_in_view_def_body", "view def V { frame concern c : C; }"}, + {"frame_in_rendering_body", "rendering def R { frame c; }"}, + {"frame_in_part_def_body", "part def P { frame c; }"}, + {"filter_in_viewpoint_def_body", "viewpoint def V { filter true; }"}, + {"filter_in_rendering_body", "rendering def R { filter true; }"}, + {"filter_in_part_def_body", "part def P { filter true; }"}, + {"accept_in_view_body", "view V { accept S; }"}, + {"accept_action_in_view_body", "view V { action a accept S; }"}, + {"accept_in_part_def_body", "part def P { accept S; }"}, + // The sequence index and the collection notations: `#` indexes through a // parenthesized index and `.?` selects through a body, so each is // rejected where the notation it needs is absent rather than parsed as diff --git a/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml b/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml new file mode 100644 index 0000000000..63b6b80d1a --- /dev/null +++ b/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml @@ -0,0 +1,7 @@ +// Invalid: TargetSuccessionMember is an ActionBodyItem; a view body admits no 'then ;' succession (SysML.xtext:1367 ActionBodyItem, :1693 TargetSuccessionMember, :2359 ViewBodyItem). +package X11TargetSuccessionOutsideActionBody { + view V { + action x; + then y; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml b/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml new file mode 100644 index 0000000000..b5bc8287fe --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml @@ -0,0 +1,7 @@ +// Invalid: FramedConcernMember is a RequirementBodyItem; a view body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2359 ViewBodyItem). +package G82FrameInViewBody { + concern def C; + view V { + frame concern c : C; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml b/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml new file mode 100644 index 0000000000..0f8a149778 --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml @@ -0,0 +1,7 @@ +// Invalid: FramedConcernMember is a RequirementBodyItem; a view def body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2329 ViewDefinitionBodyItem). +package G83FrameInViewDefBody { + concern def C; + view def V { + frame c; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml b/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml new file mode 100644 index 0000000000..d6ce17a26a --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml @@ -0,0 +1,6 @@ +// Invalid: ElementFilterMember is a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem; a rendering def body admits no 'filter' (SysML.xtext:227 ElementFilterMember, :2329 ViewDefinitionBodyItem, :2359 ViewBodyItem). +package G84FilterOutsidePackageOrViewBody { + rendering def R { + filter true; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml b/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml new file mode 100644 index 0000000000..837f0f2640 --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml @@ -0,0 +1,7 @@ +// Invalid: AcceptNode is an ActionBodyItem; a view body admits no 'accept' (SysML.xtext:1367 ActionBodyItem, :1434 AcceptNode, :2359 ViewBodyItem). +package G85AcceptOutsideActionBody { + item def S; + view V { + accept S; + } +}