diff --git a/.agents/skills/testing-pilot-rejection/SKILL.md b/.agents/skills/testing-pilot-rejection/SKILL.md index 2e9676ba1a..754ed1fa63 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/` (323 hand-written invalid models, one violated rule + citation +`tools/referee/reject/testdata/negative/` (328 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, the connector-end accessibility of a qualified definition feature, and the Feature-typed targets of a subsetting or redefinition in a SysML source), with a fresh library cache: -`323 case(s): 314 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept`, +`328 case(s): 319 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-sysml-repl/SKILL.md b/.agents/skills/testing-sysml-repl/SKILL.md index 86f02e0af8..c8705abb10 100644 --- a/.agents/skills/testing-sysml-repl/SKILL.md +++ b/.agents/skills/testing-sysml-repl/SKILL.md @@ -4351,7 +4351,7 @@ Read-only printer of the session buffer or one element: `satisfy structure (from Demo::StructureView): violated` and one line per concern (`conforms` / `violated` naming the exposed element and condition / `unevaluable` + reason); the view frames nothing itself — every concern the viewpoint frames is evaluated against what it exposes, -and a `frame` written in a view body draws a `nonstandard-notation` warning. +and a `frame` written in a view body is a parser error. 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 67dc953b87..66e924a137 100644 --- a/README.md +++ b/README.md @@ -348,8 +348,8 @@ The project is under active development, with the core infrastructure operationa - **Corpus agreement:** 358 of 397 files agree diagnostic-by-diagnostic; 315 diagnostics are ours alone and 93 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 323 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 310 both reject; 4 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 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 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 359 of 397 files agreeing, 314 diagnostics ours alone and 93 the reference's alone, 0 declared rows we are silent on, and 0 of 323 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 328 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 315 both reject; 4 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 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 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 359 of 397 files agreeing, 314 diagnostics ours alone and 93 the reference's alone, 0 declared rows we are silent on, and 0 of 328 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. @@ -362,7 +362,7 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a **Parser coverage:** 110/110 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 16 non-normative OpenSysML extensions: `OpenSysML Libraries/AnalysisRecords.sysml`, `DiagramLayout.sysml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `MOSA.sysml`, `MigrationMetadata.sysml`, `OOSEM.sysml`, `OpenSysMLMathFunctions.kerml`, `OpenSysMLRenderings.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateActivity.kerml`, `StateMachines.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml` and `SysMLValidation.sysml`. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). **Behavioral execution:** Calc/constraint/requirement/satisfy functional. Action/state executors handle nested invocation, control flow keywords, loop and conditional statements and the send statement (every conformance case passing). Coverage is self-assessed against the specification text and the normative library: the pinned OMG pilot implementation evaluates expressions but does not execute actions or state machines headlessly, so no external implementation currently adjudicates these rows. See [spec compliance](docs/project/spec-compliance.md). **Reference differential:** 397 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 358 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? 323 hand-written invalid models validated by both implementations, 314 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. +**Rejection oracle:** the reverse direction — do we reject what the reference rejects? 328 hand-written invalid models validated by both implementations, 319 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..bfb925a7fd --- /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. A `frame` in a view definition or usage body, which 0.10.0 parsed with a `nonstandard-notation` warning (an error only under `-strict`), is now a parser error in every mode, since `FramedConcernMember` appears only in `RequirementBodyItem`. +- **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`. + diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index 2c4a1212c0..8324e715ab 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -862,8 +862,8 @@ Every behavioral feature must have: - **Corpus agreement:** 358 of 397 files agree diagnostic-by-diagnostic; 315 diagnostics are ours alone and 93 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 323 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 310 both reject; 4 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 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 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 359 of 397 files agreeing, 314 diagnostics ours alone and 93 the reference's alone, 0 declared rows we are silent on, and 0 of 323 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 328 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 315 both reject; 4 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 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 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 359 of 397 files agreeing, 314 diagnostics ours alone and 93 the reference's alone, 0 declared rows we are silent on, and 0 of 328 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/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index d23ce74f7c..cd6dee32ec 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -62,7 +62,7 @@ "dir": "examples", "origin": "ours", "files": 52, - "digest": "sha256:b028e191edc6c2f7874eac4fc603dee5b47bb649d899caf8fd072e62af9031d8" + "digest": "sha256:779ea09f648b8002e32a16a8d21d067ecea2bd96eae1fb57c7e179d611c566e0" }, { "name": "probes", diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index d0d8864d0f..59ed0d58fd 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -314,8 +314,8 @@ admits `frame` (FramedConcernMember, §8.3.20/§8.3.26) only in a requirement, c body, and the reference failed to parse each of those lines (`mismatched input 'frame' expecting '}'`, then `extraneous input '}' expecting EOF`): two `syntax` rows per demo. The evaluator now checks every concern a viewpoint frames directly against what the view exposes, the three demos drop -the view-body framings with their `%view` output unchanged, and a `frame` left in a view body draws -the `nonstandard-notation` warning that `-strict` escalates to an error, as the reference rejects it. +the view-body framings with their `%view` output unchanged, and a `frame` left in a view body is a +parser error, as the reference rejects it. The six `syntax` rows go, the three demos become fully agreeing, and `examples` keeps only the 37 `DocumentQueries` binding warnings and the 17 duplicate-inherited-name warnings on the pilot side. diff --git a/docs/project/pilot-rejection-baseline.json b/docs/project/pilot-rejection-baseline.json index 15306538ba..8876143189 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": 323, - "digest": "sha256:67a2ceb8106af05d532132d3c407ab6bfe3c0f4bd0dab535acf677bb8f19cc83" + "files": 328, + "digest": "sha256:4b0ec62e3b1f9366a92474f8b89d5306a3e090b7e3e49160942110adb533ee5a" } ], - "recorded": "2026-10-17" + "recorded": "2026-10-18" }, "totals": { - "cases": 323, - "bothReject": 314, + "cases": 328, + "bothReject": 319, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 @@ -49,16 +49,16 @@ "sources": [ { "source": "extensions", - "cases": 11, - "bothReject": 11, + "cases": 12, + "bothReject": 12, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 }, { "source": "grammar", - "cases": 108, - "bothReject": 108, + "cases": 112, + "bothReject": 112, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 @@ -198,6 +198,17 @@ "pilotErrors": 4, "defaultBucket": "pilot-only-rejects" }, + { + "path": "extensions/x12-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", @@ -927,6 +938,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", @@ -3146,8 +3193,8 @@ } ], "totals": { - "cases": 323, - "bothReject": 314, + "cases": 328, + "bothReject": 319, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 diff --git a/docs/project/pilot-rejection.md b/docs/project/pilot-rejection.md index f291d4055e..565cca1f12 100644 --- a/docs/project/pilot-rejection.md +++ b/docs/project/pilot-rejection.md @@ -45,11 +45,13 @@ mandatory header — `// Invalid: ().` — naming the one rule where that rule comes from; the harness refuses a corpus file without it. Cases were derived systematically from four sources, one subdirectory each: -1. **`grammar/` — grammar mutation** (108 cases: 20 original, 45 added along the *unreached* axis +1. **`grammar/` — grammar mutation** (112 cases: 20 original, 45 added along the *unreached* axis described below, 13 from a second sweep, 7 body-position cases `g61`–`g67` from the constraint census described under `semantic/`, the two second-result-expression - bodies `g69`/`k20`, the two name-before-keyword members `g70`/`k21`, and the seventeen prefix and - body-context cases `g71`–`g81`, `k22`–`k27`: exclusive prefix alternatives written together + bodies `g69`/`k20`, the two name-before-keyword members `g70`/`k21`, the seventeen prefix and + body-context cases `g71`–`g81`, `k22`–`k27`, and the four owning-body members `g82`–`g85` — + `frame` in a view and a view def body, `filter` in a rendering def body and `accept` in a view + body, each a syntax error under the pinned grammar —: exclusive prefix alternatives written together (`abstract variation`, `composite portion`) or repeated (`composite composite`), a repeated direction (on an ordinary and on a cross feature), `ref`, `constant` and `const` on a definition, `variation` in KerML, an invalid string escape, a signed multiplicity bound, a `transition` in a part def body and the @@ -60,10 +62,10 @@ systematically from four sources, one subdirectory each: multiplicity on a definition, `g07`/`g08` state members in a part def body), a token from a sibling production (`g15` a keyword as a name, `k02` a SysML keyword in KerML), and unterminated bodies and comments (`g01`, `g12`, `k05`). -2. **`extensions/` — the notation we invented** (11 cases). Every state-machine construct our +2. **`extensions/` — the notation we invented** (12 cases). Every state-machine construct our `examples/` tree uses that no pinned production admits: `initial`, `choice`, `junction`, `history`, `region`, the since-removed `defer` member, and the `transition to ` shorthand, plus `require` - outside a requirement body (`x08`, since made a parse error), `expose` in a view def body (`x09`), which our grammar admits as an extension, and KerML's `connector` declared in a `.sysml` file (`x10`), which the default mode keeps parsed as a `kerml-notation` warning and strict mode refuses. The pinned grammar + outside a requirement body (`x08`, since made a parse error), `expose` in a view def body (`x09`), which our grammar admits as an extension, KerML's `connector` declared in a `.sysml` file (`x10`), which the default mode keeps parsed as a `kerml-notation` warning and strict mode refuses, and a one-name `then ;` in a view body (`x12`), a succession member no non-action body admits. The pinned grammar spells entry as `entry; then `, concurrency as `state ... parallel`, and transitions as `first then `, and has no pseudostates or deferral at all. Adjudication: these are **intended OpenSysML extensions**, not accidents — each has dedicated parser tests @@ -116,7 +118,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 323 +What this corpus cannot see: it tests the invalid models we thought to write. **We authored all 328 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 +166,14 @@ measured at their own round and are not the current baseline. Under the default `-conformance auto`: ``` -323 case(s): 314 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept +328 case(s): 319 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 | 11 | 11 | 0 | 0 | 0 | -| grammar | 108 | 108 | 0 | 0 | 0 | +| extensions | 12 | 12 | 0 | 0 | 0 | +| grammar | 112 | 112 | 0 | 0 | 0 | | semantic | 169 | 160 | 0 | 9 | 0 | | xpect | 35 | 35 | 0 | 0 | 0 | @@ -235,7 +237,10 @@ The action-body parameter prefix-order cases `g80`–`g81` then raised the corpu (a package-level allocation whose ends name a definition's owned features by qualified name, `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, and `x11` (an indexed connector end, `connect s.y#(1) to k.u`, which the pinned grammar has no production for — `mismatched input '#' expecting 'to'` — and which we report as `nonstandard-notation` by default and reject under strict) to 313, and `s102`/`s103` (a part subsetting an attribute definition and a part redefining a part definition: KerML's `Subsetting::subsettedFeature` and `Redefinition::redefinedFeature` are typed Feature, a rule SysML usages inherit, so the pilot fails to link either — `Couldn't resolve reference to Feature` — and we report `subsets`/`redefines target must be a feature` at the same span, in `.sysml` sources as in `.kerml` ones) to 315, and `s104`–`s107` (a second objective in an analysis case definition and in an analysis case usage, `Only one objective is allowed.`, and a declared `exhibit state e references p;` / `perform action f references p;` naming a part, `Must reference a state.` / `Must reference an action.` at the `p` — each reported where the pilot reports it) to 319; `s20b` (an `include` whose target is a use case definition — `useCaseIncluded` is a reference subsetting, so a definition is no feature it can name; both validators report `Must reference a use case.` under `validateIncludeUseCaseUsageReference`) joined later, and the corpus stands at 323. +`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, and `x11` (an indexed connector end, `connect s.y#(1) to k.u`, which the pinned grammar has no production for — `mismatched input '#' expecting 'to'` — and which we report as `nonstandard-notation` by default and reject under strict) to 313, and `s102`/`s103` (a part subsetting an attribute definition and a part redefining a part definition: KerML's `Subsetting::subsettedFeature` and `Redefinition::redefinedFeature` are typed Feature, a rule SysML usages inherit, so the pilot fails to link either — `Couldn't resolve reference to Feature` — and we report `subsets`/`redefines target must be a feature` at the same span, in `.sysml` sources as in `.kerml` ones) to 315, and `s104`–`s107` (a second objective in an analysis case definition and in an analysis case usage, `Only one objective is allowed.`, and a declared `exhibit state e references p;` / `perform action f references p;` naming a part, `Must reference a state.` / `Must reference an action.` at the `p` — each reported where the pilot reports it) to 319; `s20b` (an `include` whose target is a use case definition — `useCaseIncluded` is a reference subsetting, so a definition is no feature it can name; both validators report `Must reference a use case.` under `validateIncludeUseCaseUsageReference`) joined later, and the corpus stands at 323. The owning-body members `g82`–`g85` +(`frame` in a view and a view def body, `filter` in a rendering def body, `accept` in a view body — +each a syntax error under the pinned grammar, as our parser now reports them) and `x12` (`then +;` in a view body, a succession member no non-action body admits) brought it to 328. 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 +285,7 @@ when it was first written, six were closed by the validation work itself — `p0 Read those four 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 323 cases ourselves, a small gap count means we ran out of questions we +because we authored all 328 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 36b81f9332..7a83de8d36 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2246,7 +2246,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 node labels use Markdown only when every label line is safe: head lines are bold, the keyword is italic and details are plain, separated by real newlines. Container titles instead put the italic keyword and bold first name line together, followed by further head lines and details; unsafe text falls back to the escaped plain label. Leaf state, sequence and edge labels remain unchanged; flowchart titles add `subGraphTitleMargin` frontmatter only when they still span several lines. 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. D2 writes only tree, interconnection, state, action, sequence, requirement, definition and package renderings; case and mixed views refuse it with a typed error | `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, case, mixed — 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, evaluated against the exposed elements the concern's subject admits — the view restates no framing, since `FramedConcernMember` is a requirement, concern and viewpoint body member and no view body one (8.3.20, 8.3.26) 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` (every concern the viewpoint frames evaluated, those it inherits included, a `frame` the view writes ignored; 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 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, 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 — the view restates no framing, since `FramedConcernMember` is a requirement, concern and viewpoint body member and no view body one (8.3.20, 8.3.26) 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` (every concern the viewpoint frames evaluated, those it inherits included, an untyped framing, a framing naming a concern usage; a false condition; an unevaluable one; no admissible subject; 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 (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, 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;` or `@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 is the metadata-classification row above — a separate layer, `runtime/`, decided by this same compiled predicate) | | 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) | @@ -2499,7 +2499,7 @@ derivations, the before/after measurements and the rows still open are in | An association end that declares its cross feature inline must declare the feature `crosses` names (`Must be the cross feature`; KerML 8.3.4.5) | `ast/defusage.go` `CrossFeatureMember` (immutable, with its own identification, multiplicity and relationships); `parser/defusage.go` preserves it; `passes/w10b_cross_features.go` `checkDeclaredCrossFeature` compares it with the `crosses` target | `passes/w12d_rules_test.go`, `parser/w12d_parser_test.go`, golden `w12d_end_cross_feature`, pilot Xpect `AssociationTest_CrossFeatures_invalid.kerml.xt`:52 | ✅ Faithful | | A parallel state owns no successions or transitions — its substates are concurrent regions (`A parallel state cannot have successions or transitions.`; SysML v2 §7.16 `StateDefinition`/`StateUsage::isParallel`) | `ast/defusage.go` `IsParallel` on definition and usage; `parser/defusage.go` (a body is required, so `state def S parallel ;` stays a parse error); `passes/state_transition.go` `checkParallel`; `lower/state_graph.go` `parallelRegions` synthesizes graph-only regions from direct substates, preserves their behaviors, and reuses the extension region IR | `passes/w12d_rules_test.go`, golden `w12d_parallel_state`, `parser` negative case, pilot Xpect `TransitionUsage_invalid.sysml.xt`:45,68; `lower/state_graph_nested_test.go:TestToStateGraph_ParallelMatchesExplicitRegions`, `TestToStateGraph_ParallelStateBehaviors`, `runtime/state_parallel_standard.sysml`, `state_parallel_completion.sysml`, `state_region_completion.sysml`, `state_parallel_broadcast.sysml`, `state_parallel_accept_after.sysml`, `state_parallel_entry_behavior.sysml`, `state_nested_parallel_entry_exit_behavior.sysml`, `state_nested_parallel_region_owner_behavior.sysml` | ✅ Faithful for state definitions and usages with bodies, including nested parallel composites, nested region-owner behavior and deferred triggers, and the parallel state's entry/do/exit behavior; standard and extension region graphs complete only after every concurrent region completes, nested region sets included; typed behavior bindings reuse the lowered definition. | | A transition with an accepter has a state as its source, since only a state is active over the interval in which the accepter waits (`A transition with an accepter must have a state as its source.`; SysML v2 §7.16) | `passes/state_transition.go` `checkAccepterSource`, reported at the trigger's span (`ast.TransitionMember.TriggerSpan`) | `passes/w12d_rules_test.go`, `passes/state_transition_test.go`, pilot Xpect `TransitionUsage_invalid.sysml.xt`:54 | ⚠️ Approximate (fires only where the source resolves to a performed action; a transition out of a state whose entry action is named stays legal, and other non-vertex sources are left to the pre-existing endpoint rule) | -| A member only one body kind offers is rejected elsewhere, at the parser as in the pilot's grammar: `subject` and `actor` belong to a requirement or case body (`RequirementBody`, `CaseBody`; `validateSubjectMembershipOwningType`, `validateActorMembershipOwningType`), `stakeholder` to a requirement body (`validateStakeholderMembershipOwningType`), `objective` to a case body (`validateObjectiveMembershipOwningType`), `entry`/`do`/`exit` to a state body (`StateBody`; `validateStateSubactionMembershioOwningType`, spelled as the pilot spells it), `render` to a view body (`ViewBody`; `validateViewRenderingMembershipOwningType`), a `transition first s1 then s2;` member to a state body (`TransitionUsageMember` is a `StateBodyItem` alone), and `expose` to a view *usage* body (`ViewBodyItem`; `ViewDefinitionBodyItem` has no `Expose`). Definitions and usages of the owning kind admit them alike, a reference body (`include x { … }`, `perform a { … }`, `frame c { … }`) takes its kind's body, and a nested usage of another kind does not inherit its owner's body kind | `parser/parser.go` `bodyContext` (one notation per body kind, pushed by `parser/defusage.go` `defBodyContext`/`usageBodyContext` around every definition, usage and reference body); `parser/defusage.go` `ownedMembers`, `Parser.bodyAdmitsMember`, `Parser.misplacedMember` (diagnostic and `ErrorNode` in place of the member; the member's own syntax is still consumed so the body recovers at the next member) | `parser/owned_member_test.go` `TestMemberOutsideOwningBodyIsRejected` (each member in a part definition and usage, an action, a constraint, a package, a `perform` body, a subject's, required constraint's and rendering's own body, in the wrong owning kind — `stakeholder` in a case, `objective` and `render` in a requirement — and with visibility and metadata prefixes), `TestMemberInsideOwningBodyStaysClean` (requirement, concern, viewpoint, case, analysis, verification, use-case, view and state definitions and usages, `frame`, `include`, `perform`, `exhibit` and `satisfy` bodies, `entry; then s;`, transitions, inline and braced state actions, parallel and nested states); golden `parse/owned_body_members`; `tools/referee/reject/testdata/negative/grammar/g07`, `g08`, `g61`–`g66`, `g77` (all both-reject); `parser/grammar_prefix_test.go` `TestNegativeBodyContext`; `model/body_context_test.go` `TestBodyContextFindingsSurviveAnUnrelatedSyntaxError` (the finding stays reported beside an unrelated syntax error in the same file, since the syntax tier is not gated); `TestStdlibConformance` and the four OMG corpora unchanged | ✅ Faithful (the pilot rejects the same models with `mismatched input '' expecting '}'`; ours names the owning body and the fix. Three placements the grammar likewise forbids stay accepted as documented extensions, so they draw the syntax-tier `nonstandard-notation` warning — an error under strict mode — instead of a parse error: `expose` in a view def body, `require`/`assume` outside a requirement-style body and `frame` in a view definition or usage body (`passes/nonstandard_notation.go` `notationWalker.expose`, `notationWalker.framedConcern`; `passes/nonstandard_notation_test.go` `TestFrameInAViewBodyIsReported`, `TestFrameOutsideAViewBodyIsSilent`; `tools/referee/reject/testdata/negative/extensions/x08`, `x09`, both agreeing with the pilot under strict mode alone). `variant` outside a `variation` is well-formed syntax (`VariantUsageMember` is a `DefinitionBodyItem`) that the pilot rejects semantically, so it stays at the constraint tier as an element-scoped rule that a syntax error elsewhere no longer hides: `passes/variant_owner.go` after `validateVariationMembershipOwningNamespace`) | +| A member only one body kind offers is rejected elsewhere, at the parser as in the pilot's grammar: `subject` and `actor` belong to a requirement or case body (`RequirementBody`, `CaseBody`; `validateSubjectMembershipOwningType`, `validateActorMembershipOwningType`), `stakeholder` to a requirement body (`validateStakeholderMembershipOwningType`), `objective` to a case body (`validateObjectiveMembershipOwningType`), `entry`/`do`/`exit` to a state body (`StateBody`; `validateStateSubactionMembershioOwningType`, spelled as the pilot spells it), `render` to a view body (`ViewBody`; `validateViewRenderingMembershipOwningType`), a `transition first s1 then s2;` member to a state body (`TransitionUsageMember` is a `StateBodyItem` alone), `frame` to a requirement, concern or viewpoint body (`FramedConcernMember` is a `RequirementBodyItem` alone), and `expose` to a view *usage* body (`ViewBodyItem`; `ViewDefinitionBodyItem` has no `Expose`). Definitions and usages of the owning kind admit them alike, a reference body (`include x { … }`, `perform a { … }`, `frame c { … }`) takes its kind's body, and a nested usage of another kind does not inherit its owner's body kind | `parser/parser.go` `bodyContext` (one notation per body kind, pushed by `parser/defusage.go` `defBodyContext`/`usageBodyContext` around every definition, usage and reference body); `parser/defusage.go` `ownedMembers`, `Parser.bodyAdmitsMember`, `Parser.misplacedMember` (diagnostic and `ErrorNode` in place of the member; the member's own syntax is still consumed so the body recovers at the next member) | `parser/owned_member_test.go` `TestMemberOutsideOwningBodyIsRejected` (each member in a part definition and usage, an action, a constraint, a package, a `perform` body, a subject's, required constraint's and rendering's own body, in the wrong owning kind — `stakeholder` in a case, `objective` and `render` in a requirement — and with visibility and metadata prefixes), `TestMemberInsideOwningBodyStaysClean` (requirement, concern, viewpoint, case, analysis, verification, use-case, view and state definitions and usages, `frame`, `include`, `perform`, `exhibit` and `satisfy` bodies, `entry; then s;`, transitions, inline and braced state actions, parallel and nested states); golden `parse/owned_body_members`; `tools/referee/reject/testdata/negative/grammar/g07`, `g08`, `g61`–`g66`, `g77` (all both-reject); `parser/grammar_prefix_test.go` `TestNegativeBodyContext`; `model/body_context_test.go` `TestBodyContextFindingsSurviveAnUnrelatedSyntaxError` (the finding stays reported beside an unrelated syntax error in the same file, since the syntax tier is not gated); `TestStdlibConformance` and the four OMG corpora unchanged | ✅ Faithful (the pilot rejects the same models with `mismatched input '' expecting '}'`; ours names the owning body and the fix. Two placements the grammar likewise forbids stay accepted as documented extensions, so they draw the syntax-tier `nonstandard-notation` warning — an error under strict mode — instead of a parse error: `expose` in a view def body and `require`/`assume` outside a requirement-style body (`passes/nonstandard_notation.go` `notationWalker.expose`; `tools/referee/reject/testdata/negative/extensions/x08`, `x09`, both agreeing with the pilot under strict mode alone). `variant` outside a `variation` is well-formed syntax (`VariantUsageMember` is a `DefinitionBodyItem`) that the pilot rejects semantically, so it stays at the constraint tier as an element-scoped rule that a syntax error elsewhere no longer hides: `passes/variant_owner.go` after `validateVariationMembershipOwningNamespace`) | | A metadata prefix may be written on any member declaration, including `subject`, `actor` and `stakeholder` members (KerML 8.2.4 `PrefixMetadataMember`; SysML v2 §7.20) | `ast/behavior.go` `Prefixes` on the three member nodes; `parser/behavior.go` consumes prefix metadata before the name | `parser/w12d_parser_test.go`, golden `w12d_requirement_prefix_metadata`, pilot Xpect `SemanticMetadata_valid.sysml.xt`:53 (a false positive on a valid file, now silent) | ✅ Faithful | | A `#M` prefix on a `subject`, `assume` or `require` member annotates the usage the member owns (SysML.xtext `SubjectUsage` is `'subject' UsageExtensionKeyword* Usage`, `RequirementConstraintUsage` is `UsageExtensionKeyword* ConstraintUsageKeyword ConstraintUsageDeclaration`, and `UsageExtensionKeyword` is a `PrefixMetadataMember`), so it means what a prefix on an ordinary usage means: a `SemanticMetadata` keyword makes the member specialize its `baseType` (`Metaobjects.kerml`), and a metadata definition that redefines `annotatedElement` is checked against the member's metaclass | `ast/metadata.go` `DeclaredMetadata` (the prefixes and body of every annotatable declaration, the three members included), read by `semantics/metadata.go` `MetadataAnnotationsOf` and `semanticMetadataBases`, `semantics/annotations.go` `declaredAnnotations`, `passes/w8c_metadata_type.go` `w8cPrefixMetadata`; `semantics/model.go` `RelationshipsOf` and `semantics/multiplicity.go` `MultiplicityOf` read the members' own typing, specializations and multiplicity | `semantics/requirement_member_metadata_test.go` (`MetadataAnnotationsOf` on all three, a semantic keyword's `baseType` among their supertypes, plain metadata adding none), `passes/w8c_metadata_annotation_test.go` `TestMetadataAnnotatedElementOnRequirementMembers` (a `SysML::Usage`-only definition accepted on all three and on a `part`, a `SysML::Definition`-only one reported on all four), `passes/w8c_metadata_type_test.go` `TestW8CMetadataAbstractTypeOnRequirementMembers` | ✅ Faithful | | A semantic metadata definition that binds no `baseType` of its own inherits the binding of the metadata definition it specializes, so `metadata def K :> M;` classifies its annotated elements as `M` does, while an own `:>> baseType = …` in `K` takes precedence (KerML `Metaobjects::SemanticMetadata`: `baseType` is an ordinary feature, redefined or inherited like any other; SysML v2 §7.27.4) | `semantics/metadata.go` `Model.baseTypeOf` (own binding first, then direct supertypes, cycle-guarded) | `semantics/requirement_member_metadata_test.go` `TestSemanticMetadataInheritsBaseType`, `passes/mosa_test.go` (`#keyInterface` as a modular system interface) | ✅ Faithful | diff --git a/docs/reference/diagnostics.md b/docs/reference/diagnostics.md index 139a0991de..d2b8b530c6 100644 --- a/docs/reference/diagnostics.md +++ b/docs/reference/diagnostics.md @@ -263,8 +263,10 @@ _Generated from the diagnostic code registry by `make diagnostics-catalogue`; `m |------|----------|---------| | [`actor-membership-owning-type`](#actor-membership-owning-type) | error | `actor` outside a requirement or case body | | [`defer-notation-removed`](#defer-notation-removed) | error | `defer ;` in a state body, a removed extension | +| [`element-filter-membership-owning-type`](#element-filter-membership-owning-type) | error | `filter` outside a package or view body | | [`enumeration-body-member`](#enumeration-body-member) | error | a member an enumeration definition body does not admit | | [`expose-owning-namespace`](#expose-owning-namespace) | error | `expose` outside a view body | +| [`framed-concern-membership-owning-type`](#framed-concern-membership-owning-type) | error | `frame` outside a requirement, concern or viewpoint body | | [`import-visibility`](#import-visibility) | error | an `import` written without `public`, `private` or `protected` | | [`kerml-notation`](#kerml-notation) | warning | KerML notation in a SysML file | | [`nonstandard-notation`](#nonstandard-notation) | warning | notation OpenSysML reads that no production of the SysML v2 grammar admits | @@ -296,6 +298,14 @@ The state body member `defer ;` was an OpenSysML extension that SysML v2 Specification: SysML v2 §7.16 (states have no deferral notation) +#### `element-filter-membership-owning-type` + +**error** · Syntax · `filter` outside a package or view body + +An element filter is a member of a package or a view: `filter` is admitted at the file root, in a package body, or in the body of a view definition or usage only. Move the member into the package or view whose members it selects. + +Specification: SysML.xtext ElementFilterMember (a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem) + #### `enumeration-body-member` **error** · Syntax · a member an enumeration definition body does not admit @@ -312,6 +322,14 @@ An expose is an import a view usage owns: `expose` is admitted in the body of a Specification: SysML v2 validateExposeOwningNamespace +#### `framed-concern-membership-owning-type` + +**error** · Syntax · `frame` outside a requirement, concern or viewpoint body + +A framed concern is a member of a requirement: `frame` is admitted in the body of a requirement, concern or viewpoint definition or usage only. Move the member into the requirement or viewpoint that frames the concern. + +Specification: SysML.xtext FramedConcernMember (a RequirementBodyItem) + #### `import-visibility` **error** · Syntax · an `import` written without `public`, `private` or `protected` diff --git a/examples/disposal-robot-demo/robot.sysml b/examples/disposal-robot-demo/robot.sysml index 1dfe8e0e4b..7b3b2673a1 100644 --- a/examples/disposal-robot-demo/robot.sysml +++ b/examples/disposal-robot-demo/robot.sysml @@ -581,7 +581,8 @@ 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; diff --git a/internal/check/codes/table_syntax.go b/internal/check/codes/table_syntax.go index 2ea0ca78ef..051210c997 100644 --- a/internal/check/codes/table_syntax.go +++ b/internal/check/codes/table_syntax.go @@ -116,6 +116,20 @@ var syntaxCodes = []Entry{ "Move the transition into the state whose substates it joins, or write a succession (`first a then b;`) between actions.", Spec: "SysML v2 §8.2.2.19 TransitionUsageMember (a StateBodyItem)", }, + { + Code: "framed-concern-membership-owning-type", Tier: TierSyntax, Severity: "error", + Summary: "`frame` outside a requirement, concern or viewpoint body", + Meaning: "A framed concern is a member of a requirement: `frame` is admitted in the body of a requirement, concern or viewpoint definition or usage only. " + + "Move the member into the requirement or viewpoint that frames the concern.", + Spec: "SysML.xtext FramedConcernMember (a RequirementBodyItem)", + }, + { + Code: "element-filter-membership-owning-type", Tier: TierSyntax, Severity: "error", + Summary: "`filter` outside a package or view body", + Meaning: "An element filter is a member of a package or a view: `filter` is admitted at the file root, in a package body, or in the body of a view definition or usage only. " + + "Move the member into the package or view whose members it selects.", + Spec: "SysML.xtext ElementFilterMember (a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem)", + }, { Code: "view-rendering-membership-owning-type", Tier: TierSyntax, Severity: "error", Summary: "`render` outside a view body", diff --git a/internal/check/passes/nonstandard_notation.go b/internal/check/passes/nonstandard_notation.go index 97ffb09f0b..7ba9313c19 100644 --- a/internal/check/passes/nonstandard_notation.go +++ b/internal/check/passes/nonstandard_notation.go @@ -89,12 +89,13 @@ 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 - // inViewBody records that the body being walked is a view definition's or - // view usage's, neither of which admits a FramedConcernMember. - inViewBody bool // inKerMLDeclaration counts the enclosing declarations already reported as // KerML notation; their members move with them, so are not reported again. inKerMLDeclaration int @@ -176,7 +177,6 @@ func (w *notationWalker) walk(members []ast.Node) { } w.sysmlDeclaration(n, n.Keyword) w.keywordAsName(n.Ident) - w.framedConcern(n) w.indexedEnds(n) w.walkDeclaration(n.Members, n) w.leaveKerMLDeclaration(reported) @@ -237,22 +237,25 @@ 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, view := w.inActionBody, w.inViewDefBody, w.inViewBody + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody w.inActionBody = admitsActionBodyItems(declaration) w.inViewDefBody = isViewDefinition(declaration) - w.inViewBody = w.inViewDefBody || isViewUsage(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, w.inViewBody = action, viewDef, view + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state } // walkPackageMembers walks a namespace or package body, the member lists a fix -// may add an import to. +// may add an import to; such a body admits no action or state items. func (w *notationWalker) walkPackageMembers(members []ast.Node) { + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody + w.inActionBody, w.inViewDefBody, w.inStateBody = false, false, false w.bodies = append(w.bodies, bodyFrame{members: members, namespaceish: true}) w.walk(members) w.bodies = w.bodies[:len(w.bodies)-1] + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state } // walkActionBody walks the body of an action node, which is an ActionBody @@ -261,10 +264,10 @@ func (w *notationWalker) walkActionBody(members []ast.Node) { if len(members) == 0 { return } - action, viewDef, view := w.inActionBody, w.inViewDefBody, w.inViewBody - w.inActionBody, w.inViewDefBody, w.inViewBody = true, false, false + action, viewDef := w.inActionBody, w.inViewDefBody + w.inActionBody, w.inViewDefBody = true, false w.walk(members) - w.inActionBody, w.inViewDefBody, w.inViewBody = action, viewDef, view + w.inActionBody, w.inViewDefBody = action, viewDef } func isViewDefinition(node ast.Node) bool { @@ -272,9 +275,27 @@ func isViewDefinition(node ast.Node) bool { return ok && def.Kind == ast.DefView } -func isViewUsage(node ast.Node) bool { - usage, ok := node.(*ast.Usage) - return ok && usage.Kind == ast.UsageView +// 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 @@ -310,13 +331,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") @@ -418,16 +451,6 @@ func (w *notationWalker) stateActivityImport(n *ast.Import) { "neither States.sysml nor StatePerformances.kerml declares `isActive` on a state usage, so a conforming tool leaves `x.isActive` unresolved") } -// framedConcern reports a `frame` in a view body: FramedConcernMember belongs to -// requirement, concern and viewpoint bodies only (SysML v2 §8.3.20, §8.3.26). -func (w *notationWalker) framedConcern(n *ast.Usage) { - if !w.inViewBody || n.Kind != ast.UsageFramedConcern { - return - } - w.extension(w.declarationKeywordSpan(n, "frame"), "`frame` in a view body", - "only a requirement, concern or viewpoint body frames a concern; a view is checked against the concerns the viewpoints it satisfies frame") -} - // transition reports a `transition` in an action body: only `succession … if …` // is standard there (SysML.xtext GuardedSuccession); TransitionUsageMember is a StateBodyItem. func (w *notationWalker) transition(n *ast.TransitionMember) { @@ -443,9 +466,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_strict_test.go b/internal/check/passes/nonstandard_notation_strict_test.go index b384cafc7f..16cda22020 100644 --- a/internal/check/passes/nonstandard_notation_strict_test.go +++ b/internal/check/passes/nonstandard_notation_strict_test.go @@ -16,8 +16,6 @@ var extensionInventory = []string{ "state def S { shallow history h; }", "state def S { deep history h; }", "package P { view def V { expose P::*; } }", - "package P { concern def C; view def V { frame concern c : C; } }", - "package P { concern def C; view v { frame concern c : C; } }", "action def A { action x; action y; transition first x then y; }", "action def A { action a; decide d; succession s first d if true then a; else a; }", "action def A { action a; merge m; succession first m then a; then a; }", diff --git a/internal/check/passes/nonstandard_notation_test.go b/internal/check/passes/nonstandard_notation_test.go index c4c8f22394..9fdb597fb9 100644 --- a/internal/check/passes/nonstandard_notation_test.go +++ b/internal/check/passes/nonstandard_notation_test.go @@ -117,62 +117,6 @@ func TestStateExtensionsAreReported(t *testing.T) { } } -// A `frame` in a view body is an extension: FramedConcernMember belongs to a -// requirement, concern or viewpoint body. Both spellings are reported, in a view -// definition, a view usage and a view nested in another view alike. -func TestFrameInAViewBodyIsReported(t *testing.T) { - const lib = "concern def C; concern c : C; " - for _, src := range []string{ - lib + "view def V { frame concern k : C; }", - lib + "view def V { frame c; }", - lib + "view v { frame concern k : C; }", - lib + "view v { frame c; }", - lib + "view v { view inner { frame c; } }", - lib + "part def P { view v { frame c; } }", - } { - wantNotation(t, "a.sysml", src, CodeNonstandardNotation, "`frame` in a view body") - } -} - -// A `frame` where the grammar places it stays silent: in a requirement, -// concern or viewpoint body, including one declared inside a view body. -func TestFrameOutsideAViewBodyIsSilent(t *testing.T) { - const lib = "concern def C; concern c : C; " - for _, src := range []string{ - lib + "viewpoint def VP { frame concern k : C; frame c; }", - lib + "viewpoint vp { frame c; }", - lib + "requirement def R { frame c; }", - lib + "requirement r { frame concern k : C; }", - lib + "concern def D { frame c; }", - lib + "view def V { viewpoint vp { frame c; } }", - lib + "view v { requirement r { frame c; } }", - } { - wantSilent(t, "a.sysml", src) - } -} - -// The `frame` warning points at the keyword, not at the whole member. -func TestFrameInAViewBodyIsReportedAtItsKeyword(t *testing.T) { - src := `concern def C; - view def V { - frame concern k : C; - }` - root, pd, idx := analyzeInputs(t, "a.sysml", src) - if len(pd) != 0 { - t.Fatalf("%s: parse errors %+v", src, pd) - } - got := (NonstandardNotationPass{}).Run(NewContext("a.sysml", idx, pd), "a.sysml", root) - if len(got) != 1 { - t.Fatalf("%s: got %d diagnostics %+v, want 1", src, len(got), got) - } - if line := strings.Count(src[:got[0].Span.Offset], "\n") + 1; line != 3 { - t.Errorf("diagnostic is on line %d, want 3", line) - } - if got[0].Span.Len != len("frame") { - t.Errorf("diagnostic spans %d bytes, want the keyword", got[0].Span.Len) - } -} - // A transition stating its ends with `first` and `then` is standard. func TestStandardTransitionStaysSilent(t *testing.T) { wantSilent(t, "a.sysml", "state def S { state a; state b; transition first a then b; }") @@ -323,6 +267,44 @@ func TestTargetSuccessionAfterANonActionMemberIsAnExtension(t *testing.T) { wantSilent(t, "a.kerml", "behavior A { step a; then a; }") } +// Outside an action body (TargetSuccessionMember) or a state body (a target +// transition), `then ;` 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) + // A package body admits no target succession: the parser rejects it, and the + // pass must not stack a notation finding on the same span. + for _, src := range []string{ + "action def A { package P { action x; then y; } }", + "state def S { package P { action x; then y; } }", + } { + root, pd, idx := analyzeInputs(t, "a.sysml", src) + if !hasParseError(pd) { + t.Fatalf("%s: want a parse error on the package body's `then`", src) + } + for _, d := range (NonstandardNotationPass{}).Run(NewContext("a.sysml", idx, pd), "a.sysml", root) { + if d.Code == CodeNonstandardNotation { + t.Errorf("%s: got notation finding %+v, want none beside the parse error", src, d) + } + } + } + + 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 b86acb1388..bc57c6194e 100644 --- a/internal/exec/runtime/signal_test.go +++ b/internal/exec/runtime/signal_test.go @@ -1730,9 +1730,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; @@ -2032,7 +2032,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/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_test.go b/internal/semantic/semantics/conformance_test.go index fec7d1a824..c5d854a42b 100644 --- a/internal/semantic/semantics/conformance_test.go +++ b/internal/semantic/semantics/conformance_test.go @@ -205,27 +205,6 @@ func TestViewConformanceInheritedSatisfy(t *testing.T) { wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) } -// A `frame` a view, its definition or a nested view writes is no SysML v2 -// member of a view body and is not consulted: the concerns evaluated are the -// viewpoint's, whatever the view says. -func TestViewConformanceIgnoresTheViewsOwnFraming(t *testing.T) { - m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; - view def StructureView { frame concern cost : CostConcern; } - view v : StructureView { - expose vehicle; satisfy vp; - frame concern cost : CostConcern; - view inner { frame concern cost : CostConcern; } - } - `) - eval := &fakeEvaluator{} - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) - wantNames(t, "concerns of vp", concernNames(vp), []string{"mass"}) - wantNames(t, "concerns evaluated", eval.asked, []string{"mass/vehicle"}) - wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) -} - // A concern a viewpoint inherits is framed by the viewpoint too, so the view is // held to it after the viewpoint's own. func TestViewConformanceInheritedViewpointFraming(t *testing.T) { @@ -414,6 +393,20 @@ func TestViewConformanceIgnoresSatisfyWithASubject(t *testing.T) { wantVerdict(t, "verdict of v", report.Verdict, VerdictConforms, vp.Reason) } +// 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 UntypedVP { frame concern mass; } + viewpoint untypedVp : UntypedVP; + view v { expose vehicle; satisfy untypedVp; } + `) + 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 the viewpoint's framings come back in declaration order even // where a named declaration and an anonymous reference are mixed, which the // symbol table keeps in separate member lists. diff --git a/internal/syntax/parser/behavior.go b/internal/syntax/parser/behavior.go index d50f9d7a0b..782c52c650 100644 --- a/internal/syntax/parser/behavior.go +++ b/internal/syntax/parser/behavior.go @@ -695,10 +695,13 @@ func (p *Parser) parseInitialNode(tok lexer.Token) ast.Node { p.error(p.peek().Span, "expected 'then' after guard condition") } - // 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) + // A `first … then …` succession's body is the ActionTargetSuccession's + // UsageBody (SysML.xtext); a one-ended `first a { … }` ends in a RelationshipBody. + ctx := bodyOther + if successor != nil && p.bodyContext().admitsAcceptNode() { + ctx = bodySuccession + } + members, hasBody := p.parseNodeBodyContext(start, "initial node", ctx) node := &ast.InitialNode{ First: first, @@ -1067,12 +1070,17 @@ 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): + // a `then { accept … }` 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 a261b96216..a21353dc65 100644 --- a/internal/syntax/parser/defusage.go +++ b/internal/syntax/parser/defusage.go @@ -2050,6 +2050,12 @@ var ownedMembers = map[string]ownedMember{ "do": {"the do action of a state", "state", []bodyContext{bodyState}, "state-subaction-membership-owning-type"}, "exit": {"the exit action of a state", "state", []bodyContext{bodyState}, "state-subaction-membership-owning-type"}, "render": {"the rendering of a view", "view", []bodyContext{bodyViewDef, bodyView}, "view-rendering-membership-owning-type"}, + // 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}, "framed-concern-membership-owning-type"}, + // 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}, "element-filter-membership-owning-type"}, // 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}, "transition-membership-owning-type"}, @@ -2072,8 +2078,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 { @@ -2118,6 +2125,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 } @@ -2897,6 +2906,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 @@ -3072,6 +3086,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..aaf66303c7 100644 --- a/internal/syntax/parser/owned_member_test.go +++ b/internal/syntax/parser/owned_member_test.go @@ -114,6 +114,19 @@ 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 nested view", "view v { view inner { frame c; } }", "frame", `kind="frame"`}, + {"frame in view inside part def", "part def P { view v { frame 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 +167,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 +216,21 @@ 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; }"}, + {"frame in viewpoint inside view def", "concern def C; view def V { viewpoint vp { frame c; } }"}, + {"frame in requirement inside view", "concern def C; view v { requirement r { 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 a602c506f6..8d12b1fcbb 100644 --- a/internal/syntax/parser/parser.go +++ b/internal/syntax/parser/parser.go @@ -116,6 +116,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, @@ -128,6 +134,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() { @@ -136,10 +150,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/model/body_context_test.go b/internal/workspace/model/body_context_test.go index 6b53d8112f..f79ead3c5c 100644 --- a/internal/workspace/model/body_context_test.go +++ b/internal/workspace/model/body_context_test.go @@ -34,7 +34,7 @@ func TestBodyContextFindingsSurviveAnUnrelatedSyntaxError(t *testing.T) { {"expose in a view def", "package P { view def V { expose P::*; }" + broken, "expose", "warning: `expose` in a view def body is an OpenSysML extension"}, {"frame in a view", "package P { concern def C; view v { frame concern c : C; }" + broken, "frame", - "warning: `frame` in a view body is an OpenSysML extension"}, + "error: 'frame' declares the concern a requirement or viewpoint frames and is only allowed in a requirement body"}, {"transition in a part def", "package P { part def D { state s1; state s2; transition first s1 then s2; }" + broken, "transition", "error: 'transition' declares a transition between states and is only allowed in a state body"}, {"variant outside a variation", "package P { part def D { variant part v : D; }" + broken, "variant", 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 801a97fe90..8897fff8db 100644 --- a/tests/export/testdata/convert/imported_references.golden.ttl +++ b/tests/export/testdata/convert/imported_references.golden.ttl @@ -1624,13 +1624,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 ; @@ -1767,7 +1768,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 ; @@ -1778,14 +1779,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 ; @@ -1830,104 +1829,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 03df09d144..c8d58e9e71 100644 --- a/tests/parser/negative_test.go +++ b/tests/parser/negative_test.go @@ -320,6 +320,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/x12-target-succession-outside-action-body.sysml b/tools/referee/reject/testdata/negative/extensions/x12-target-succession-outside-action-body.sysml new file mode 100644 index 0000000000..63b6b80d1a --- /dev/null +++ b/tools/referee/reject/testdata/negative/extensions/x12-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; + } +}