diff --git a/changes/unreleased/census-states-events.changed.md b/changes/unreleased/census-states-events.changed.md new file mode 100644 index 0000000000..d287a6f1b6 --- /dev/null +++ b/changes/unreleased/census-states-events.changed.md @@ -0,0 +1,3 @@ +- **Preserve UML opaque body text during migration.** Each raw body and matching language now appear as a non-executed textual representation with whitespace preserved, while supported body translations continue to use the trimmed first body and remain executable. +- **Retain UML state-machine structure and termination semantics.** Composite states with one region retain that region as a parallel sub-state. A region left without an active child by default entry counts as complete, whether its stand-in was entered or it has no stand-in or start; a stand-in's running do action defers that completion until it ends. A region activated later by a transition or fork branch must reach its final state. This matches PSSM *Entering 004*. A first entry through history uses the same default, while an owner with no default entry remains an error. Terminate pseudostates now terminate the whole state machine. +- **Clarify the v1-to-v2 transformation census.** Timed triggers and preserved opaque/state behavior are counted as faithful, while trigger-scoped event acceptance is recorded as deliberate. diff --git a/docs/internals/design/precise-semantics-alignment.md b/docs/internals/design/precise-semantics-alignment.md index 28de185d26..fd202e81bb 100644 --- a/docs/internals/design/precise-semantics-alignment.md +++ b/docs/internals/design/precise-semantics-alignment.md @@ -1688,7 +1688,7 @@ which supersede the hand count this section was first written with — the moves | The UML `StateMachine` as the class under test (a standalone machine with attributes, operations, a constructor) | `part def` with `attribute`s, `action def`s and `exhibit state`, as an owned machine's: the reader (`reader.go`) reads the machine as the `Target` whose `Machine` is itself, with its attributes, operations and their methods, and its constructor | standard | | A guard whose behavior acts on the model (calls `trace(...)` before returning its value) | none: a v2 guard is a Boolean expression (§7.18.3, `validateTransitionFeatureMembershipGuardExpression`; `bool guard[*]` in `TransitionPerformances.kerml`, the effect a separate `step`), and an expression has no spelling for an action. UML 2.5.1 §14.5.11 `Transition::guard` itself calls such a guard ill formed. Recording the runtime's guard reads as the referee's observable instead is refused too: the reads the suite traces are a junction's on the target's default entry, read at the incoming transition's selection, where the library orders every transition inside a state after the state's `entry` — see [A guard whose behavior acts on the model](#a-guard-whose-behavior-acts-on-the-model) | no translation | | A guard whose behavior is an opaque behavior, not an activity | none: the reader follows an activity's nodes to tell whether the behavior acts, and does not read an opaque body, so the guard is refused rather than carried as its Boolean text alone. A `FunctionBehavior` is the exception — it accesses no object by UML's contract (§13.2.3.3) — and is translated as the expression it spells | no translation | -| Fork into states of orthogonal regions that have no initial pseudostate | `parallel` regions spell the shape and the `fork` extension the fork; a region a fork enters needs no `entry; then` (finding 6 below, fixed) | extension | +| Fork into states of orthogonal regions that have no initial pseudostate | `parallel` regions spell the shape and the `fork` extension the fork; a branch enters its target without an `entry; then`. Default entry enters a region's state stand-in even without a start in its body; if no child becomes active, the region counts as complete. A region with no stand-in and no start remains inactive and also counts as complete on default entry. A branch that activates a region must reach its final state | extension | The classification is by construct, in the order of the table: a test whose model uses any construct with no spelling or no translation is counted as not expressible whatever else it @@ -1755,18 +1755,19 @@ is where a later translation moves them back. | *Event 019-E* | standard | *translated since the emitter binds entry parameters and returns outputs:* `T2` accepts `'or'(left, right)` and stores both in `trigger_v_or_left`, `trigger_v_or_right`; the two regions' substate entries declare `in left = trigger_v_or_left; in right = trigger_v_or_right;` and assign the operation's two outputs (`out result`, `out 'return'`), each region's entry writing them in the order the regions are entered, so the caller receives the second region's values and the two admitted traces are both reached and nothing else | | *Deferred 007* | extension | *translated since the emitter spells a returning effect with inputs:* the deferred `op(p1)` call fires `T4` once the second state is active; `T4`'s effect is `action def T4_effect` with `in p` bound to the accept's `p1` and `out 'return'`, its `return` an assignment of `not p`; the one admitted trace, the first state's exit, `T3(effect)`, `T4(effect)[in=true][out=false]` and the tester's `[out=false]`, is reached | | *Standalone 003* | standard | *the standalone machine is read as the target since the reader does so, and translated since the emitter binds entry parameters and returns outputs:* the same shape as *Event 019-E*, the machine itself the class under test with `or` its operation; both admitted traces are reached | -| *Fork 002* | extension | *translated since finding 6 was fixed:* the fork enters the two regions of a nested composite state, which have no initial pseudostate; the lowerer used to refuse a `parallel` region with no `entry; then` — this project's gap, not v2's | -| *Join 001* | extension | *translated since finding 6 was fixed:* the fork enters the two regions of the top-level composite state, which have no initial pseudostate; the same lowerer refusal | +| *Fork 002* | extension | *translated:* the fork enters two regions without initial pseudostates; default entry enters their state stand-ins with no active child, and the fork activates its named targets | +| *Join 001* | extension | *translated:* the fork enters two regions without initial pseudostates; an omitted region's stand-in is entered with no active child and counts as complete on default entry | | *Choice 005* | extension | *refused, settled:* the guards of the junction's and the choice's four outgoing transitions each call `trace("T1.n(guard)")` and the admitted trace records the calls, to show when each guard is read; a v2 guard is an expression with no room for an action, so the translation keeps only the guard's value and cannot reach the trace, and is refused rather than run short. Making the runtime's guard reads the referee's observable would not reach the trace either: the junction sits on the composite's default entry and the suite reads its guards before `T2(effect)` and the composite's entry, where the library reads a transition inside a state after the state's `entry` — see [A guard whose behavior acts on the model](#a-guard-whose-behavior-acts-on-the-model) | The last two were kept apart from the other seven and from the 29 with no spelling: UML allows -a fork to target states inside orthogonal regions that have no initial pseudostate, SysML v2 -`parallel` regions can spell the shape, and only the lowerer's check stood in the way. The -lowerer now accepts a region a fork enters (finding 6), so the two run and the referee reports -them in its expressible buckets; a region with neither an entry transition nor a fork branch -into it is still refused, and the classifier names that *lowerer refuses an orthogonal region -with neither an entry transition nor a fork branch into it* (*Entry 002 E*, which is not -expressible on other grounds too). +a fork to target states inside orthogonal regions that have no initial pseudostate, and SysML +v2 `parallel` regions can spell the shape. Default entry enters a region's state stand-in even +without an entry transition; if default entry leaves that stand-in with no active child, the +region counts as complete. A region with no stand-in and no start remains inactive and also +counts as complete on default entry. A later transition or fork branch that activates a region +does not make it complete; that region must reach its final state. PSSM *Entering 004* requires +immediate completion for the single-region default-entry case. Entry 002 E remains +not-expressible on its entry-point construct. #### Behavior parameters, operation results, tester traces and standalone machines @@ -2352,35 +2353,23 @@ sites of the runtime's fixed, the pool's order and the do step drawn on the entr of them has to change (second open decision). `state_choice_pseudostate` does not reach it. *Fixed* with the second open decision: the code now matches the note (SM30's *Decided* sentence), and Track E records the finding as landed. -6. **The lowerer refuses a fork into orthogonal regions that have no initial pseudostate.** - UML lets a fork's outgoing transitions enter states inside a composite state's orthogonal - regions directly, with no initial pseudostate in those regions (PSSM *Fork 002* and *Join - 001* are built this way); SysML v2 `parallel` regions can spell the shape, and this - project's `fork` extension can spell the fork. `lower.ToStateGraph` refuses it — "region - `` has no initial state; write `entry; then ;` inside the region" — because it - required every region to name its own start even when a fork was the only way in. A gap of - ours, which the PSSM referee's classifier recorded as *lowerer refuses fork into a region - without an entry transition*. *Fixed:* `lower/fork_plan.go:planForks` reads every fork's - branches into a `ForkPlan` — one target state per orthogonal region of one composite state, - no guard, at least two branches — and `ToStateGraph` accepts a region with no entry - transition when `ForkStarted` says a fork enters it, still refusing one with neither - (`robustness_test.go:fork_leaves_a_region_without_a_way_in`). Such a region has no default - start, so `checkForkOnlyRegion` also refuses a machine where any other way into the composite - — a transition to the composite itself, to a state in another of its regions or to its - history, its own self-transition, or the machine's entry naming it, directly or through a - junction, choice or join — would start the region by default, naming that way in - (`robustness_test.go:fork_only_region_entered_by_default`). The runtime consumes the plan - (`state_executor.go:fireForkTransition` → `state_region_entry.go:enterForkBranches`): the - source configuration is left down to the least common ancestor of the source and the - composite, as for a move to a single state (`leaveForFork`), so an active ancestor is - neither exited nor entered again; then each branch runs its effect, enters what is left of - the way down to the composite and its target directly (PSSM §8.5.7), a region no branch - names taking its own initial. Pinned by `state_fork_enters_regions_without_initial`, - `state_fork_in_composite_enters_parallel_substate`, `state_fork_within_active_ancestor`, - `state_fork_within_active_region` (all with trace goldens) and `lower/fork_plan_test.go`. - *Fork 002* and *Join 001* translate and run; the branches are still entered in the regions' - declaration order, so the interleavings PSSM admits beyond that one are SM22's open decision, - and `docs/project/pssm-referee.md` records where each landed. +6. **A region left without an active child by default entry completes its composite.** + UML permits a region without an initial pseudostate, and SysML v2 `parallel` can represent + it. When `RegionState[region]` supplies a stand-in, `regionStart` enters it even without a + start transition in its body; if no child becomes active, the region is complete on default + entry. When no stand-in and no start exist, the region remains inactive and is likewise + complete on default entry. `enterRegion` records this default-entry case and + `enterStateInto` queues the composite's completion after its regions are entered. A region + activated later by a transition or fork branch is not default-complete and must reach its + final state; a running do-action still defers completion. PSSM *Entering 004* verifies the + single-region case. Coverage includes `state_parallel_region_without_entry_completes`, + `TestRuntimeRobustnessInactiveRegion`, + `TestRuntimeRobustnessParallelWrapperLeavesInnerRegionInactive`, + `state_parallel_stateless_region_with_behaviors`, + `state_parallel_region_without_entry_inactive`, `state_fork_only_region_entered_by_default`, + `state_nested_fork_starts_outer_region_inactive` and + `state_history_restores_inactive_parallel_region`. The PSSM classifier no longer reports + the old lowerer refusal; Entry 002 E remains not-expressible on its entry-point construct. 7. **A transition from a composite state into its own history pseudostate reads the record before the state is left.** The configuration a history restores is written when its owner is exited (`state_executor.go:exitState` → `recordChildHistory`, `recordRegionHistory`), but diff --git a/docs/project/pssm-referee-baseline.json b/docs/project/pssm-referee-baseline.json index 35c5dc49a9..a33eb719a6 100644 --- a/docs/project/pssm-referee-baseline.json +++ b/docs/project/pssm-referee-baseline.json @@ -6,8 +6,8 @@ "url": "https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi", "suiteDigest": "c355b249c356774377a46b60345019d827af1ce417bde88e533aa5f39206ae07", "tests": 103, - "recorded": "2026-10-03", - "develop": "3d45ca6fb200339461594cd086004e461633e6d1" + "recorded": "2026-10-10", + "develop": "02db635bed117511e859db8d90b4626dd5da1a85" }, "buckets": { "differs-by-design": 7, @@ -806,7 +806,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "entry point EntryPoint1; lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it S1/Region1; lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it S1/Region2; local transition T1.1; local transition T2.1" + "entry point EntryPoint1; local transition T1.1; local transition T2.1" ] }, { diff --git a/docs/project/pssm-referee.md b/docs/project/pssm-referee.md index 1f20610934..7960c75789 100644 --- a/docs/project/pssm-referee.md +++ b/docs/project/pssm-referee.md @@ -133,8 +133,10 @@ under [Behavior parameters, operation results, tester traces and standalone mach [candidate table](../internals/design/precise-semantics-alignment.md#a-guard-whose-behavior-acts-on-the-model) records the three candidates: the settled refusal of a construct v2 cannot spell faithfully. - **A fork into orthogonal regions that have no initial pseudostate**: *Fork 002*, *Join 001* - — kept apart from the rest while the lowerer refused the shape, and translated since it - accepts it, see [Findings about our own conformance](#findings-about-our-own-conformance). + — translated with the region-entry rule: a region's state stand-in is entered on default + entry even when its body has no start, leaving no active child; a region without a stand-in + and without a start remains inactive, while fork branches enter their named targets; see + [Findings about our own conformance](#findings-about-our-own-conformance). ## Translating @@ -714,9 +716,10 @@ transition when a fork's branch enters it ([finding 6](#findings-about-our-own-c so the two tests the classifier filed under *lowerer refuses fork into a region without an entry transition* translate and run. Both moved `not-expressible` → `fail`; nothing else moved, and the eighteen other failures' reasons are byte-identical to the previous baseline's. Three -`not-expressible` tests that also carried the reason stay where they are on their other grounds: -*Transition 023* and *Standalone 002* drop it (their forks now enter their regions), *Entry 002 E* -keeps the renamed reason (its regions have neither an entry transition nor a fork branch). +`not-expressible` tests that also carried the reason stayed there on other grounds: +*Transition 023* and *Standalone 002* have their regions entered by forks, while *Entry 002 E* +also contains an entry point. The general inactive-region rule below supersedes that former +lowerer refusal. | Test | Row | Movement | Adjudication | |---|---|---|---| @@ -874,10 +877,17 @@ By reason, as the classifier names them: exit left, only by completion transitions or by paths accepting different events). - A standalone state machine is read as the target class, and a tester's `trace(...)` after a call is driven, so neither is a reason any longer; Event 019 A runs and passes. -- **lowerer refuses an orthogonal region with neither an entry transition nor a fork branch - into it** (ours): Entry 002 E, which is not expressible on other grounds too. Fork 002 and - Join 001, filed here while the lowerer refused every region without an entry transition, - translate since finding 6 was fixed. +- A composite-state region without an entry is valid: default entry enters its state stand-in; + when no child becomes active, the region counts as complete. A region with no stand-in and no + start remains inactive and also counts as complete on default entry. A region activated later + by a transition or fork branch must reach its final state. PSSM *Entering 004* verifies + immediate completion for the single-region case; `state_parallel_region_without_entry_completes` + covers it in the runtime. Entry 002 E remains not-expressible on its entry-point construct; + the no-entry refusal is no longer a classifier reason. Coverage includes + `TestRuntimeRobustnessInactiveRegion`, `state_parallel_region_without_entry_inactive`, + `state_parallel_stateless_region_with_behaviors`, `state_fork_only_region_entered_by_default`, + `state_nested_fork_starts_outer_region_inactive` and + `state_history_restores_inactive_parallel_region`. - **guard side effect** (no translation, settled): Choice 005. Making the runtime's guard reads the referee's observable instead was tried and refused: the suite reads the junction's guards at the incoming transition's selection, before its effect and the target's entry, where @@ -899,30 +909,17 @@ three tests as a translation limit and on two as the suite's defect, and its las a do step on the entry front, *Terminate 002*'s — is fixed. No test now cites an open site of the runtime; a `fail` cites a tool choice, a translation limit or the suite's defect: -- **The lowerer refused a fork into orthogonal regions that have no initial pseudostate** - (*Fork 002*, *Join 001*; alignment finding 6). UML lets a fork's outgoing transitions enter - states inside a composite state's orthogonal regions directly, with no initial pseudostate in - those regions; SysML v2 `parallel` regions can spell the shape and this project's `fork` - extension can spell the fork. `lower.ToStateGraph` refused it ("region `` has no initial - state; write `entry; then ;` inside the region") because it required every region to - name its own start even when a fork was the only way in. The two tests were filed - `not-expressible` under the distinct reason *lowerer refuses fork into a region without an - entry transition* so they were never confused with the constructs v2 has no spelling for. - Fixed: `lower/fork_plan.go` reads each fork's branches into a `ForkPlan` — one target state - per orthogonal region of one composite state — and the lowerer accepts a region a fork enters - without an entry transition of its own, still refusing one with neither (the classifier's - reason is now *lowerer refuses an orthogonal region with neither an entry transition nor a - fork branch into it*, which only *Entry 002 E* still carries, and it reads a branch's region - the way the lowerer does — the region of the fork's composite the target lies in, however - deep); the runtime leaves the source configuration — every region of the composite when - the fork is reached from inside them — runs the first branch's effect, enters the rest of the - way down to the composite, then enters every region in declaration order, each branch's - effect before its target (`state_executor.go:leaveForFork`, - `state_region_entry.go:enterForkBranches`; `state_fork_enters_regions_without_initial`, - `state_fork_in_composite_enters_parallel_substate`, - `state_fork_omitted_region_declared_first` and `state_fork_from_within_owner_regions` with - their trace goldens). The two tests - translate and run; where each landed is in the movements table above. +- **A composite-state region has no entry transition** (alignment finding 6). UML permits + such a region and SysML v2 can spell it as a parallel state's sub-state. The lowerer records + no default start; runtime default entry leaves the region out of the active configuration, + whether or not a fork can enter it. A later explicit transition or fork branch can activate + it, and the owner remains incomplete until every region is final. `regionStart`, + `enterRegion`, `regionComplete` and `stateComplete` implement the rule. The lowerer tests + `TestToStateGraph_RegionWithoutInitialOrForkRemainsInactive`, + `TestToStateGraph_ForkOmissionLeavesRegionInactive` and + `TestToStateGraph_NestedForkLeavesUnenteredRegionsInactive`; runtime fixtures cover default + entry, later transition/fork entry, history restoration and completion blocking. Entry 002 E + remains not-expressible for its entry-point construct, not for an absent region start. - **A transition from a composite state into its own history pseudostate read the record before the state was left** (*History 001-A*, *History 002-D*; alignment finding 7). The configuration a history restores is recorded when its owner is exited diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 0623e18aa3..a4213ff031 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -982,11 +982,12 @@ nothing above. | A succession (`succession first s then a;`) names its endpoints the way a transition does, so it reaches a nested, region-local or qualified vertex, and a pseudostate as well as a state | `resolve/transition.go` `ResolveEndpoint`; `resolve/edge.go` and `resolve/document.go` route succession ends; `passes/state_transition.go`; `lower/state_graph.go` `collectTransitions` (UsageSuccession and SuccessionEdge cases) → `(*StateGraph).endpointVertex`/`endpointState`, over the same `lower.EndpointResolver` a transition's endpoints resolve through | `passes/succession_endpoint_test.go:TestResolvedStateEndpointNotVertexIsReported`, `:TestStateSuccessionEndpointSpellingsAcceptVertices`, `lower/state_graph_nested_test.go:TestSuccessionReachesAPseudostate`, `:TestSuccessionQualifiedTargetNamesTheVertexItQualifies`, `:TestToStateGraph_EntrySuccessionNamesInitialState` | ✅ Faithful (previously matched the endpoint's last name segment against a flat state list, which reached no pseudostate and could bind a same-named vertex of another state) | | Two regions may declare same-named pseudostates, and each is a vertex of its own | `lower/state_graph.go` `StateGraph.Pseudostates` (declaration-ordered slice, not keyed by name), `addPseudostate` | `lower/state_graph_nested_test.go:TestSameNamedPseudostatesInSiblingRegionsAreBothCollected` | ✅ Faithful | | Junction pseudostates: the route through a junction is resolved statically before the incoming effect; every outgoing guard is read against the current data, several enabled branches are a recorded transition choice, and an unguarded branch is the default when none of the guards holds. The settled route continues through further junctions but stays open at the first choice; a history default is resolved the same way. Availability checks only the incoming transition's own route (SM32): if that route has no way through, its compound transition is not enabled. A nested default-entry junction belongs to a separate transition out of the composite's `entry` action and is read when that transition is taken, after the outer effect and composite entry; if no branch is enabled, the run fails with a typed error naming the junction (SM46), as covered by `state_entry_transition_junction_no_way_fails.sysml` and `state_entry_transition_junction_reads_outer_effect.sysml`. The occurrence may go to another enabled transition, be deferred or be discarded as unmatched; a completion with no route is dropped. For a join, selection checks the way out using only the candidate and prior arrivals. Signal/change dispatches count same-occurrence peers only when their regions chose them; firing checks completion against those peers, and a dead route fires none. Timer expiries are separate occurrences even when due together; a dead completing segment is disabled, leaving its source's timer-group alternatives available (`state_join_peer_not_chosen_does_not_block_arrival`, `state_join_time_segments_expire_together`, `state_join_dead_timer_join_keeps_group_alternative`). A history default route disables the transition only when its source is outside the history owner. Only `errNoWayThrough` disables: cycles, unevaluable guards and binding failures still fail as the transition fires. Dead branches whose continuation has no way through are filtered from a junction draw; one surviving branch is followed without a draw. Choice remains dynamic, and a dead junction reached beyond a choice remains a run error. UML 2.5.1 §14.2.3.8.1 (compound transition) and §14.2.3.7 (junction) are the extension's references. KerML `ControlPerformances::DecisionPerformance` declares `feature outgoingHBLink: HappensBefore[1]`; it does not specify no-way-through or unmatched-event behavior | `state_route.go` `resolveRoute`, `routeAvailable`, `followOut`, `enabledBranches`, `junctionDraw`, `travel`, `travelResolving`, `settleDraws`, `pickBranch`, `errNoWayThrough`; `state_executor.go` `startIn`, `resolveEntryRoute`, `continueEntryRoute`, transition enablement and history-default routing | `state_junction_pseudostate.sysml`, `state_completion_through_pseudostate.sysml`, `state_junction_several_enabled_branches.sysml`, `state_junction_drawn_as_its_transition_fires.sysml`, `state_junction_guards_read_once.sysml`, `state_junction_beyond_a_draw_read_once.sysml`, `state_history_default_through_junction.sysml`, `state_junction_no_way_through_unmatched.sysml`, `state_junction_no_way_through_other_transition_fires.sysml`, `state_junction_no_way_through_deferred.sysml`, `state_junction_dead_branch_not_drawn.sysml`, `state_completion_no_way_through_dropped.sysml`, `state_history_default_no_way_through.sysml`, `state_history_self_transition_default_no_way_through_restores.sysml`, `state_join_no_way_out_disables_last_segment.sysml`, `state_entry_transition_junction_no_way_fails.sysml`, `state_entry_transition_junction_reads_outer_effect.sysml`; `explore_test.go:TestExploreStaticJunctionBranches`, `:TestExploreJunctionDrawnAsTransitionFires`, `:TestExploreHistoryDefaultThroughJunction`; `replay_test.go:TestReplayRefusedChoiceUndoesTheJunctionDrawBeforeIt`, `:TestReplayRefusedChoiceUndoesTheHistoryDefaultsJunctionDraw`; `robustness_junction_exit_route_test.go:TestRuntimeRobustnessJunctionExitRoute`; `robustness_junction_join_test.go:TestRuntimeRobustnessJunctionJoin` (dead junction beyond choice, cycle, unevaluable guard, join exit failure); `robustness_test.go:region_pseudostate_without_satisfied_guard` | ✅ Faithful | -| Fork pseudostates: the branches enter one composite state's orthogonal regions, one target per region, at least two, none guarded or triggered (UML 2.5.1 §14.2.3.5 Pseudostates: the transitions outgoing from a fork "must not have a guard or a trigger"), bypassing the initial state of each region they name — a target may lie below a region's own substates, the region being the one it lies in transitively, and the branch enters every state on the way down; a region the fork leaves out starts at its own initial state, and a region without one may be entered by a fork's branch alone — a machine with another way into the composite state that would start such a region by default (a transition to the state itself, to another of its regions or its history, the machine's entry naming it, or another fork — nested in one of its regions, or above it — passing through) is refused when lowered. Entering through a fork exits the source configuration down to the ancestor the source and the composite share — every region of the composite, in declaration order, when the source lies inside them, the composite itself staying active — then the first branch in region order runs its effect and enters the states still on the way down, and every region enters in declaration order, each branch's effect before its target — a region the fork leaves out never brings the composite in ahead of a branch, however early it is declared — the effects reading and writing the attributes of the state declaring the fork, as a transition leaving one of its substates does; branches ending at `done` complete the composite, or the machine, as an ordinary entry does. A fork's static footprint covers the regions it leaves to start by default, so the checker's reduction keeps their entry behaviors' reads, writes and completion dependent on what other units do | `lower/fork_plan.go` `ForkPlan`, `planFork`, `forkOwner`, `enclosingRegion`, `ForkStarted`, `checkForkOnlyRegion`, `defaultEntryInto`, `defaultStart`, `forkStartsByDefault`; `lower/state_footprint.go` `stateFootprintBuilder.pseudostate` (fork case), `entersRegion`; `lower/state_graph.go` `ownTransitionEffects`; `state_executor.go` `fireForkTransition`, `leaveForFork`, `exitRegionsOf`, `completeIfDone`; `state_region_entry.go` `forkEntry`, `enterForkBranches`, `enterRegion`, `enterLazily` | `lower/fork_plan_test.go` (`TestToStateGraph_ForkEntersRegionsWithoutInitial`, `:TestToStateGraph_ForkBranchEffectsOwnedByDeclaringState`, `:TestToStateGraph_ForkKeepsRegionInitialApart`, `:TestToStateGraph_ForkBranchTargetsNestedState`, `:TestToStateGraph_ForkBranchesNestedInOneRegionFail`, `:TestToStateGraph_RegionWithoutInitialOrForkFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByDefaultFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryFails`, `:TestToStateGraph_ForkOnlyRegionOmittedByAnotherForkFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails`, `:TestToStateGraph_ForkOnlyRegionKeepsExplicitEntries`, `:TestToStateGraph_ForkShapeRejected`), `lower/state_footprint_test.go:TestTransitionFootprintsCoverForkOmittedRegions`, `state_fork_join_pseudostate.sysml` + trace golden, `state_fork_enters_regions_without_initial.sysml`, `state_fork_in_composite_enters_parallel_substate.sysml`, `state_fork_through_inactive_ancestors.sysml`, `state_fork_within_active_ancestor.sysml`, `state_fork_within_active_region.sysml`, `state_fork_completes_owner.sysml`, `state_fork_completes_nested_owner.sysml`, `state_fork_enters_nested_region_states.sysml`, `state_fork_enters_nested_parallel_state.sysml`, `state_fork_omitted_region_declared_first.sysml`, `state_fork_from_within_owner_regions.sysml`, `state_fork_from_within_nested_owner_regions.sysml` (+ trace goldens), `state_fork_branch_effect_owns_state_attribute.sysml`, `fork_join_test.go:TestForkBypassesTargetedRegionInitials`, `robustness_test.go:fork_branches_share_region`, `:fork_leaves_a_region_without_a_way_in`, `:fork_only_region_entered_by_default`, `:fork_branch_with_a_trigger`, `:nested_fork_starts_an_outer_region_by_default` | ✅ Faithful | +| Fork pseudostates: the branches enter one composite state's orthogonal regions, one target per region, at least two, none guarded or triggered (UML 2.5.1 §14.2.3.5 Pseudostates: the transitions outgoing from a fork "must not have a guard or a trigger"), bypassing the initial state of each region they name — a target may lie below a region's own substates, the region being the one it lies in transitively, and the branch enters every state on the way down; a region the fork leaves out starts at its own initial state, and a region without one may be entered by a fork's branch alone — a machine with another way into the composite state that would start such a region by default (a transition to the state itself, to another of its regions or its history, the machine's entry naming it, or another fork — nested in one of its regions, or above it — passing through) may enter the composite with that no-start region inactive; a top-level machine region without a start and a region whose stand-in is parallel are refused when lowered. Entering through a fork exits the source configuration down to the ancestor the source and the composite share — every region of the composite, in declaration order, when the source lies inside them, the composite itself staying active — then the first branch in region order runs its effect and enters the states still on the way down, and every region enters in declaration order, each branch's effect before its target — a region the fork leaves out never brings the composite in ahead of a branch, however early it is declared — the effects reading and writing the attributes of the state declaring the fork, as a transition leaving one of its substates does; branches ending at `done` complete the composite, or the machine, as an ordinary entry does. A fork's static footprint covers the regions it leaves to start by default, so the checker's reduction keeps their entry behaviors' reads, writes and completion dependent on what other units do | `lower/fork_plan.go` `ForkPlan`, `planFork`, `forkOwner`, `enclosingRegion`, `ForkStarted`, `checkForkOnlyRegion`, `defaultEntryInto`, `defaultStart`, `forkStartsByDefault`; `lower/state_footprint.go` `stateFootprintBuilder.pseudostate` (fork case), `entersRegion`; `lower/state_graph.go` `ownTransitionEffects`; `state_executor.go` `fireForkTransition`, `leaveForFork`, `exitRegionsOf`, `completeIfDone`; `state_region_entry.go` `forkEntry`, `enterForkBranches`, `enterRegion`, `enterLazily` | `lower/fork_plan_test.go` (`TestToStateGraph_ForkEntersRegionsWithoutInitial`, `:TestToStateGraph_ForkBranchEffectsOwnedByDeclaringState`, `:TestToStateGraph_ForkKeepsRegionInitialApart`, `:TestToStateGraph_ForkBranchTargetsNestedState`, `:TestToStateGraph_ForkBranchesNestedInOneRegionFail`, `:TestToStateGraph_RegionWithoutInitialOrForkRemainsInactive`, `:TestToStateGraph_ForkOnlyRegionEnteredByDefaultLeavesItInactive`, `:TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryLeavesItInactive`, `:TestToStateGraph_ForkOmissionLeavesRegionInactive`, `:TestToStateGraph_NestedForkLeavesUnenteredRegionsInactive`, `:TestToStateGraph_ForkOnlyRegionKeepsExplicitEntries`, `:TestToStateGraph_ForkShapeRejected`), `lower/state_footprint_test.go:TestTransitionFootprintsCoverForkOmittedRegions`, `state_fork_join_pseudostate.sysml` + trace golden, `state_fork_enters_regions_without_initial.sysml`, `state_fork_in_composite_enters_parallel_substate.sysml`, `state_fork_through_inactive_ancestors.sysml`, `state_fork_within_active_ancestor.sysml`, `state_fork_within_active_region.sysml`, `state_fork_completes_owner.sysml`, `state_fork_completes_nested_owner.sysml`, `state_fork_enters_nested_region_states.sysml`, `state_fork_enters_nested_parallel_state.sysml`, `state_fork_omitted_region_declared_first.sysml`, `state_fork_from_within_owner_regions.sysml`, `state_fork_from_within_nested_owner_regions.sysml` (+ trace goldens), `state_fork_branch_effect_owns_state_attribute.sysml`, `fork_join_test.go:TestForkBypassesTargetedRegionInitials`, `robustness_test.go:fork_branches_share_region`, `:fork_leaves_a_region_without_a_way_in`, `:fork_only_region_entered_by_default`, `:fork_branch_with_a_trigger`, `:nested_fork_starts_an_outer_region_by_default` | ✅ Faithful | +| On default entry, a composite region without a start enters its stand-in without activating a child, or remains inactive when it has no stand-in. Either form is complete by default, so a composite whose regions are all default-complete completes immediately (PSSM *Entering 004*); a region activated later by a transition or fork branch must reach a final state before its owner completes. A stand-in's running do-action defers completion until it ends. | `state_region_entry.go` `enterRegion`, `regionStart`, `enterLazily`; `state_executor.go` `allRegionsDefaultComplete`, `regionComplete`, `stateComplete`, `scheduleCompletionTransitions`, `settleDoActions`; `lower/state_graph.go` `ToStateGraph` | `lower/state_graph_nested_test.go:TestToStateGraph_ParallelWithoutInitialFailsClearly`; `robustness_test.go:parallel_state_region_without_initial`, `:parallel_state_region_itself_parallel`; `robustness_inactive_region_test.go:TestRuntimeRobustnessInactiveRegion`; `robustness_parallel_wrapper_test.go:TestRuntimeRobustnessParallelWrapperLeavesInnerRegionInactive`; `state_parallel_region_without_entry_completes.sysml` + trace golden (PSSM *Entering 004*), `state_parallel_region_without_entry_inactive.sysml` + trace golden, `state_parallel_stateless_region_with_behaviors.sysml`, `state_parallel_stand_in_do_action_defers_completion.sysml` + trace golden, `state_fork_only_region_entered_by_default.sysml` + trace golden, `state_nested_fork_starts_outer_region_inactive.sysml` + trace golden | ✅ Faithful | | Join pseudostates: each occurrence fires every not-yet-arrived segment it enables and whose region's dispatch chose it, exiting each source, running its effect and recording each in `joinArrived` only after the firing succeeds; when multiple segments fire, they are drawn in `join ` order. At selection, including for each time expiry, probes count only the candidate and prior arrivals, checking the way out only if they complete the join. In signal/change dispatches, same-occurrence peers count only when their regions chose them; firing checks completion against those peers, and if the route is dead, none fire. Timer expiries are separate occurrences even at the same instant; a dead completing segment is disabled, leaving its source's other timer-group alternatives available (`state_join_peer_not_chosen_does_not_block_arrival`, `state_join_time_segments_expire_together`, `state_join_dead_timer_join_keeps_group_alternative`). The join completes when prior arrivals plus segments enabled by the current occurrence and chosen by their regions in that dispatch cover every incoming segment and the outgoing route is available. The current segments are drawn as `join `, arrivals clear and the owner is left after the last segment effect. Completion segments fire only on their own completion; waiting regions do not complete their owner; arrivals clear on region re-entry or when another transition leaves the owner. Snapshots, held images and canonical check state preserve arrivals. PSSM leaves the owner before the final incoming effect; leaving after that effect is the extension's reading recorded in SM34. `TransitionPerformances.kerml` says `private succession [1] transitionLinkSource then [*] effect;`; `StatePerformances.kerml` says `and hence happening during the state performance.` Neither supplies a cross-segment arrival rule; that is the UML/PSSM extension reference | `state_route.go` `joinFiringNow`; `state_executor.go` `fireJoinTransition`, `fireJoinSegment`, `fireJoinIncoming`, `leaveJoinOwner`, `regionWaitingAtJoin`, `setRegionState`, `clearJoinArrivalsOwnedBy`; `snapshot.go`, `held_image_behavior.go`, `check_state.go` preserve `joinArrived` | Conformance fixtures: `state_fork_join_pseudostate`, `state_join_arrival_dropped_when_owner_exited`, `state_join_change_segment_rises_alone`, `state_join_change_segment_waits_before_junction`, `state_join_change_segment_waits_before_junction_metadata`, `state_join_change_segments_rise_together`, `state_join_completion_is_not_a_timers_expiry`, `state_join_completion_segment_waits_for_do_behavior`, `state_join_dead_timer_join_keeps_group_alternative`, `state_join_exit_from_regions_before_owner_exit`, `state_join_from_composite_sources`, `state_join_from_nested_states`, `state_join_no_way_out_disables_last_segment`, `state_join_of_machine_regions_from_nested_source`, `state_join_runs_every_incoming_effect`, `state_join_segment_fires_on_own_signal`, `state_join_segments_arrive_together_on_one_occurrence`, `state_join_segment_not_chosen_does_not_arrive`, `state_join_peer_not_chosen_does_not_block_arrival`, `state_join_segment_reads_its_payload`, `state_join_segment_trigger_unmatched`, `state_join_time_segment_needs_same_occurrence`, `state_join_time_segment_not_taken_by_signal`, `state_join_time_segment_unsynchronized_reads_no_route`, `state_join_time_segment_unsynchronized_reads_no_route_metadata`, `state_join_time_segments_expire_apart`, `state_join_time_segments_expire_together`, `state_join_waits_for_every_segment_enabled`; tests: `pseudostate_test.go:TestForkAndJoinPseudostates`, `:TestJoinWaitsForEveryBranch`, `:TestJoinSegmentFiresOnOwnOccurrence`, `:TestJoinDisarmedSegmentCanArriveAlone`, `:TestJoinFromActiveCompositeSourcesFires`, `:TestJoinCompletionSegmentWaitsForItsSourcesDoBehavior`, `:TestJoinCompletionSegmentWaitsForItsSourcesBody`; `fork_join_test.go:TestForkJoinVisitOrderIsDeterministic`; `choice_test.go:TestCompletionTransitionArrivesAtUnreadyJoin`; `state_change_trigger_test.go:TestChangeTriggerDisarmedSegmentCanWaitForItsPeer`; `join_snapshot_test.go:TestSnapshotRestoresJoinArrivals`, `:TestCloneNilJoinArrivalsIsWritable`; `check_test.go:TestCheckJoinWaitsForSlowestBranch`; `replay_test.go:TestReplayRefusedJoinDrawChangesNothing`, `:TestReplayRefusedJoinDrawUndoesTheSegmentsChoice`; `robustness_junction_join_test.go:TestRuntimeRobustnessJunctionJoin` (dead signal and timer join exits, exit failure) | ✅ Faithful | | A compound transition through a pseudostate declared inside a composite state exits and runs its effects segment by segment (UML 2.5.1 §14.2.3.8.4, the segments' behaviors "executed in sequence", each after the exits of the states its segment leaves; PSSM §8.5 on exit points): a transition `first Inner accept Sig do { A } then Owner::x;` into a junction `x` of `Owner`, continued by `first Owner::x do { B } then Out;`, exits `Inner`, runs `A`, exits `Owner`, runs `B`, then enters `Out` — not every exit first. The segments' effects are grouped by segment (`routeEffect.segment`); a segment leaving from a pseudostate of a state exits that state's descendants the move leaves and the states up to that segment's boundary before its effects, and a segment from the machine's body exits what the move has still to leave. Into a join of `Owner` from its orthogonal regions, each region's transition exits its source and runs its effect (the regions in an open order), then `Owner` exits, then the join's outgoing transition runs. A route that ends at a terminate action, or at a history pseudostate, keeps its effects in that order. This is the runtime form the SysML v1 migrator writes a composite state's exit point in (`docs/reference/sysml-v1-migration.md`, *Pseudostate exitPoint on a composite State*); a transition into a junction of a composite state from outside runs the state's entry behavior before the junction's outgoing transition and the target's entries, which the migrator's entry point form relies on | `state_route.go` travelResolving, leaveAlong, leftBySegment, segmentBoundary, vertexState; `state_executor.go` terminateAlong, terminateAt, moveToHistory (effects passed through) | `state_junction_exit_effect_before_owner_exit.sysml` + `.trace.golden`, `state_join_exit_from_regions_before_owner_exit.sysml` + `.trace.golden` (`admissible` orders), `state_junction_entry_skips_default_initial.sysml` + `.trace.golden` (an entry through a junction runs the state's entry behavior, skips the region's `entry; then`, and history restores the substate); `robustness_junction_exit_route_test.go:TestRuntimeRobustnessJunctionExitRoute` (a junction left through with no outgoing transition, one whose outgoing transition ends at a shallow history, one whose every guard is false: typed errors naming the junction); `tests/migrate/states_test.go:TestCompositeStateConnectionPointsKeepTheUMLOrder` (the migrated `station_points.xmi` run end to end) | ✅ Faithful | | A segment of a compound transition that continues inside a region of the parallel state the transition enters runs its effect on that region's entry front: after the parallel state's entry, before its own target's entry, and interleaved with the sibling regions' entry units, since nothing in `StatePerformances.kerml` orders one region's `middle` performances against another's ([the derivation](behavior-semantic-oracle.md#effects-on-the-way-into-the-regions-of-a-parallel-state-each-precedes-its-own-targets-entry-the-regions-interleave)); a segment before the parallel state is entered runs where it did. Availability checks only the incoming transition's own route (SM32). A nested default entry is a separate transition out of the composite's `entry` action: its junction is read when that transition is taken, after the outer effect and composite entry; a dead route fails with a typed run error naming the junction (SM46). For a draw, nested default-entry targets are alternatives: any available target suffices, while every region of a composite target must have an available default entry; `state_entry_route_dead_alternative.sysml` covers a viable alternative beside a dead nested route. The conformance cases `state_entry_transition_junction_no_way_fails.sysml` and `state_entry_transition_junction_reads_outer_effect.sysml` cover the timing and failure | `runtime/state_route.go` `routeAvailable`, `resolveRoute`, `followOut`; `runtime/state_executor.go` `startIn`, `resolveEntryRoute`, `continueEntryRoute`; `runtime/state_region_entry.go` `enterRegionsInto`, `enterRegion` (`runEntryRouteEffects`); `runtime/state_run_to_completion.go` `holdEntry`/`performHeld` (a held cascade keeps its region's pending effects) | conformance `state_junction_inside_orthogonal_region.sysml` (explored: three orders; + trace, seed, declared and check goldens), `state_entry_transition_junction_no_way_fails.sysml`, `state_entry_transition_junction_reads_outer_effect.sysml`, `state_entry_route_dead_alternative.sysml` | ✅ Faithful | -| History pseudostates (shallow and deep). A region is recorded per region, in the state it was left in, so a region left by a transition that started inside its own composite state's region is restored to that composite state and its inner configuration rather than to the region's initial state; a region left with no active state at all has nothing to restore; a region or body left at `done` completed and leaves no history. A shallow or deep history with nothing recorded takes its own outgoing transition when it has one and otherwise performs the owning state's default entry, through its `entry` transition, as a plain transition into the composite would; an owner with no entry transition either is the typed `ErrHistoryWithoutEntry` naming the history and its owner | `parser/behavior.go` parseStateMember (`history ;`, `shallow history ;`, `deep history ;`) and `lower/state_metadata.go` (`#shallowHistory state`/`#deepHistory state`); `state_executor.go` fireHistoryTransition, `:historyEntry`, `:historyRecorded` (an empty history, read by `state_route.go` `resolveRoute` too when settling the default transition), `:hasDefaultEntry`, `:deepestRecorded`, `exitState` (records the configuration left), `:recordChildHistory`, `:recordRegionHistory`, `:forgetRegionHistory`, `errors.go` `ErrHistoryWithoutEntry`, `state_region_transition.go` `leaveRegion`, `exitRegionTo`, `lower/state_graph.go` PseudostateOwner | `tests/parser/testdata/parse/state_history.golden`, `parser/state_notation_test.go:TestHistoryPseudostateParsing`, `lower/state_notation_test.go:TestToStateGraph_HistoryNotation`, `state_shallow_history.sysml`, `state_deep_history.sysml`, `state_history_revisit.sysml` + trace golden, `state_deep_history_region_composite.sysml`, `history_test.go:TestShallowHistoryRestoresLastSubstate`, `:TestDeepHistoryRestoresInnermostSubstate`, `:TestHistoryRestoresOrthogonalRegions`, `:TestDeepHistoryRestoresBelowRegion`, `:TestDeepHistoryRestoresARegionLeftFromInsideItsCompositeState`, `:TestHistoryTakesDefaultTransitionWhenUnvisited`, `:TestHistoryOverACompletedConfigurationIsADefaultEntry`, `state_history_empty_default_entry`, `state_deep_history_empty_default_entry`, `state_history_after_completion_default_entry`, `state_history_after_completion_default_transition` (+ trace goldens), `robustness_test.go:history_outside_composite_state`, `:history_without_record_default_or_entry` | ✅ Faithful | +| History pseudostates (shallow and deep). A region is recorded per region, in the state it was left in, so a region left by a transition that started inside its own composite state's region is restored to that composite state and its inner configuration rather than to the region's initial state; a region left with no active state at all has nothing to restore; a region or body left at `done` completed and leaves no history. A shallow or deep history with nothing recorded takes its own outgoing transition when it has one and otherwise performs the owning state's default entry, through its `entry` transition or the same region defaults as a plain transition into the composite would; an owner with no entry transition either is the typed `ErrHistoryWithoutEntry` naming the history and its owner | `parser/behavior.go` parseStateMember (`history ;`, `shallow history ;`, `deep history ;`) and `lower/state_metadata.go` (`#shallowHistory state`/`#deepHistory state`); `state_executor.go` fireHistoryTransition, `:historyEntry`, `:historyRecorded` (an empty history, read by `state_route.go` `resolveRoute` too when settling the default transition), `:hasDefaultEntry`, `:deepestRecorded`, `exitState` (records the configuration left), `:recordChildHistory`, `:recordRegionHistory`, `:forgetRegionHistory`, `errors.go` `ErrHistoryWithoutEntry`, `state_region_transition.go` `leaveRegion`, `exitRegionTo`, `lower/state_graph.go` PseudostateOwner | `tests/parser/testdata/parse/state_history.golden`, `parser/state_notation_test.go:TestHistoryPseudostateParsing`, `lower/state_notation_test.go:TestToStateGraph_HistoryNotation`, `state_shallow_history.sysml`, `state_deep_history.sysml`, `state_history_revisit.sysml` + trace golden, `state_deep_history_region_composite.sysml`, `history_test.go:TestShallowHistoryRestoresLastSubstate`, `:TestDeepHistoryRestoresInnermostSubstate`, `:TestHistoryRestoresOrthogonalRegions`, `:TestDeepHistoryRestoresBelowRegion`, `:TestDeepHistoryRestoresARegionLeftFromInsideItsCompositeState`, `:TestHistoryTakesDefaultTransitionWhenUnvisited`, `:TestHistoryOverACompletedConfigurationIsADefaultEntry`, `state_history_empty_default_entry`, `state_deep_history_empty_default_entry`, `state_history_after_completion_default_entry`, `state_history_after_completion_default_transition` (+ trace goldens), `robustness_test.go:history_outside_composite_state`, `:history_without_record_default_or_entry`, `state_history_first_entry_inactive_region.sysml` + trace golden, `state_history_restores_inactive_parallel_region.sysml` + trace golden| ✅ Faithful | | Composite state with regions entered by a plain transition | `state_executor.go` transitionToInto (keeps the region configuration entering it just built) | `history_test.go:TestHistoryRestoresOrthogonalRegions` | ✅ Faithful | | Leaving a composite state exits only its own regions | `state_executor.go` exitState (scoped to `CompositeStates[state]`) | `history_test.go:TestExitingNestedRegionsKeepsSiblingRegions` | ✅ Faithful | | A transition between two regions of one composite state exits its source only: KerML `StatePerformances::StateTransitionPerformance` orders `private succession [*] guard then [1] transitionLinkSource.exit`, so the composite state is neither exited nor re-entered, the source's region is left without an active state, and the regions holding neither endpoint keep theirs. A target nested inside the target region's own composite state moves that inner region, exiting the state it was running. A region whose target is nested inside a composite state it is not running records that composite state as its active one. A source active in a region nested deeper than its target's region leaves its region set up to the level the two share, exiting the composite state holding its own region. The level is found by walking outward through the region declaring the region owner's nearest region-declared ancestor, so a region owned by a plain substate is not missed. A target outside the composite state still exits it and its regions — each state on the way out exactly once, the region a state is active in being cleared before that state is exited, since a region's active state may be nested below it — and a nested non-orthogonal transition still exits up to the endpoints' least common ancestor | `state_region_transition.go` `fireTransitionInRegion`, `concurrentRegionsFor`, `enclosingRegion`, `siblingRegionContaining`, `moveBetweenRegions`, `exitRegionTo`, `leaveRegion`, `isBelowOrEqual`; `state_executor.go` `getLCA` (nested and outward transitions only) | `state_transition_cross_region.sysml` + trace golden, `state_transition_cross_region_third_region.sysml`, `state_transition_cross_region_nested_target.sysml`, `state_transition_cross_region_inactive_wrapper.sysml`, `state_transition_cross_region_deep_source.sysml`, `state_transition_cross_region_substate_owner.sysml`, `state_transition_leave_composite_substate_region.sysml`, `state_transition_sibling_region.sysml`, `state_region_cross_pseudostate.sysml` + trace golden, `cross_region_transition_test.go:TestCrossRegionTransitionExitsSourceOnly`, `:TestCrossRegionTransitionIntoNestedTargetExitsTheAbandonedState`, `:TestCrossRegionTransitionIntoInactiveCompositeRecordsTheEnteredState`, `:TestCrossRegionTransitionFromDeeperRegionExitsUpToTheSharedLevel`, `:TestCrossRegionTransitionFromARegionOwnedByASubstate`, `:TestNestedTransitionExitsUpToTheLCA`, `:TestTransitionOutOfCompositeStateExitsEveryRegion`, `:TestTransitionOutOfCompositeStateExitsNestedStatesOnce`, `region_pseudostate_test.go:TestRegionPseudostateIntoSiblingRegionExitsSourceOnly`, `robustness_test.go:state_cross_region_transitions_ping_pong` | ✅ Faithful | diff --git a/docs/project/sysml-v1-transformation-census-baseline.json b/docs/project/sysml-v1-transformation-census-baseline.json index 357162222b..2750f02c0e 100644 --- a/docs/project/sysml-v1-transformation-census-baseline.json +++ b/docs/project/sysml-v1-transformation-census-baseline.json @@ -22,7 +22,7 @@ }, { "name": "fixtures", - "files": 104 + "files": 105 } ], "counts": { @@ -140,7 +140,7 @@ }, "uml:Class": { "pssm": 311, - "fixtures": 500 + "fixtures": 501 }, "uml:ClearAssociationAction": { "pssm": 0, @@ -256,7 +256,7 @@ }, "uml:Pseudostate": { "pssm": 289, - "fixtures": 71 + "fixtures": 74 }, "uml:RaiseExceptionAction": { "pssm": 0, @@ -304,7 +304,7 @@ }, "uml:Region": { "pssm": 248, - "fixtures": 54 + "fixtures": 56 }, "uml:RemoveStructuralFeatureValueAction": { "pssm": 0, @@ -6733,15 +6733,17 @@ "ownedRelationship" ], "ocl": "sha256:3f4ba616a89403b7434108f7d6e931f2731f96306a5f6a0966acaa645618a884", - "status": "approximate", + "status": "faithful", "implementation": [ - "internal/translate/migrate/activity.go:opaqueAction" + "internal/translate/migrate/activity.go:opaqueAction", + "internal/translate/migrate/behavior.go:textualReps", + "internal/translate/migrate/values.go:opaqueBodyPairs" ], "tests": [ - "tests/migrate/stub_actions_test.go:TestStubActionsInMethodsKeepTheirPinsAndAllocation", + "tests/migrate/opaque_textual_rep_test.go:TestOpaqueActionTextualRepresentations", "tests/migrate/opaque_migration_test.go:TestTranslatedOutputPinsFeedTheirFlows" ], - "reason": "the body is translated into action statements when its language is read; otherwise the action is a stub keeping its pins (fixtures: 78 of 109 approximated)", + "reason": "translated bodies retain executable action statements and each nonempty body/language pair is also written as a textual representation; a body without a language is kept as a comment", "scope": [ "uml:OpaqueAction" ] @@ -14969,15 +14971,19 @@ "ownedMemberParameter" ], "ocl": "sha256:b599b3105bd411023d03ef6f65ce83d30b10fbe15d78fd0a6e0f77bdaeb7e2e8", - "status": "approximate", + "status": "deliberate", "implementation": [ "internal/translate/migrate/behavior.go:event", - "internal/translate/migrate/states.go:triggerAccept" + "internal/translate/migrate/states.go:triggerAccept", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates" ], - "reason": "an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8)", + "reason": "time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped", "scope": [ "uml:TimeEvent", "uml:ChangeEvent", @@ -14999,15 +15005,19 @@ "direction" ], "ocl": "sha256:5fffed7c1a7497a40c4a426d15855b961fb30ed60601f01f05f85e95503d5b7e", - "status": "approximate", + "status": "deliberate", "implementation": [ "internal/translate/migrate/behavior.go:event", - "internal/translate/migrate/states.go:triggerAccept" + "internal/translate/migrate/states.go:triggerAccept", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates" ], - "reason": "an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8)", + "reason": "time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped", "scope": [ "uml:TimeEvent", "uml:ChangeEvent", @@ -15029,15 +15039,19 @@ "ownedRelationship" ], "ocl": "sha256:991063d5d273e68b510443110b1363d49b80f06a2e1e35553b4576b356931519", - "status": "approximate", + "status": "deliberate", "implementation": [ "internal/translate/migrate/behavior.go:event", - "internal/translate/migrate/states.go:triggerAccept" + "internal/translate/migrate/states.go:triggerAccept", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates" ], - "reason": "an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8)", + "reason": "time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped", "scope": [ "uml:TimeEvent", "uml:ChangeEvent", @@ -15661,16 +15675,17 @@ "ownedMemberElement" ], "ocl": "sha256:f16e0694037266ec5c66a075f58da7f8afe0456eef0e783bbd2230a936c80042", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/behavior.go:opaqueBehaviorBody", - "internal/translate/migrate/behavior.go:calcBody" + "internal/translate/migrate/behavior.go:calcBody", + "internal/translate/migrate/behavior.go:textualReps", + "internal/translate/migrate/values.go:opaqueBodyPairs" ], "tests": [ - "tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations" ], - "reason": "an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated)", + "reason": "action and calc definitions retain translated executable statements and each nonempty body/language pair is written as a textual representation; a body without a language is kept as a comment", "scope": [ "uml:OpaqueBehavior" ] @@ -15691,16 +15706,17 @@ "language" ], "ocl": "sha256:e32dc2615a865f5c3529771060cf49d5ab93c3febd301780eeba603df6a268ad", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/behavior.go:opaqueBehaviorBody", - "internal/translate/migrate/behavior.go:calcBody" + "internal/translate/migrate/behavior.go:calcBody", + "internal/translate/migrate/behavior.go:textualReps", + "internal/translate/migrate/values.go:opaqueBodyPairs" ], "tests": [ - "tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations" ], - "reason": "an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated)", + "reason": "candidate erratum: the mapping pairs body i with language i, so a body with no language is dropped; it is kept as a comment. Each nonempty pair is written as a textual representation, while translated bodies remain executable", "scope": [ "uml:OpaqueBehavior" ] @@ -15720,16 +15736,17 @@ "ownedRelationship" ], "ocl": "sha256:5b8bc31f50e3ce6c8471a11457f4bdb438f88a735e94fcb4fb26714b74ab952c", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/behavior.go:opaqueBehaviorBody", - "internal/translate/migrate/behavior.go:calcBody" + "internal/translate/migrate/behavior.go:calcBody", + "internal/translate/migrate/behavior.go:textualReps", + "internal/translate/migrate/values.go:opaqueBodyPairs" ], "tests": [ - "tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations" ], - "reason": "an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated)", + "reason": "action and calc definitions retain translated executable statements and each nonempty body/language pair is written as a textual representation; a body without a language is kept as a comment", "scope": [ "uml:OpaqueBehavior" ] @@ -15800,15 +15817,19 @@ "ownedRelationship" ], "ocl": "sha256:2c49efe7744ba67b0a3b6c556c41408b6b79a3045399dfaa7f976d5a64ed7d0d", - "status": "approximate", + "status": "deliberate", "implementation": [ "internal/translate/migrate/behavior.go:event", - "internal/translate/migrate/states.go:triggerAccept" + "internal/translate/migrate/states.go:triggerAccept", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates" ], - "reason": "an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8)", + "reason": "time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped", "scope": [ "uml:TimeEvent", "uml:ChangeEvent", @@ -15830,16 +15851,18 @@ "ownedRelationship" ], "ocl": "sha256:ed59c3e997e910a41221d2ae7ad509599208e3269cf55778fd646296a20c3adc", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -15860,16 +15883,18 @@ "ownedRelationship" ], "ocl": "sha256:d717104fa2d018b0e7149d820c525abdbfc8d533e44bcdf0a50abb4d9520d187", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -15889,16 +15914,18 @@ "ownedMemberFeature" ], "ocl": "sha256:56c5e4333e81634810b0cb75449136029bb93769d1afd78bd0f00930a8200679", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -15918,16 +15945,18 @@ "chainingFeature" ], "ocl": "sha256:cea9c905816edf7ec2fa68e9b7f10d39156b10b681dfd9ed33d92c8963155211", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -15947,16 +15976,18 @@ "chainingFeature" ], "ocl": "sha256:1f201e33bb28ac3c3e474697a118d1d00e092f4dad268b83dc683568557d2795", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -15976,16 +16007,18 @@ "type" ], "ocl": "sha256:65cc81047f3797e5ca3fc0444e0cd064d1d7235ccd236ee77d95751c2b520668", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16005,16 +16038,18 @@ "value" ], "ocl": "sha256:ee431ee6fb4a4779777953496d73432438011b0eedb9c84c1c5b2d3a4b01a8d9", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16034,16 +16069,18 @@ "ownedRelationship" ], "ocl": "sha256:1cb76b92a56e4f8a0b138ddbbd0642e3ab6983c638c40f2a86559d21126d5d10", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16063,16 +16100,18 @@ "ownedRelationship" ], "ocl": "sha256:e0a0d993afdc09f0e438f8d2ea07d0c67458e4df37ee1e98188d447319d267f3", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16092,16 +16131,18 @@ "ownedMemberParameter" ], "ocl": "sha256:0c6e80744f1ca172c940f22b094e2364bc6d69582a3b9a15144ce5745c65b318", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16121,16 +16162,18 @@ "ownedMemberFeature" ], "ocl": "sha256:c7df47fe2a9fa31107d518e24f01829668c676e6665e1da8b5ce0e6d06011984", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16150,16 +16193,18 @@ "type" ], "ocl": "sha256:99f71aa12ed3dcdd79e31f0037f3245c739c6fe39fa433b88ff6e1399bef9af2", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16179,16 +16224,18 @@ "value" ], "ocl": "sha256:e3cb5fd1edea70fa17ec22c62fc20a65c3771e9518391c0e70e197fe5dd315c3", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16208,16 +16255,18 @@ "ownedRelationship" ], "ocl": "sha256:9db5c3e4b5135c8927c6b963e3afd27d821f72ee478bbabec299d65383e0c0ad", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16238,16 +16287,18 @@ "ownedRelationship" ], "ocl": "sha256:a17957f49053c192a111d4e6e4f61ad414db26e0d3a93c03bfb3f6c04376aee9", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16268,16 +16319,18 @@ "ownedRelatedElement" ], "ocl": "sha256:270fcbe6db145562d533b5be7d37ffc91751ff4cda63310d7cfeb2db2d600aef", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16298,16 +16351,18 @@ "direction" ], "ocl": "sha256:572fc9fa7d11d4c2fe67b9718d79459b8008f7facd82aa998179820d92cf4c6e", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16327,16 +16382,18 @@ "ownedMemberFeature" ], "ocl": "sha256:ef786324ce5947676f2470d703d799b0a8a77a2ecd31995da4eceef5dd030aea", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16356,16 +16413,18 @@ "ownedMemberParameter" ], "ocl": "sha256:dbdb6b7988362cf2e92b97f5a1db7d08b773bf55c27b2277cc5f450b65473841", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16385,16 +16444,18 @@ "ownedRelationship" ], "ocl": "sha256:53783e2be10546a837ddfa5615b62a941a65bda451b78c275a92019aabf3387d", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16414,16 +16475,18 @@ "referencedFeature" ], "ocl": "sha256:cbad9ec43d951dfc41eed13556de29f62bd195afc1763a6e3a2c8b03e3252f61", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -16444,16 +16507,18 @@ "ownedRelationship" ], "ocl": "sha256:cc4cf1080da81b630e3530b262eb0bc299aba09d95694f62d2d7c3dae91f8e70", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:triggerAccept", - "internal/translate/migrate/values.go:valueExpr" + "internal/translate/migrate/values.go:valueExpr", + "internal/translate/migrate/events.go:instantExpr" ], "tests": [ - "tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature", + "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", + "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards" ], - "reason": "a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events)", + "reason": "the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value", "scope": [ "uml:TimeEvent" ] @@ -19259,16 +19324,19 @@ "ownedRelationship" ], "ocl": "sha256:d0135ff9e57bd8d4894be3bbaaf71452667f8b6c799b6e5a6c7dfb8d98384598", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:pseudostateLine", - "internal/translate/migrate/states.go:vertex" + "internal/translate/migrate/states.go:vertex", + "internal/translate/migrate/states.go:vertexBase", + "internal/translate/migrate/states.go:target" ], "tests": [ "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/terminate_test.go:TestTerminatePseudostateEndsOrthogonalMachine", + "tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion" ], - "reason": "choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5)", + "reason": "candidate erratum: the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case)", "scope": [ "uml:Pseudostate" ] @@ -19468,16 +19536,19 @@ "filter" ], "ocl": "sha256:abd3cb2bfdfe4cd3c1b36d93405651adfe4ca80da714267f65411cc45e153481", - "status": "approximate", + "status": "faithful", "implementation": [ "internal/translate/migrate/states.go:pseudostateLine", - "internal/translate/migrate/states.go:vertex" + "internal/translate/migrate/states.go:vertex", + "internal/translate/migrate/states.go:vertexBase", + "internal/translate/migrate/states.go:target" ], "tests": [ "tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates", - "tests/migrate/migrate_test.go:TestGoldenNotation" + "tests/migrate/terminate_test.go:TestTerminatePseudostateEndsOrthogonalMachine", + "tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion" ], - "reason": "choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5)", + "reason": "candidate erratum: the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case)", "scope": [ "uml:Pseudostate" ] @@ -19497,15 +19568,24 @@ "ownedRelationship" ], "ocl": "sha256:b442f34f152532b9b29b28f5b1ecc2e0886e1f0a17f1377b578c52ceef9350af", - "status": "approximate", + "status": "faithful", "implementation": [ - "internal/translate/migrate/states.go:region" + "internal/translate/migrate/states.go:state", + "internal/translate/migrate/states.go:nameRegions", + "internal/translate/migrate/states.go:regions", + "internal/translate/migrate/states.go:regionWrittenAsState", + "internal/translate/migrate/states.go:region", + "internal/translate/migrate/names.go:path", + "internal/translate/migrate/edges.go:edgePlaceIn" ], "tests": [ + "tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateIsWrapped", + "tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion", + "tests/migrate/region_wrapper_test.go:TestStateMachineSingleRegionRemainsFlattened", "tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards", "tests/migrate/empty_region_test.go:TestEmptyRegionsAreNotWritten" ], - "reason": "the region of a single-region state is flattened into its state rather than written as a state usage; orthogonal regions become the substates of a parallel state; empty regions are not written", + "reason": "candidate erratum: for a state machine's only Region the mapping writes a StateUsage in a state definition that is not parallel (`isParallel = region->size() > 1`) and nothing enters it, so it is never entered; its vertices become the state definition's own body. A StateMachine's single Region remains flattened; a composite State's single Region is written as a sub-state of its `parallel` state; orthogonal Regions are parallel sub-states; empty Regions are omitted because an empty parallel sub-state never completes", "scope": [ "uml:Region" ] diff --git a/docs/project/sysml-v1-transformation-census.md b/docs/project/sysml-v1-transformation-census.md index 9673437d38..db67b483bd 100644 --- a/docs/project/sysml-v1-transformation-census.md +++ b/docs/project/sysml-v1-transformation-census.md @@ -34,7 +34,7 @@ that build one relationship of a target — share the verdict of the construct t -**Census:** 783 mapping classes — 510 ✅ faithful, 55 ⚠️ approximate, 42 ❌ not implemented, 176 ⛔ deliberate, 0 🚧 known failure, 0 ❔ unknown. +**Census:** 783 mapping classes — 539 ✅ faithful, 22 ⚠️ approximate, 42 ❌ not implemented, 180 ⛔ deliberate, 0 🚧 known failure, 0 ❔ unknown. | Package | Mappings | ✅ | ⚠️ | ❌ | ⛔ | 🚧 | ❔ | |---|---|---|---|---|---|---|---| @@ -47,7 +47,7 @@ that build one relationship of a target — share the verdict of the construct t | SysMLv1::Ports&Flows | 20 | 9 | 0 | 0 | 11 | 0 | 0 | | SysMLv1::Requirements | 49 | 34 | 10 | 0 | 5 | 0 | 0 | | UML4SysML::Actions::AcceptEventActions | 25 | 24 | 0 | 0 | 1 | 0 | 0 | -| UML4SysML::Actions::Actions | 8 | 7 | 1 | 0 | 0 | 0 | 0 | +| UML4SysML::Actions::Actions | 8 | 8 | 0 | 0 | 0 | 0 | 0 | | UML4SysML::Actions::InvocationActions | 40 | 38 | 0 | 0 | 2 | 0 | 0 | | UML4SysML::Actions::LinkActions | 7 | 7 | 0 | 0 | 0 | 0 | 0 | | UML4SysML::Actions::ObjectActions | 42 | 17 | 0 | 19 | 6 | 0 | 0 | @@ -57,17 +57,17 @@ that build one relationship of a target — share the verdict of the construct t | UML4SysML::Actions::VariableActions | 31 | 26 | 0 | 0 | 5 | 0 | 0 | | UML4SysML::Activities | 68 | 60 | 0 | 0 | 8 | 0 | 0 | | UML4SysML::Classification | 48 | 42 | 1 | 0 | 5 | 0 | 0 | -| UML4SysML::CommonBehavior | 57 | 28 | 29 | 0 | 0 | 0 | 0 | +| UML4SysML::CommonBehavior | 57 | 53 | 0 | 0 | 4 | 0 | 0 | | UML4SysML::CommonStructure | 18 | 16 | 0 | 0 | 2 | 0 | 0 | | UML4SysML::InformationFlows | 9 | 8 | 0 | 0 | 1 | 0 | 0 | | UML4SysML::Interactions | 19 | 6 | 0 | 0 | 13 | 0 | 0 | | UML4SysML::Packages | 34 | 8 | 0 | 0 | 26 | 0 | 0 | | UML4SysML::SimpleClassifiers | 22 | 20 | 0 | 0 | 2 | 0 | 0 | -| UML4SysML::StateMachines | 26 | 22 | 4 | 0 | 0 | 0 | 0 | +| UML4SysML::StateMachines | 26 | 25 | 1 | 0 | 0 | 0 | 0 | | UML4SysML::StructuredClassifiers | 39 | 23 | 8 | 0 | 8 | 0 | 0 | | UML4SysML::UseCases | 14 | 8 | 1 | 4 | 1 | 0 | 0 | | UML4SysML::Values | 33 | 14 | 0 | 3 | 16 | 0 | 0 | -| **Total** | 783 | 510 | 55 | 42 | 176 | 0 | 0 | +| **Total** | 783 | 539 | 22 | 42 | 180 | 0 | 0 | ## Census rows @@ -354,7 +354,7 @@ that build one relationship of a target — share the verdict of the construct t | `CommonAction_Mapping` (abstract) | `Action` | `ActionUsage` | `internal/translate/migrate/activity.go:declareKind` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `OABodyMembership_Mapping` | `OpaqueAction` | `OwningMembership` | `internal/translate/migrate/activity.go:declareKind` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `OABody_Mapping` | `OpaqueAction` | `TextualRepresentation` | `internal/translate/migrate/activity.go:declareKind` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | -| `OpaqueAction_Mapping` | `OpaqueAction` | `ActionUsage` | `internal/translate/migrate/activity.go:opaqueAction` | `tests/migrate/stub_actions_test.go:TestStubActionsInMethodsKeepTheirPinsAndAllocation`
`tests/migrate/opaque_migration_test.go:TestTranslatedOutputPinsFeedTheirFlows` | ⚠️ approximate | the body is translated into action statements when its language is read; otherwise the action is a stub keeping its pins (fixtures: 78 of 109 approximated) | +| `OpaqueAction_Mapping` | `OpaqueAction` | `ActionUsage` | `internal/translate/migrate/activity.go:opaqueAction`
`internal/translate/migrate/behavior.go:textualReps`
`internal/translate/migrate/values.go:opaqueBodyPairs` | `tests/migrate/opaque_textual_rep_test.go:TestOpaqueActionTextualRepresentations`
`tests/migrate/opaque_migration_test.go:TestTranslatedOutputPinsFeedTheirFlows` | ✅ faithful | translated bodies retain executable action statements and each nonempty body/language pair is also written as a textual representation; a body without a language is kept as a comment | | `Pin_Mapping` | `Pin` | `ReferenceUsage` | `internal/translate/migrate/activity.go:declarePins` | `tests/migrate/arguments_test.go:TestAllocatedCallsWithoutBehaviorDeclareTheirPins`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ValuePinFeatureValue_Mapping` | `ValuePin` | `FeatureValue` | `internal/translate/migrate/activity.go:declarePins`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ValuePinUntyped_Mapping` | `ValuePin` | `ReferenceUsage` | `internal/translate/migrate/activity.go:declarePins`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | @@ -685,9 +685,9 @@ that build one relationship of a target — share the verdict of the construct t | OMG mapping | v1 source | v2 target | Implementation | Test | Status | Reason | |---|---|---|---|---|---|---| | `Behavior_Mapping` (abstract) | `Behavior` | `Behavior` | `internal/translate/migrate/behavior.go:behaviorBody`
`internal/translate/migrate/behavior.go:classifierBehavior` | `tests/migrate/behavior_more_test.go:TestClassifierBehaviorContextFollowsInputParameter`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | -| `ChangeEventReturnParameterMembership_Mapping` | `ChangeEvent` | `ReturnParameterMembership` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) | -| `ChangeEventReturnParameter_Mapping` | `ChangeEvent` | `ReferenceUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) | -| `ChangeEvent_Mapping` | `ChangeEvent` | `CalculationUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) | +| `ChangeEventReturnParameterMembership_Mapping` | `ChangeEvent` | `ReturnParameterMembership` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates` | ⛔ deliberate | time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped | +| `ChangeEventReturnParameter_Mapping` | `ChangeEvent` | `ReferenceUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates` | ⛔ deliberate | time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped | +| `ChangeEvent_Mapping` | `ChangeEvent` | `CalculationUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates` | ⛔ deliberate | time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped | | `ChangeTriggerBindingConnector_Mapping` | `Trigger` | `BindingConnectorAsUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ChangeTriggerConstraintUsage_Mapping` | `Trigger` | `ConstraintUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ChangeTriggerEndFeatureMembership_Mapping` | `Trigger` | `EndFeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | @@ -711,34 +711,34 @@ that build one relationship of a target — share the verdict of the construct t | `ChangeTriggerReturnParameter_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ChangeTriggerReturnReferenceSubsetting_Mapping` | `Trigger` | `ReferenceSubsetting` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `ChangeTriggerReturnReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | -| `OpaqueBehaviorMembership_Mapping` | `OpaqueBehavior` | `OwningMembership` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody` | `tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) | -| `OpaqueBehaviorSpecification_Mapping` | `OpaqueBehavior` | `TextualRepresentation` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody` | `tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) | -| `OpaqueBehavior_Mapping` | `OpaqueBehavior` | `ActionDefinition` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody` | `tests/migrate/opaque_migration_test.go:TestNonScalarFeaturesAndScriptLiterals`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) | +| `OpaqueBehaviorMembership_Mapping` | `OpaqueBehavior` | `OwningMembership` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody`
`internal/translate/migrate/behavior.go:textualReps`
`internal/translate/migrate/values.go:opaqueBodyPairs` | `tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations` | ✅ faithful | action and calc definitions retain translated executable statements and each nonempty body/language pair is written as a textual representation; a body without a language is kept as a comment | +| `OpaqueBehaviorSpecification_Mapping` | `OpaqueBehavior` | `TextualRepresentation` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody`
`internal/translate/migrate/behavior.go:textualReps`
`internal/translate/migrate/values.go:opaqueBodyPairs` | `tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations` | ✅ faithful | candidate erratum: the mapping pairs body i with language i, so a body with no language is dropped; it is kept as a comment. Each nonempty pair is written as a textual representation, while translated bodies remain executable | +| `OpaqueBehavior_Mapping` | `OpaqueBehavior` | `ActionDefinition` | `internal/translate/migrate/behavior.go:opaqueBehaviorBody`
`internal/translate/migrate/behavior.go:calcBody`
`internal/translate/migrate/behavior.go:textualReps`
`internal/translate/migrate/values.go:opaqueBodyPairs` | `tests/migrate/opaque_textual_rep_test.go:TestOpaqueBehaviorTextualRepresentations` | ✅ faithful | action and calc definitions retain translated executable statements and each nonempty body/language pair is written as a textual representation; a body without a language is kept as a comment | | `SignalTriggerReferenceUsageFeatureTyping_Mapping` | `Trigger` | `FeatureTyping` | `internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `SignalTriggerReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | -| `TimeEvent_Mapping` | `TimeEvent` | `CalculationUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) | -| `TimeTriggerBindingConnector_Mapping` | `Trigger` | `BindingConnectorAsUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerCalculationUsage_Mapping` | `Trigger` | `CalculationUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerEndFeatureMembership_Mapping` | `Trigger` | `EndFeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerEventChainingFeature_Mapping` | `Trigger` | `FeatureChaining` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerEventReturnParameterChainingFeature_Mapping` | `Trigger` | `FeatureChaining` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerExpressionFeatureTyping_Mapping` | `Trigger` | `FeatureTyping` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerExpressionFeatureValue_Mapping` | `Trigger` | `FeatureValue` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerExpressionFeature_Mapping` | `Trigger` | `Feature` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerExpressionInvocationExpression_Mapping` | `Trigger` | `InvocationExpression` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerExpressionParameterMembership_Mapping` | `Trigger` | `ParameterMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerFeatureMembership_Mapping` | `Trigger` | `FeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerFeatureTyping_Mapping` | `Trigger` | `FeatureTyping` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerFeatureValue_Mapping` | `Trigger` | `FeatureValue` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerFeature_Mapping` | `Trigger` | `Feature` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerInvocationExpression_Mapping` | `Trigger` | `TriggerInvocationExpression` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReferenceSubsetting_Mapping` | `Trigger` | `ReferenceSubsetting` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReturnEndFeatureMembership_Mapping` | `Trigger` | `EndFeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReturnParameterMembership_Mapping` | `Trigger` | `ReturnParameterMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReturnParameter_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReturnReferenceSubsetting_Mapping` | `Trigger` | `ReferenceSubsetting` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| `TimeTriggerReturnReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/duration_units_test.go:TestDurationWithNoUnitIsInMilliseconds`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | +| `TimeEvent_Mapping` | `TimeEvent` | `CalculationUsage` | `internal/translate/migrate/behavior.go:event`
`internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates` | ⛔ deliberate | time and change events are written inline at each referring trigger as `accept after`, `accept at`, or `accept when`, in the trigger's scope; a standalone package-owned calc cannot resolve contextual features (2 of 16 fixture TimeEvents read `tcs.ditSetup`). An event no trigger refers to is skipped; CallEvents remain unmapped | +| `TimeTriggerBindingConnector_Mapping` | `Trigger` | `BindingConnectorAsUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerCalculationUsage_Mapping` | `Trigger` | `CalculationUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerEndFeatureMembership_Mapping` | `Trigger` | `EndFeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerEventChainingFeature_Mapping` | `Trigger` | `FeatureChaining` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerEventReturnParameterChainingFeature_Mapping` | `Trigger` | `FeatureChaining` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerExpressionFeatureTyping_Mapping` | `Trigger` | `FeatureTyping` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerExpressionFeatureValue_Mapping` | `Trigger` | `FeatureValue` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerExpressionFeature_Mapping` | `Trigger` | `Feature` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerExpressionInvocationExpression_Mapping` | `Trigger` | `InvocationExpression` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerExpressionParameterMembership_Mapping` | `Trigger` | `ParameterMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerFeatureMembership_Mapping` | `Trigger` | `FeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerFeatureTyping_Mapping` | `Trigger` | `FeatureTyping` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerFeatureValue_Mapping` | `Trigger` | `FeatureValue` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerFeature_Mapping` | `Trigger` | `Feature` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerInvocationExpression_Mapping` | `Trigger` | `TriggerInvocationExpression` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReferenceSubsetting_Mapping` | `Trigger` | `ReferenceSubsetting` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReturnEndFeatureMembership_Mapping` | `Trigger` | `EndFeatureMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReturnParameterMembership_Mapping` | `Trigger` | `ReturnParameterMembership` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReturnParameter_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReturnReferenceSubsetting_Mapping` | `Trigger` | `ReferenceSubsetting` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | +| `TimeTriggerReturnReferenceUsage_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr`
`internal/translate/migrate/events.go:instantExpr` | `tests/migrate/context_test.go:TestRelativeAcceptEventReadsOwnerFeature`
`tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards` | ✅ faithful | the trigger payload reference usage typed `DurationValue` or `TimeInstantValue` is written with its after/at invocation as `accept after ` or `accept at `; a time event whose `when` is neither an expression nor a number with a time unit remains unmapped because v1 supplies no value | | `TriggerParameterMembership_Mapping` | `Trigger` | `ParameterMembership` | `internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `Trigger_Mapping` | `Trigger` | `AcceptActionUsage` | `internal/translate/migrate/states.go:triggerAccept` | `tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | @@ -874,7 +874,7 @@ that build one relationship of a target — share the verdict of the construct t | OMG mapping | v1 source | v2 target | Implementation | Test | Status | Reason | |---|---|---|---|---|---|---| | `ChangeTriggerReferenceUsage2_Mapping` | `Trigger` | `ReferenceUsage` | `internal/translate/migrate/states.go:triggerAccept`
`internal/translate/migrate/values.go:valueExpr` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | -| `CommonPseudostate_Mapping` (abstract) | `Pseudostate` | `Namespace` | `internal/translate/migrate/states.go:pseudostateLine`
`internal/translate/migrate/states.go:vertex` | `tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5) | +| `CommonPseudostate_Mapping` (abstract) | `Pseudostate` | `Namespace` | `internal/translate/migrate/states.go:pseudostateLine`
`internal/translate/migrate/states.go:vertex`
`internal/translate/migrate/states.go:vertexBase`
`internal/translate/migrate/states.go:target` | `tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/terminate_test.go:TestTerminatePseudostateEndsOrthogonalMachine`
`tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion` | ✅ faithful | candidate erratum: the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case) | | `ConnectionPointReference_Mapping` | `ConnectionPointReference` | `StateUsage` | `internal/translate/migrate/states.go:connectionPoint` | `tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates` | ✅ faithful | — | | `DoBehaviorStateSubactionMembership_Mapping` | `Behavior` | `StateSubactionMembership` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/behavior.go:behaviorBody` | `tests/migrate/behavior_more_test.go:TestStateBehaviorsSharingANameAreDistinguished`
`tests/migrate/carriers_test.go:TestStateBehaviorsTakeTheSignalTheirTransitionsAccept` | ✅ faithful | — | | `EntryBehaviorStateSubactionMembership_Mapping` | `Behavior` | `StateSubactionMembership` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/behavior.go:behaviorBody` | `tests/migrate/behavior_more_test.go:TestStateBehaviorsSharingANameAreDistinguished`
`tests/migrate/carriers_test.go:TestStateBehaviorsTakeTheSignalTheirTransitionsAccept` | ✅ faithful | — | @@ -882,8 +882,8 @@ that build one relationship of a target — share the verdict of the construct t | `FinalState_Mapping` | `FinalState` | `StateUsage` | `internal/translate/migrate/states.go:state` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — | | `InitialStateSubactionMembership_Mapping` | `Pseudostate` | `StateSubactionMembership` | `internal/translate/migrate/states.go:initial`
`internal/translate/migrate/states.go:enter` | `tests/migrate/behavior_test.go:TestStateMachineMigratesToAnExecutableStateDef` | ✅ faithful | — | | `InitialState_Mapping` | `Pseudostate` | `ActionUsage` | `internal/translate/migrate/states.go:initial`
`internal/translate/migrate/states.go:enter` | `tests/migrate/behavior_test.go:TestStateMachineMigratesToAnExecutableStateDef` | ✅ faithful | — | -| `PseudoState_Mapping` | `Pseudostate` | `StateUsage` | `internal/translate/migrate/states.go:pseudostateLine`
`internal/translate/migrate/states.go:vertex` | `tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ⚠️ approximate | choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5) | -| `Region_Mapping` | `Region` | `StateUsage` | `internal/translate/migrate/states.go:region` | `tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/empty_region_test.go:TestEmptyRegionsAreNotWritten` | ⚠️ approximate | the region of a single-region state is flattened into its state rather than written as a state usage; orthogonal regions become the substates of a parallel state; empty regions are not written | +| `PseudoState_Mapping` | `Pseudostate` | `StateUsage` | `internal/translate/migrate/states.go:pseudostateLine`
`internal/translate/migrate/states.go:vertex`
`internal/translate/migrate/states.go:vertexBase`
`internal/translate/migrate/states.go:target` | `tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates`
`tests/migrate/terminate_test.go:TestTerminatePseudostateEndsOrthogonalMachine`
`tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion` | ✅ faithful | candidate erratum: the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case) | +| `Region_Mapping` | `Region` | `StateUsage` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/states.go:nameRegions`
`internal/translate/migrate/states.go:regions`
`internal/translate/migrate/states.go:regionWrittenAsState`
`internal/translate/migrate/states.go:region`
`internal/translate/migrate/names.go:path`
`internal/translate/migrate/edges.go:edgePlaceIn` | `tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateIsWrapped`
`tests/migrate/region_wrapper_test.go:TestSingleRegionCompositeStateConnectionPointsStayInRegion`
`tests/migrate/region_wrapper_test.go:TestStateMachineSingleRegionRemainsFlattened`
`tests/migrate/behavior_more_test.go:TestStateMachineWithOrthogonalRegionsAndGuards`
`tests/migrate/empty_region_test.go:TestEmptyRegionsAreNotWritten` | ✅ faithful | candidate erratum: for a state machine's only Region the mapping writes a StateUsage in a state definition that is not parallel (`isParallel = region->size() > 1`) and nothing enters it, so it is never entered; its vertices become the state definition's own body. A StateMachine's single Region remains flattened; a composite State's single Region is written as a sub-state of its `parallel` state; orthogonal Regions are parallel sub-states; empty Regions are omitted because an empty parallel sub-state never completes | | `StateBehaviorPerformActionUsageFeatureTyping_Mapping` | `Behavior` | `FeatureTyping` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/behavior.go:behaviorBody` | `tests/migrate/behavior_more_test.go:TestStateBehaviorsSharingANameAreDistinguished`
`tests/migrate/carriers_test.go:TestStateBehaviorsTakeTheSignalTheirTransitionsAccept` | ✅ faithful | — | | `StateBehaviorPerformActionUsage_Mapping` | `Behavior` | `PerformActionUsage` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/behavior.go:behaviorBody` | `tests/migrate/behavior_more_test.go:TestStateBehaviorsSharingANameAreDistinguished`
`tests/migrate/carriers_test.go:TestStateBehaviorsTakeTheSignalTheirTransitionsAccept` | ✅ faithful | — | | `StateBehaviorStateSubactionMembership_Mapping` (abstract) | `Behavior` | `StateSubactionMembership` | `internal/translate/migrate/states.go:state`
`internal/translate/migrate/behavior.go:behaviorBody` | `tests/migrate/behavior_more_test.go:TestStateBehaviorsSharingANameAreDistinguished`
`tests/migrate/carriers_test.go:TestStateBehaviorsTakeTheSignalTheirTransitionsAccept` | ✅ faithful | — | @@ -1064,6 +1064,7 @@ expects instead. | `ControlNodeObjectFlowReferenceUsage_Mapping` | `ObjectFlow` | `ReferenceUsage` | ✅ faithful | the `outputObject` branch of the OCL tests `from.target.oclIsTypeOf(UML::MergeNode)` where it means `from.source`, which is written | | `JoinMergeNodeObjectFlowOperatorExpression_Mapping` | `ObjectFlow` | `OperatorExpression` | ✅ faithful | a merge forwards each arriving value on its own rather than the sequence of every input, which is written | | `Slot_Mapping` | `Slot` | `Feature` | ✅ faithful | the mapping creates a KerML Feature; a SysML redefinition of the classifier's feature is a Usage (AttributeUsage / ReferenceUsage / PartUsage per the feature) | +| `OpaqueBehaviorSpecification_Mapping` | `OpaqueBehavior` | `TextualRepresentation` | ✅ faithful | the mapping pairs body i with language i, so a body with no language is dropped; it is kept as a comment. Each nonempty pair is written as a textual representation, while translated bodies remain executable | | `Constraint_Mapping` | `Constraint` | `ConstraintDefinition` | ⛔ deliberate | the mapping puts the specification in an owned calc feature with a return parameter (ElementFeatureMembership + CommonReturnParameterReferenceUsageMembership) rather than as the def's result expression, so a def written literally would not test the specification | | `InformationFlow_Mapping` | `InformationFlow` | `FlowDefinition` | ✅ faithful | the mapping creates a FlowDefinition for a v1 information flow between specific sources and targets; a definition has no ends bound to those features, which a FlowUsage would have; an information flow realized only by an association is not migrated (PSSM 0, fixtures 0, TMT 0) | | `CombinedFragment_Mapping` | `CombinedFragment` | `Interaction` | ⛔ deliberate | the mapping creates an Interaction, a definition, for a fragment that occurs once inside its owning interaction; an occurrence usage would be the matching kind | @@ -1073,6 +1074,9 @@ expects instead. | `ElementImport_Mapping` | `ElementImport` | `MembershipImport` | ✅ faithful | the mapping sets `importedMemberName` to the alias, which a MembershipImport does not have; an aliased element import is written as an alias membership `alias A for P::X` | | `PackageURIFeatureTyping_Mapping` | `Package` | `FeatureTyping` | ⛔ deliberate | the mapping types the URI metadata usage by `PackageData`, an attribute definition, but a metadata usage must be typed by a metadata definition | | `StereotypeOccurenceUsage_Mapping` | `Stereotype` | `OccurrenceUsage` | ⛔ deliberate | the occurrence usage is typed through a mapping class the model does not define and is owned by no mapping | +| `CommonPseudostate_Mapping` | `Pseudostate` | `Namespace` | ✅ faithful | the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case) | +| `PseudoState_Mapping` | `Pseudostate` | `StateUsage` | ✅ faithful | the mapping writes every pseudostate other than an initial one as a plain StateUsage, dropping its kind: a choice or junction becomes a state that waits, a fork enters one target and a terminate ends nothing. Choice, junction, and history are written as `#StateMachines:: state`; forks and joins as `fork`/`join` nodes; terminate as `action terminate;`. A transition that crosses directly from an entry point to an exit point without settling in a state remains unmapped (one fixture case) | +| `Region_Mapping` | `Region` | `StateUsage` | ✅ faithful | for a state machine's only Region the mapping writes a StateUsage in a state definition that is not parallel (`isParallel = region->size() > 1`) and nothing enters it, so it is never entered; its vertices become the state definition's own body. A StateMachine's single Region remains flattened; a composite State's single Region is written as a sub-state of its `parallel` state; orthogonal Regions are parallel sub-states; empty Regions are omitted because an empty parallel sub-state never completes | | `ExpressionElse_Mapping` | `Expression` | `OperatorExpression` | ✅ faithful | the mapping writes an OperatorExpression with operator `else` (a SysMLv1 textual representation), and no library function is named else, so the expression is ill-formed; v2 writes an else guard as the `else` succession of a decision or the unguarded branch of a choice | @@ -1091,22 +1095,17 @@ SysML profile namespace. The counts are elements affected, not effort. | Rank | OMG mappings | Status | Scope | PSSM suite | Fixtures | Total | Reason | |---|---|---|---|---|---|---|---| | 1 | 1: `PropertyUntyped_Mapping` | ⚠️ approximate | `uml:Property` | 25 | 493 | 518 | an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound | -| 2 | 2: `CommonPseudostate_Mapping`, `PseudoState_Mapping` | ⚠️ approximate | `uml:Pseudostate` | 289 | 71 | 360 | choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5) | -| 3 | 1: `Region_Mapping` | ⚠️ approximate | `uml:Region` | 248 | 54 | 302 | the region of a single-region state is flattened into its state rather than written as a state usage; orthogonal regions become the substates of a parallel state; empty regions are not written | -| 4 | 1: `OpaqueAction_Mapping` | ⚠️ approximate | `uml:OpaqueAction` | 0 | 158 | 158 | the body is translated into action statements when its language is read; otherwise the action is a stub keeping its pins (fixtures: 78 of 109 approximated) | -| 5 | 3: `OpaqueBehaviorMembership_Mapping`, `OpaqueBehaviorSpecification_Mapping`, `OpaqueBehavior_Mapping` | ⚠️ approximate | `uml:OpaqueBehavior` | 0 | 49 | 49 | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) | -| 6 | 8: `EndToSubsettedFeatureChaining_Mapping`, `EndToSubsettedFeature_Mapping`, `NonOwnedEndFeatureTyping_Mapping`, `NonOwnedEndMembership_Mapping`, `NonOwnedEndSubsettingMembership_Mapping`, `NonOwnedEndSubsetting_Mapping`, `NonOwnedEndToSubsettedFeatureMembership_Mapping`, `NonOwnedEnd_Mapping` | ⚠️ approximate | `uml:Association` | 4 | 23 | 27 | a classifier-owned end is written as that classifier's property and as an end of the connection def crossing it through the opposite end, `end wheels : Wheel crosses car.wheels;`, the mapping's feature chain; the cross is left out, and the association reported approximated with the reason, where it would be unsound: the opposite end is untyped, the end's type is not written or is written as a usage (an actor), the association is not binary, or the property is not written as a feature of the opposite end's type | -| 7 | 4: `ChangeEventReturnParameterMembership_Mapping`, `ChangeEventReturnParameter_Mapping`, `ChangeEvent_Mapping`, `TimeEvent_Mapping` | ⚠️ approximate | `uml:TimeEvent`
`uml:ChangeEvent`
`uml:CallEvent` | 8 | 17 | 25 | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) | -| 8 | 23: `TimeTriggerBindingConnector_Mapping`, `TimeTriggerCalculationUsage_Mapping`, `TimeTriggerEndFeatureMembership_Mapping`, `TimeTriggerEventChainingFeature_Mapping`, `TimeTriggerEventReturnParameterChainingFeature_Mapping`, `TimeTriggerExpressionFeatureTyping_Mapping`, `TimeTriggerExpressionFeatureValue_Mapping`, `TimeTriggerExpressionFeature_Mapping`, `TimeTriggerExpressionInvocationExpression_Mapping`, `TimeTriggerExpressionParameterMembership_Mapping`, `TimeTriggerFeatureMembership_Mapping`, `TimeTriggerFeatureTyping_Mapping`, `TimeTriggerFeatureValue_Mapping`, `TimeTriggerFeature_Mapping`, `TimeTriggerInvocationExpression_Mapping`, `TimeTriggerReferenceSubsetting_Mapping`, `TimeTriggerReferenceUsage2_Mapping`, `TimeTriggerReferenceUsage_Mapping`, `TimeTriggerReturnEndFeatureMembership_Mapping`, `TimeTriggerReturnParameterMembership_Mapping`, `TimeTriggerReturnParameter_Mapping`, `TimeTriggerReturnReferenceSubsetting_Mapping`, `TimeTriggerReturnReferenceUsage_Mapping` | ⚠️ approximate | `uml:TimeEvent` | 0 | 16 | 16 | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | -| 9 | 3: `EqualOperatorExpressionFeatureValue_Mapping`, `EqualOperatorExpressionFeature_Mapping`, `EqualOperatorExpressionOperandParameterMembership_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | these are used only by TIAOperatorExpression_Mapping, as the private first/second operands of a TestIdentityAction's `==`, not by Expression trees; they are written with TestIdentityAction, which is not migrated (PSSM: 14) | -| 10 | 3: `TIAOperatorExpression_Mapping`, `TIAResultExpressionMembership_Mapping`, `TestIdentityAction_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | a TestIdentityAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | -| 11 | 9: `RICOAFeatureValueOperatorExpressionFeatureValue_Mapping`, `RICOAFeatureValueOperatorExpressionFeature_Mapping`, `RICOAFeatureValueOperatorExpression_Mapping`, `RICOAFeatureValueOperatorFeatureReferenceExpression_Mapping`, `RICOAFeatureValueOperatorMembership_Mapping`, `RICOAFeatureValueOperatorParameterMembership_Mapping`, `RICOAFeatureValue_Mapping`, `RICOAOutputPin_Mapping`, `ReadIsClassifiedObjectAction_Mapping` | ❌ not implemented | `uml:ReadIsClassifiedObjectAction` | 12 | 0 | 12 | a ReadIsClassifiedObjectAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | -| 12 | 4: `UseCaseEmptySubjectReferenceUsage_Mapping`, `UseCaseObjectiveMembership_Mapping`, `UseCaseObjectiveRequirementUsage_Mapping`, `UseCaseObjectiveSubjectMembership_Mapping` | ❌ not implemented | `uml:UseCase` | 0 | 12 | 12 | no objective is written for a use case: v1 has none to read, and the empty objective requirement the mapping creates is not written | -| 13 | 1: `LoopNode_Mapping` | ⚠️ approximate | `uml:LoopNode` | 2 | 9 | 11 | a decider loop whose parts exchange values over pins the migrator writes is written as a v2 loop (the loops.xmi tested-first and post-tested fixtures): its loop variables and an `ended` flag as private features, its parts performing in the loop — test before body when tested first, else after — and its results read from the variables at the end; no corpus loop takes the loop form: PSSM's 2 fall back because their body outputs are structured-node output pins the migrator does not write, and TMT's 1 names no decider, so each keeps the body-once form and stays approximated per element; candidate erratum: the mapping gives only an ActionUsage and leaves its details undefined, so a loop's test and iteration are lost | -| 14 | 1: `Actor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 9 | 9 | an actor is written as one part usage, not a part def: the actor lines of a use case diagram are connections, which join usages, and each use case associated with it declares an `actor` parameter subsetting that one usage | -| 15 | 2: `TestCaseActivityReturnParameterMembership_Mapping`, `TestCaseActivity_Mapping` | ⚠️ approximate | `sysml:TestCase` | 0 | 5 | 5 | the test case is written as a `verification def` whose objective verifies the requirements, but its behavior (activity or interaction) is not migrated | -| 16 | 8: `ElementGroupMetadaMembership_Mapping`, `ElementGroupMetadataFeatureMembership_Mapping`, `ElementGroupMetadataFeatureTyping_Mapping`, `ElementGroupMetadataFeatureValue_Mapping`, `ElementGroupMetadataRedefinition_Mapping`, `ElementGroupMetadataReferenceUsage_Mapping`, `ElementGroupMetadataUsage_Mapping`, `ElementGroup_Mapping` | ❌ not implemented | `sysml:ElementGroup` | 0 | 0 | 0 | «ElementGroup» is not read by the migrator: the comment is written as a comment and no package grouping its members is written | -| 17 | 8: `ProblemRationaleMetadataFeatureMembership_Mapping`, `ProblemRationaleMetadataFeatureTyping_Mapping`, `ProblemRationaleMetadataFeatureValue_Mapping`, `ProblemRationaleMetadataMembership_Mapping`, `ProblemRationaleMetadataRedefinition_Mapping`, `ProblemRationaleMetadataReferenceUsage_Mapping`, `ProblemRationaleMetadataUsage_Mapping`, `ProblemRationale_Mapping` | ❌ not implemented | `sysml:Problem`
`sysml:Rationale` | 0 | 0 | 0 | «Problem» and «Rationale» are not read by the migrator: the comment is written as a comment without metadata saying which it is | -| 18 | 7: `REAFeatureValueOperatorExpressionFeatureTyping_Mapping`, `REAFeatureValueOperatorExpressionFeature_Mapping`, `REAFeatureValueOperatorExpressionMembership_Mapping`, `REAFeatureValueOperatorExpression_Mapping`, `REAFeatureValue_Mapping`, `REAOutputPin_Mapping`, `ReadExtentAction_Mapping` | ❌ not implemented | `uml:ReadExtentAction` | 0 | 0 | 0 | a ReadExtentAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | -| 19 | 8: `TraceAnnotation_Mapping`, `TraceMetadataFeatureMembership_Mapping`, `TraceMetadataReferenceUsageFeatureValue_Mapping`, `TraceMetadataReferenceUsageRedefinition_Mapping`, `TraceMetadataReferenceUsage_Mapping`, `TraceMetadataUsageFeatureTyping_Mapping`, `TraceMetadataUsage_Mapping`, `Trace_Mapping` | ⚠️ approximate | `sysml:Trace` | 0 | 0 | 0 | a «Trace» is written as a plain dependency with a `/* «Trace» */` comment, not as a dependency annotated by trace metadata | +| 2 | 8: `EndToSubsettedFeatureChaining_Mapping`, `EndToSubsettedFeature_Mapping`, `NonOwnedEndFeatureTyping_Mapping`, `NonOwnedEndMembership_Mapping`, `NonOwnedEndSubsettingMembership_Mapping`, `NonOwnedEndSubsetting_Mapping`, `NonOwnedEndToSubsettedFeatureMembership_Mapping`, `NonOwnedEnd_Mapping` | ⚠️ approximate | `uml:Association` | 4 | 23 | 27 | a classifier-owned end is written as that classifier's property and as an end of the connection def crossing it through the opposite end, `end wheels : Wheel crosses car.wheels;`, the mapping's feature chain; the cross is left out, and the association reported approximated with the reason, where it would be unsound: the opposite end is untyped, the end's type is not written or is written as a usage (an actor), the association is not binary, or the property is not written as a feature of the opposite end's type | +| 3 | 1: `TimeTriggerReferenceUsage2_Mapping` | ⚠️ approximate | `uml:TimeEvent` | 0 | 16 | 16 | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) | +| 4 | 3: `EqualOperatorExpressionFeatureValue_Mapping`, `EqualOperatorExpressionFeature_Mapping`, `EqualOperatorExpressionOperandParameterMembership_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | these are used only by TIAOperatorExpression_Mapping, as the private first/second operands of a TestIdentityAction's `==`, not by Expression trees; they are written with TestIdentityAction, which is not migrated (PSSM: 14) | +| 5 | 3: `TIAOperatorExpression_Mapping`, `TIAResultExpressionMembership_Mapping`, `TestIdentityAction_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | a TestIdentityAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | +| 6 | 9: `RICOAFeatureValueOperatorExpressionFeatureValue_Mapping`, `RICOAFeatureValueOperatorExpressionFeature_Mapping`, `RICOAFeatureValueOperatorExpression_Mapping`, `RICOAFeatureValueOperatorFeatureReferenceExpression_Mapping`, `RICOAFeatureValueOperatorMembership_Mapping`, `RICOAFeatureValueOperatorParameterMembership_Mapping`, `RICOAFeatureValue_Mapping`, `RICOAOutputPin_Mapping`, `ReadIsClassifiedObjectAction_Mapping` | ❌ not implemented | `uml:ReadIsClassifiedObjectAction` | 12 | 0 | 12 | a ReadIsClassifiedObjectAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | +| 7 | 4: `UseCaseEmptySubjectReferenceUsage_Mapping`, `UseCaseObjectiveMembership_Mapping`, `UseCaseObjectiveRequirementUsage_Mapping`, `UseCaseObjectiveSubjectMembership_Mapping` | ❌ not implemented | `uml:UseCase` | 0 | 12 | 12 | no objective is written for a use case: v1 has none to read, and the empty objective requirement the mapping creates is not written | +| 8 | 1: `LoopNode_Mapping` | ⚠️ approximate | `uml:LoopNode` | 2 | 9 | 11 | a decider loop whose parts exchange values over pins the migrator writes is written as a v2 loop (the loops.xmi tested-first and post-tested fixtures): its loop variables and an `ended` flag as private features, its parts performing in the loop — test before body when tested first, else after — and its results read from the variables at the end; no corpus loop takes the loop form: PSSM's 2 fall back because their body outputs are structured-node output pins the migrator does not write, and TMT's 1 names no decider, so each keeps the body-once form and stays approximated per element; candidate erratum: the mapping gives only an ActionUsage and leaves its details undefined, so a loop's test and iteration are lost | +| 9 | 1: `Actor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 9 | 9 | an actor is written as one part usage, not a part def: the actor lines of a use case diagram are connections, which join usages, and each use case associated with it declares an `actor` parameter subsetting that one usage | +| 10 | 2: `TestCaseActivityReturnParameterMembership_Mapping`, `TestCaseActivity_Mapping` | ⚠️ approximate | `sysml:TestCase` | 0 | 5 | 5 | the test case is written as a `verification def` whose objective verifies the requirements, but its behavior (activity or interaction) is not migrated | +| 11 | 8: `ElementGroupMetadaMembership_Mapping`, `ElementGroupMetadataFeatureMembership_Mapping`, `ElementGroupMetadataFeatureTyping_Mapping`, `ElementGroupMetadataFeatureValue_Mapping`, `ElementGroupMetadataRedefinition_Mapping`, `ElementGroupMetadataReferenceUsage_Mapping`, `ElementGroupMetadataUsage_Mapping`, `ElementGroup_Mapping` | ❌ not implemented | `sysml:ElementGroup` | 0 | 0 | 0 | «ElementGroup» is not read by the migrator: the comment is written as a comment and no package grouping its members is written | +| 12 | 8: `ProblemRationaleMetadataFeatureMembership_Mapping`, `ProblemRationaleMetadataFeatureTyping_Mapping`, `ProblemRationaleMetadataFeatureValue_Mapping`, `ProblemRationaleMetadataMembership_Mapping`, `ProblemRationaleMetadataRedefinition_Mapping`, `ProblemRationaleMetadataReferenceUsage_Mapping`, `ProblemRationaleMetadataUsage_Mapping`, `ProblemRationale_Mapping` | ❌ not implemented | `sysml:Problem`
`sysml:Rationale` | 0 | 0 | 0 | «Problem» and «Rationale» are not read by the migrator: the comment is written as a comment without metadata saying which it is | +| 13 | 7: `REAFeatureValueOperatorExpressionFeatureTyping_Mapping`, `REAFeatureValueOperatorExpressionFeature_Mapping`, `REAFeatureValueOperatorExpressionMembership_Mapping`, `REAFeatureValueOperatorExpression_Mapping`, `REAFeatureValue_Mapping`, `REAOutputPin_Mapping`, `ReadExtentAction_Mapping` | ❌ not implemented | `uml:ReadExtentAction` | 0 | 0 | 0 | a ReadExtentAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped | +| 14 | 8: `TraceAnnotation_Mapping`, `TraceMetadataFeatureMembership_Mapping`, `TraceMetadataReferenceUsageFeatureValue_Mapping`, `TraceMetadataReferenceUsageRedefinition_Mapping`, `TraceMetadataReferenceUsage_Mapping`, `TraceMetadataUsageFeatureTyping_Mapping`, `TraceMetadataUsage_Mapping`, `Trace_Mapping` | ⚠️ approximate | `sysml:Trace` | 0 | 0 | 0 | a «Trace» is written as a plain dependency with a `/* «Trace» */` comment, not as a dependency annotated by trace metadata | diff --git a/internal/exec/runtime/check_state.go b/internal/exec/runtime/check_state.go index 4a9c8566ad..c08a7094f2 100644 --- a/internal/exec/runtime/check_state.go +++ b/internal/exec/runtime/check_state.go @@ -274,6 +274,16 @@ func (s *stateSpeller) machine(e *StateExecutor) { for _, region := range e.orderedActiveRegions() { fmt.Fprintf(&s.out, " active{%s = %s}", regionKey(region), e.statePath(e.activeConfig.regionStates[region])) } + complete := make([]*ast.StateRegion, 0, len(e.activeConfig.defaultComplete)) + for region, isComplete := range e.activeConfig.defaultComplete { + if isComplete { + complete = append(complete, region) + } + } + sort.Slice(complete, func(i, j int) bool { return regionKey(complete[i]) < regionKey(complete[j]) }) + for _, region := range complete { + fmt.Fprintf(&s.out, " default-complete{%s}", regionKey(region)) + } } for _, state := range e.stateStack { fmt.Fprintf(&s.out, " stack{%s}", e.statePath(state)) diff --git a/internal/exec/runtime/robustness_inactive_region_test.go b/internal/exec/runtime/robustness_inactive_region_test.go new file mode 100644 index 0000000000..d88e924875 --- /dev/null +++ b/internal/exec/runtime/robustness_inactive_region_test.go @@ -0,0 +1,66 @@ +package runtime + +import "testing" + +func TestRuntimeRobustnessInactiveRegion(t *testing.T) { + t.Run("empty regions complete without activating inner states", func(t *testing.T) { + _, visits, err := executeStateSource(t, "Machine", `package test { + state Machine { + entry; then working; + state working parallel { + state left { state idle; } + state right { state waiting; } + } + state finished; + transition first working then finished; + } + }`) + if err != nil { + t.Fatalf("execute state machine: %v", err) + } + finished := false + for _, visit := range visits { + switch visit { + case "idle", "waiting": + t.Errorf("no-entry region state %s was activated", visit) + case "finished": + finished = true + } + } + if !finished { + t.Fatal("working did not complete after both regions were left inactive") + } + }) + + t.Run("stand-in do action waits for a signal before owner completion", func(t *testing.T) { + _, visits, err := executeStateSource(t, "Machine", `package test { + state Machine { + entry; then working; + state working parallel { + state standIn { + do action wait { + first start; + then action receive accept Never; + then done; + } + state inner; + } + } + state finished; + transition first working then finished; + } + attribute def Never; + }`) + if err != nil { + t.Fatalf("execute state machine: %v", err) + } + for _, visit := range visits { + switch visit { + case "inner": + t.Error("unstarted inner state was activated") + case "finished": + t.Error("owner completed while its stand-in do action awaited a signal") + } + } + }) +} diff --git a/internal/exec/runtime/robustness_parallel_wrapper_test.go b/internal/exec/runtime/robustness_parallel_wrapper_test.go new file mode 100644 index 0000000000..c53b349b21 --- /dev/null +++ b/internal/exec/runtime/robustness_parallel_wrapper_test.go @@ -0,0 +1,64 @@ +package runtime + +import ( + "slices" + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/check/passes" + "github.com/Open-MBEE/OpenSysML/internal/workspace/libs" +) + +func TestRuntimeRobustnessParallelWrapperLeavesInnerRegionInactive(t *testing.T) { + _, visits, err := executeStateSource(t, "Machine", ` + package test { + state Machine { + entry; then S; + state S parallel { + state R { + state A; + } + } + state finished; + transition first S then finished; + } + } + `) + if err != nil { + t.Fatalf("execute parallel state with an unstarted stand-in: %v", err) + } + if slices.Contains(visits, "A") { + t.Errorf("the unstarted body state A was entered: %v", visits) + } + if !slices.Contains(visits, "finished") { + t.Errorf("S did not complete while its stand-in body was unstarted: %v", visits) + } +} + +func TestRuntimeRobustnessParallelWrapperRejectsUnresolvedMetadata(t *testing.T) { + src := ` + package test { + state def Machine { + entry; then S; + state S parallel { + @NoSuchMetadata; + state R { + entry; then A; + state A; + } + } + } + } + ` + root := parseAndBuild(t, src) + idx := libs.NewModelIndex() + idx.AddDocument("", root) + idx.ExpandWildcardImports() + diagnostics := passes.Analyze("", root, nil, idx) + for _, diagnostic := range diagnostics { + if strings.Contains(diagnostic.Message, "NoSuchMetadata") { + return + } + } + t.Fatalf("unresolved parallel-state annotation produced no diagnostic: %v", diagnostics) +} diff --git a/internal/exec/runtime/robustness_test.go b/internal/exec/runtime/robustness_test.go index 2b3758576e..f9d67a6edf 100644 --- a/internal/exec/runtime/robustness_test.go +++ b/internal/exec/runtime/robustness_test.go @@ -289,10 +289,7 @@ func TestRuntimeRobustness(t *testing.T) { t.Run("action_body_unresolved_feature", testActionBodyUnresolvedFeature) t.Run("state_body_unresolved_unit", testStateBodyUnresolvedUnit) t.Run("fork_branches_share_region", testForkBranchesShareRegion) - t.Run("fork_leaves_a_region_without_a_way_in", testForkLeavesARegionWithoutAWayIn) - t.Run("fork_only_region_entered_by_default", testForkOnlyRegionEnteredByDefault) t.Run("fork_branch_with_a_trigger", testForkBranchWithATrigger) - t.Run("nested_fork_starts_an_outer_region_by_default", testNestedForkStartsAnOuterRegionByDefault) t.Run("join_with_one_incoming_branch", testJoinWithOneIncomingBranch) t.Run("join_incoming_effect_that_fails", testJoinIncomingEffectThatFails) t.Run("join_with_two_segments_from_one_source", testJoinWithTwoSegmentsFromOneSource) @@ -6124,66 +6121,6 @@ func testForkBranchesShareRegion(t *testing.T) { } } -// testForkLeavesARegionWithoutAWayIn: a region a fork enters needs no entry -// transition, but a sibling region neither enters still has no way in. -func testForkLeavesARegionWithoutAWayIn(t *testing.T) { - _, _, err := executeStateSource(t, "Machine", `package test { - state Machine { - entry; then init; - state init; - state working parallel { - state left { state a; } - state right { state b; } - state third { state c; } - } - fork split; - - transition first init then split; - transition first split then a; - transition first split then b; - } - }`) - if err == nil { - t.Fatal("expected an error for the region no fork enters") - } - if !strings.Contains(err.Error(), "region third has no initial state") { - t.Errorf("expected the third region's missing initial, got: %v", err) - } - if strings.Contains(err.Error(), "region left") || strings.Contains(err.Error(), "region right") { - t.Errorf("the fork-entered regions were refused too: %v", err) - } -} - -// testForkOnlyRegionEnteredByDefault: a region only a fork enters has no -// default start, so another transition into its composite state is refused -// before the machine runs rather than failing when that transition fires. -func testForkOnlyRegionEnteredByDefault(t *testing.T) { - _, _, err := executeStateSource(t, "Machine", `package test { - attribute def Go; - state Machine { - entry; then init; - state init; - state working parallel { - state left { state a; } - state right { state b; } - } - fork split; - - transition first init accept Go then working; - transition first init then split; - transition first split then a; - transition first split then b; - } - }`) - if err == nil { - t.Fatal("expected an error for the transition entering the fork-only regions by default") - } - if !strings.Contains(err.Error(), "region left in state working has no initial state") || - !strings.Contains(err.Error(), "the transition from init to working") { - t.Errorf("expected the default entry into left to be named, got: %v", err) - } -} - // testForkBranchWithATrigger: a fork's branches fire as one with the fork, so a // branch that waits for an occurrence is refused before the machine runs rather // than firing regardless of it. @@ -6212,44 +6149,6 @@ func testForkBranchWithATrigger(t *testing.T) { } } -// testNestedForkStartsAnOuterRegionByDefault: a fork into a nested composite -// state enters the outer one on the way, starting its other region by default, -// so an outer region only another fork enters is refused before the machine runs. -func testNestedForkStartsAnOuterRegionByDefault(t *testing.T) { - _, _, err := executeStateSource(t, "Machine", `package test { - state Machine { - entry; then init; - state init; - state outer parallel { - state o1 { - entry; then hold; - state hold; - state inner parallel { - state left { state a; } - state right { state b; } - } - fork split; - transition first split then a; - transition first split then b; - } - state o2 { state c; } - } - fork split2; - - transition first init then split; - transition first split2 then hold; - transition first split2 then c; - } - }`) - if err == nil { - t.Fatal("expected an error for the nested fork starting o2 by default") - } - if !strings.Contains(err.Error(), "region o2 in state outer has no initial state") || - !strings.Contains(err.Error(), "the transition from init to split") { - t.Errorf("expected the nested fork's route to be named, got: %v", err) - } -} - // testJoinWithOneIncomingBranch: a join synchronizes branches, so a single // incoming transition is a modeling error rather than a pass-through. func testJoinWithOneIncomingBranch(t *testing.T) { diff --git a/internal/exec/runtime/snapshot.go b/internal/exec/runtime/snapshot.go index 7671c96fb5..427a0b4283 100644 --- a/internal/exec/runtime/snapshot.go +++ b/internal/exec/runtime/snapshot.go @@ -1004,7 +1004,11 @@ func cloneConfiguration(config *StateConfiguration) *StateConfiguration { if config == nil { return nil } - return &StateConfiguration{simpleState: config.simpleState, regionStates: maps.Clone(config.regionStates)} + return &StateConfiguration{ + simpleState: config.simpleState, + regionStates: maps.Clone(config.regionStates), + defaultComplete: maps.Clone(config.defaultComplete), + } } func cloneDispatch(dispatch *Dispatch) *Dispatch { diff --git a/internal/exec/runtime/state_executor.go b/internal/exec/runtime/state_executor.go index e83241fc05..349191f26c 100644 --- a/internal/exec/runtime/state_executor.go +++ b/internal/exec/runtime/state_executor.go @@ -24,7 +24,8 @@ type StateConfiguration struct { simpleState *ast.StateNode // For composite states with regions: map of region → active state in that region - regionStates map[*ast.StateRegion]*ast.StateNode + regionStates map[*ast.StateRegion]*ast.StateNode + defaultComplete map[*ast.StateRegion]bool } // StateExecutor executes state machines using event-driven semantics. @@ -322,7 +323,8 @@ func newStateExecutorOn( breakpointNodes: make(map[ast.Node]bool), dispatchMark: -1, activeConfig: &StateConfiguration{ - regionStates: make(map[*ast.StateRegion]*ast.StateNode), + regionStates: make(map[*ast.StateRegion]*ast.StateNode), + defaultComplete: make(map[*ast.StateRegion]bool), }, entering: make(map[*ast.StateNode]bool), } @@ -843,8 +845,7 @@ func (e *StateExecutor) scheduleCompletionTransitions(state *ast.StateNode) erro return nil } -// completesAtEntry reports whether entering state as the end of an entry path queues -// its completion at once: it has a completion transition and nothing below to enter. +// completesAtEntry reports whether entering a bodyless state queues its completion at once. func (e *StateExecutor) completesAtEntry(state *ast.StateNode) bool { return !e.hasBody(state) && completionCount(e.graph.Transitions[state]) > 0 } @@ -2623,6 +2624,7 @@ func (e *StateExecutor) abandonMachine() []string { e.clearEntryState() e.activeConfig.simpleState = nil e.activeConfig.regionStates = make(map[*ast.StateRegion]*ast.StateNode) + e.activeConfig.defaultComplete = make(map[*ast.StateRegion]bool) e.stateStack = nil e.completionDue = false // Nothing dispatches on an ended machine: what it queued is discarded. @@ -2722,9 +2724,12 @@ func (e *StateExecutor) completeInto(trans *lower.Transition, fromName string, t return nil } -// regionComplete reports whether region rests at its own completion vertex (not a -// nested composite's `done`), or at its owner once a transition into the owner left it empty. +// regionComplete reports whether region reached its completion vertex, was left empty by default entry, or was left at its owner. func (e *StateExecutor) regionComplete(region *ast.StateRegion) bool { + if e.activeConfig.defaultComplete[region] { + standIn := e.graph.RegionState[region] + return standIn == nil || !e.hasRunningDoAction(standIn) + } active, ok := e.activeConfig.regionStates[region] if !ok { return false @@ -2735,6 +2740,19 @@ func (e *StateExecutor) regionComplete(region *ast.StateRegion) bool { return active == e.graph.RegionOwner[region] } +func (e *StateExecutor) allRegionsDefaultComplete(state *ast.StateNode) bool { + regions := e.graph.CompositeStates[state] + if len(regions) == 0 { + return false + } + for _, region := range regions { + if !e.activeConfig.defaultComplete[region] || !e.regionComplete(region) { + return false + } + } + return true +} + // stateComplete reports whether state's body has completed: it is a completion // vertex, its substate rests at its `done`, or its every orthogonal region does. func (e *StateExecutor) stateComplete(state *ast.StateNode) bool { @@ -3069,14 +3087,15 @@ func (e *StateExecutor) historyEntry(hist *ast.PseudostateNode, owner *ast.State } // hasDefaultEntry reports whether entering state with no branch chosen has a -// state to start in: an entry transition of its body, or of each of its regions. +// valid default configuration, including regions left empty by default entry. func (e *StateExecutor) hasDefaultEntry(state *ast.StateNode) bool { regions, orthogonal := e.graph.CompositeStates[state] if !orthogonal { return len(e.graph.StartOf(state)) > 0 } for _, region := range regions { - if e.graph.RegionState[region] == nil && len(e.graph.StartOf(region)) == 0 { + standIn := e.graph.RegionState[region] + if standIn != nil && len(e.graph.CompositeStates[standIn]) > 0 && len(e.graph.StartOf(region)) == 0 { return false } } @@ -3398,6 +3417,7 @@ func (e *StateExecutor) fireJoinSegment(trans *lower.Transition, plan *lower.Joi if active, isActive := e.activeConfig.regionStates[region]; isActive { e.recordRegionHistory(region, active) delete(e.activeConfig.regionStates, region) + delete(e.activeConfig.defaultComplete, region) } } if err := e.exitStates(e.exitPath(leaving, plan.Owner, nil)); err != nil { @@ -3437,6 +3457,7 @@ func (e *StateExecutor) joinIncoming(join *ast.PseudostateNode) []*lower.Transit // setRegionState clears arrivals from a region before its active state changes. func (e *StateExecutor) setRegionState(region *ast.StateRegion, state *ast.StateNode) { e.clearJoinArrivalsForRegion(region) + delete(e.activeConfig.defaultComplete, region) e.activeConfig.regionStates[region] = state } @@ -4546,7 +4567,7 @@ func doBehaviorDescription(state *ast.StateNode) string { } // settleDoActions drops the do behaviors that have finished and schedules the -// completion of their states; a do action whose state was exited is already gone. +// completion of their states and newly completed owners. func (e *StateExecutor) settleDoActions() error { finished := make([]*ast.StateNode, 0, len(e.doActions)) kept := e.doActions[:0] @@ -4565,6 +4586,7 @@ func (e *StateExecutor) settleDoActions() error { // A state completes once its do behavior has finished and its body, where it // runs one, has reached `done`; completeIfDone schedules the latter case, and a // body the move has yet to enter completes nothing. + scheduledOwners := make(map[*ast.StateNode]struct{}, len(finished)) for _, state := range finished { if e.bodyAhead(state) || (e.bodyRunning(state) && !e.stateComplete(state)) { continue @@ -4572,6 +4594,20 @@ func (e *StateExecutor) settleDoActions() error { if err := e.scheduleCompletionTransitions(state); err != nil { return fmt.Errorf("schedule completion of state %s: %w", state.Name, err) } + if region := e.RegionOf(state); region != nil && e.graph.RegionState[region] == state { + owner := e.graph.RegionOwner[region] + if owner != nil && e.stateComplete(owner) { + if _, ok := scheduledOwners[owner]; !ok { + scheduledOwners[owner] = struct{}{} + if err := e.scheduleCompletionTransitions(owner); err != nil { + return fmt.Errorf("schedule completion of state %s: %w", owner.Name, err) + } + } + } + } + if err := e.completeIfDone(state); err != nil { + return fmt.Errorf("complete after do behavior of state %s: %w", state.Name, err) + } } return nil } @@ -5172,6 +5208,7 @@ func (e *StateExecutor) initialize() (err error) { e.state = StateRunning e.activeConfig.regionStates = make(map[*ast.StateRegion]*ast.StateNode) + e.activeConfig.defaultComplete = make(map[*ast.StateRegion]bool) e.activeConfig.simpleState = nil return e.enterMachineRegions() } @@ -5554,6 +5591,11 @@ func (e *StateExecutor) enterStateInto(state *ast.StateNode, branches map[*ast.S if e.state == StateTerminated { return nil } + if e.allRegionsDefaultComplete(state) { + if err := e.scheduleCompletionTransitions(state); err != nil { + return fmt.Errorf("schedule completion of state %s: %w", state.Name, err) + } + } } // The completion goes into the pool behind those queued by the entries performed @@ -5796,6 +5838,7 @@ func (e *StateExecutor) exitRegionsBelow(owner *ast.StateNode, regions []*ast.St // Clear the entry first: the recursive exit walks the same map, and the // region still pointing at regionState would exit it a second time. delete(e.activeConfig.regionStates, region) + delete(e.activeConfig.defaultComplete, region) bodies = append(bodies, func() error { for current := regionState; current != nil && current != owner; current = e.graph.ParentState[current] { if err := e.exitState(current); err != nil { @@ -5937,6 +5980,7 @@ func (e *StateExecutor) getCurrentState() *ast.StateNode { func (e *StateExecutor) setCurrentState(state *ast.StateNode) { e.activeConfig.simpleState = state e.activeConfig.regionStates = make(map[*ast.StateRegion]*ast.StateNode) // Clear regions + e.activeConfig.defaultComplete = make(map[*ast.StateRegion]bool) } // StateStack returns a copy of the state stack (active configuration). diff --git a/internal/exec/runtime/state_region_entry.go b/internal/exec/runtime/state_region_entry.go index 28d3986858..be1e03d209 100644 --- a/internal/exec/runtime/state_region_entry.go +++ b/internal/exec/runtime/state_region_entry.go @@ -161,6 +161,12 @@ func (e *StateExecutor) enterRegion(w *regionEntry) error { if e.state == StateTerminated { return nil } + if entry == nil { + if w.target == nil { + e.activeConfig.defaultComplete[w.region] = true + } + return nil + } e.setRegionState(w.region, entry) _, deepest, err := e.enterToward(w.container, entry, w.branches) if err != nil { @@ -174,6 +180,10 @@ func (e *StateExecutor) enterRegion(w *regionEntry) error { if branch, ok := e.branchesTo(nil, deepest)[w.region]; ok { e.setRegionState(w.region, branch) } + if w.target == nil && entry == e.graph.RegionState[w.region] && deepest == entry && + len(e.graph.StartOf(entry)) == 0 { + e.activeConfig.defaultComplete[w.region] = true + } return nil } @@ -214,9 +224,6 @@ func (e *StateExecutor) regionStart(w *regionEntry) (*ast.StateNode, error) { if e.state == StateTerminated { return nil, nil } - if entry == nil { - return nil, fmt.Errorf("region %s has no initial state", w.region.Name) - } return entry, nil } diff --git a/internal/exec/runtime/state_region_transition.go b/internal/exec/runtime/state_region_transition.go index 67cfcbc6cb..ee3c47a40e 100644 --- a/internal/exec/runtime/state_region_transition.go +++ b/internal/exec/runtime/state_region_transition.go @@ -391,6 +391,7 @@ func (e *StateExecutor) exitRegionTo(region *ast.StateRegion, stop *ast.StateNod } path := e.regionExitPath(region, stop) delete(e.activeConfig.regionStates, region) + delete(e.activeConfig.defaultComplete, region) if stop == nil { // The region keeps no active state, so it has none to restore either. e.forgetRegionHistory(region) @@ -457,6 +458,7 @@ func (e *StateExecutor) exitRegionOwnerTo(owner, lca *ast.StateNode) error { if active, isActive := e.activeConfig.regionStates[declaring]; isActive && e.isBelowOrEqual(active, current) { e.recordRegionHistory(declaring, active) delete(e.activeConfig.regionStates, declaring) + delete(e.activeConfig.defaultComplete, declaring) } } if err := e.exitState(current); err != nil { @@ -477,6 +479,7 @@ func (e *StateExecutor) leaveTopRegions(trans *lower.Transition, effects []route continue } delete(e.activeConfig.regionStates, region) + delete(e.activeConfig.defaultComplete, region) bodies = append(bodies, func() error { leaving := make([]*ast.StateNode, 0) for current := active; current != nil; current = e.graph.ParentState[current] { @@ -489,6 +492,7 @@ func (e *StateExecutor) leaveTopRegions(trans *lower.Transition, effects []route return err } e.activeConfig.regionStates = make(map[*ast.StateRegion]*ast.StateNode) + e.activeConfig.defaultComplete = make(map[*ast.StateRegion]bool) e.activeConfig.simpleState = nil if err := e.runEffects(effects, e.descendantChain(nil, target)); err != nil { diff --git a/internal/exec/runtime/state_route.go b/internal/exec/runtime/state_route.go index b4fcc6f7d6..6f18de01b4 100644 --- a/internal/exec/runtime/state_route.go +++ b/internal/exec/runtime/state_route.go @@ -3,6 +3,7 @@ package runtime import ( "errors" "fmt" + "maps" "slices" "github.com/Open-MBEE/OpenSysML/internal/ir/lower" @@ -740,8 +741,9 @@ func (e *StateExecutor) exitAhead(states []*ast.StateNode) error { if len(states) == 0 { return nil } - simple, regions := e.activeConfig.simpleState, e.activeConfig.regionStates + simple, regions, complete := e.activeConfig.simpleState, e.activeConfig.regionStates, e.activeConfig.defaultComplete e.activeConfig.regionStates = make(map[*ast.StateRegion]*ast.StateNode, len(regions)) + e.activeConfig.defaultComplete = maps.Clone(complete) for region, active := range regions { e.activeConfig.regionStates[region] = active } @@ -751,7 +753,7 @@ func (e *StateExecutor) exitAhead(states []*ast.StateNode) error { e.exitingAhead = true err := e.exitStates(states) e.exitingAhead = false - e.activeConfig.simpleState, e.activeConfig.regionStates = simple, regions + e.activeConfig.simpleState, e.activeConfig.regionStates, e.activeConfig.defaultComplete = simple, regions, complete return err } diff --git a/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.check.expected.json b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.check.expected.json new file mode 100644 index 0000000000..d6bcd615d8 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.check.expected.json @@ -0,0 +1,7 @@ +{ + "verdict": "no violation, exhaustive", + "agreed": { + "finalState": "finished", + "log": "\"finished(entry) \"" + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.expected.json b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.expected.json new file mode 100644 index 0000000000..b6e665400f --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.expected.json @@ -0,0 +1,18 @@ +{ + "type": "state", + "events": [ + {"signal": "Default"}, + {"signal": "Back"}, + {"signal": "Fork"}, + {"signal": "FinishA"}, + {"signal": "FinishB"}, + {"signal": "Back"}, + {"signal": "All"}, + {"signal": "FinishA"}, + {"signal": "FinishB"}, + {"signal": "FinishC"} + ], + "finalState": "finished", + "outputs": {"log": {"type": "String", "value": "finished(entry) "}}, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.sysml b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.sysml new file mode 100644 index 0000000000..fd53f15614 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.sysml @@ -0,0 +1,43 @@ +package ForkOnlyRegion { + item def Default; + item def Back; + item def Fork; + item def All; + item def FinishA; + item def FinishB; + item def FinishC; + + state Machine { + attribute log : String = ""; + entry; then init; + state init; + state working parallel { + state left { + state a { entry { assign log := log + "a(entry) "; } } + transition first a accept FinishA then done; + } + state right { + state b { entry { assign log := log + "b(entry) "; } } + transition first b accept FinishB then done; + } + state third { + state c { entry { assign log := log + "c(entry) "; } } + transition first c accept FinishC then done; + } + } + state finished { entry { assign log := log + "finished(entry) "; } } + fork split; + fork all; + + transition first init accept Default then working; + transition first init accept Fork then split; + transition first init accept All then all; + transition first working accept Back then init; + transition first working then finished; + transition first split then a; + transition first split then b; + transition first all then a; + transition first all then b; + transition first all then c; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.trace.golden b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.trace.golden new file mode 100644 index 0000000000..ceb26ef86f --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_fork_only_region_entered_by_default.trace.golden @@ -0,0 +1,11 @@ +exit: init +enter: working +transition: init -> working (event: accept Default) +exit: working +enter: finished (entry action) +stmt action body + stmt assign log + eval feature log -> "" + eval literal "finished(entry) " -> "finished(entry) " + eval operator + -> "finished(entry) " +transition: working -> finished diff --git a/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.expected.json b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.expected.json new file mode 100644 index 0000000000..4a0ce11463 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.expected.json @@ -0,0 +1,12 @@ +{ + "type": "state", + "events": [ + {"signal": "Enter"}, + {"signal": "Go"} + ], + "finalState": "finished", + "outputs": { + "log": {"type": "String", "value": "A1(entry);finished(entry);"} + }, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.sysml b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.sysml new file mode 100644 index 0000000000..96f0ed60c9 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.sysml @@ -0,0 +1,24 @@ +package HistoryFirstEntryInactiveRegion { + attribute def Enter; + attribute def Go; + + state Machine { + attribute log : String = ""; + entry; then idle; + state idle; + state P parallel { + history resume; + state a { + entry; then A1; + state A1 { entry { assign log := log + "A1(entry);"; } } + transition first A1 accept Go then done; + } + state b { + state B1 { entry { assign log := log + "B1(entry);"; } } + } + } + state finished { entry { assign log := log + "finished(entry);"; } } + transition first idle accept Enter then P::resume; + transition first P then finished; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.trace.golden b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.trace.golden new file mode 100644 index 0000000000..c5bb9dd149 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_first_entry_inactive_region.trace.golden @@ -0,0 +1,21 @@ +exit: idle +enter: P +enter: A1 (entry action) +stmt action body + stmt assign log + eval feature log -> "" + eval literal "A1(entry);" -> "A1(entry);" + eval operator + -> "A1(entry);" +transition: idle -> P (event: accept Enter) +exit: A1 +enter: done +transition: A1 -> done (event: accept Go) +exit: done +exit: P +enter: finished (entry action) +stmt action body + stmt assign log + eval feature log -> "A1(entry);" + eval literal "finished(entry);" -> "finished(entry);" + eval operator + -> "A1(entry);finished(entry);" +transition: P -> finished diff --git a/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.expected.json b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.expected.json new file mode 100644 index 0000000000..04b772411c --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.expected.json @@ -0,0 +1,14 @@ +{ + "type": "state", + "events": [ + {"signal": "Leave"}, + {"signal": "ReturnShallow"}, + {"signal": "Leave"}, + {"signal": "ReturnDeep"} + ], + "finalState": "active+b", + "outputs": { + "log": {"type": "String", "value": "A(entry) A(exit) A(entry) A(exit) A(entry) "} + }, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.sysml b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.sysml new file mode 100644 index 0000000000..1bcd579e60 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.sysml @@ -0,0 +1,34 @@ +package HistoryInactiveRegion { + item def Leave; + item def ReturnShallow; + item def ReturnDeep; + + state Machine { + attribute log : String = ""; + entry; then working; + state working parallel { + state a { + entry; then active; + state active { + entry { assign log := log + "A(entry) "; } + exit { assign log := log + "A(exit) "; } + } + } + state b { + state inactive { + entry { assign log := log + "B(entry) "; } + exit { assign log := log + "B(exit) "; } + } + } + history shallow; + deep history deep; + } + state outside; + state completed; + + transition first active when Leave then outside; + transition first outside when ReturnShallow then shallow; + transition first outside when ReturnDeep then deep; + transition first working then completed; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.trace.golden b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.trace.golden new file mode 100644 index 0000000000..438bf3192b --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_history_restores_inactive_parallel_region.trace.golden @@ -0,0 +1,36 @@ +exit: active (exit action) +stmt action body + stmt assign log + eval feature log -> "A(entry) " + eval literal "A(exit) " -> "A(exit) " + eval operator + -> "A(entry) A(exit) " +exit: working +enter: outside +transition: active -> outside (event: accept Leave) +exit: outside +enter: working +enter: active (entry action) +stmt action body + stmt assign log + eval feature log -> "A(entry) A(exit) " + eval literal "A(entry) " -> "A(entry) " + eval operator + -> "A(entry) A(exit) A(entry) " +transition: outside -> working (event: accept ReturnShallow) +exit: active (exit action) +stmt action body + stmt assign log + eval feature log -> "A(entry) A(exit) A(entry) " + eval literal "A(exit) " -> "A(exit) " + eval operator + -> "A(entry) A(exit) A(entry) A(exit) " +exit: working +enter: outside +transition: active -> outside (event: accept Leave) +exit: outside +enter: working +enter: active (entry action) +stmt action body + stmt assign log + eval feature log -> "A(entry) A(exit) A(entry) A(exit) " + eval literal "A(entry) " -> "A(entry) " + eval operator + -> "A(entry) A(exit) A(entry) A(exit) A(entry) " +transition: outside -> working (event: accept ReturnDeep) diff --git a/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.expected.json b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.expected.json new file mode 100644 index 0000000000..8bddc56370 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.expected.json @@ -0,0 +1,8 @@ +{ + "type": "state", + "finalState": "a+inner+o2+b", + "outputs": { + "log": {"type": "String", "value": ""} + }, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.sysml b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.sysml new file mode 100644 index 0000000000..68de3fb5aa --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.sysml @@ -0,0 +1,23 @@ +package NestedForkInactiveRegion { + state Machine { + attribute log : String = ""; + entry; then init; + state init; + state outer parallel { + state o1 { + entry; then hold; + state hold; + state inner parallel { + state left { state a; } + state right { state b; } + } + fork split; + transition first split then a; + transition first split then b; + } + state o2 { state c { entry { assign log := log + "c(entry) "; } } } + } + + transition first init then split; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.trace.golden b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.trace.golden new file mode 100644 index 0000000000..2294e7f48f --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_nested_fork_starts_outer_region_inactive.trace.golden @@ -0,0 +1,7 @@ +exit: init +enter: outer +choice fork split: next split->a(effect), split->b(effect) (unordered; took split->a(effect) first) +enter: inner +enter: a +enter: b +transition: init -> split diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.expected.json b/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.expected.json new file mode 100644 index 0000000000..2f95d8f984 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.expected.json @@ -0,0 +1,9 @@ +{ + "type": "state", + "libraries": true, + "events": [ + {"signal": "Ping"}, + {"signal": "Leave"} + ], + "finalState": "done" +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.sysml b/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.sysml new file mode 100644 index 0000000000..30f559feff --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_deferred_event.sysml @@ -0,0 +1,35 @@ +// A parallel state owns a DeferredEvent annotation and the migrator's standard +// deferred-signal encoding. Ping is kept while Busy is active and replayed to +// Ready after Leave exits Busy. +package ParallelDeferredEvent { + item def Ping; + item def Leave; + + state def Machine { + entry; then start; + state start; + state Busy parallel { + @MigrationMetadata::DeferredEvent { ref :>> signal : Ping; } + item deferred : Ping[*] ordered; + do action buffer { + first start then receive; + action receive accept kept : Ping; + then action keep { assign deferred := SequenceFunctions::including(deferred, receive.kept); } + then receive; + } + exit action flush { + for kept in deferred { send kept to self; } + then action clear { assign deferred := (); } + } + state R { + entry; then active; + state active; + } + } + state Ready; + + succession first start then Busy; + transition first Busy accept Leave then Ready; + transition first Ready accept Ping then done; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.expected.json b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.expected.json new file mode 100644 index 0000000000..2c71894aaa --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.expected.json @@ -0,0 +1,8 @@ +{ + "type": "state", + "finalState": "done", + "outputs": { + "log": {"type": "String", "value": "S1(entry);S1(exit);"} + }, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.sysml b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.sysml new file mode 100644 index 0000000000..5a292087ef --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.sysml @@ -0,0 +1,18 @@ +package ParallelRegionCompletion { + private import ScalarValues::*; + + state Machine { + attribute log : String = ""; + entry; then S1; + // PSSM Entering 004: a region without an initial pseudostate completes on default entry. + state S1 parallel { + entry action { assign log := log + "S1(entry);"; } + exit action { assign log := log + "S1(exit);"; } + state r { + state S1_1 { entry action { assign log := log + "S1.1(entry);"; } } + transition first S1_1 then done; + } + } + transition first S1 then done; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.trace.golden b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.trace.golden new file mode 100644 index 0000000000..5ea5057ff1 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_completes.trace.golden @@ -0,0 +1,8 @@ +exit: S1 (exit action) +stmt action body + stmt assign log + eval feature log -> "S1(entry);" + eval literal "S1(exit);" -> "S1(exit);" + eval operator + -> "S1(entry);S1(exit);" +enter: done +transition: S1 -> done diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.check.expected.json b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.check.expected.json new file mode 100644 index 0000000000..16eb9068ab --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.check.expected.json @@ -0,0 +1,7 @@ +{ + "verdict": "no violation, exhaustive", + "agreed": { + "finalState": "outside", + "log": "\"A(entry) outside(entry) \"" + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.expected.json b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.expected.json new file mode 100644 index 0000000000..77faec7afd --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.expected.json @@ -0,0 +1,16 @@ +{ + "type": "state", + "events": [ + {"signal": "Go"}, + {"signal": "FinishA"}, + {"signal": "Back"}, + {"signal": "Direct"}, + {"signal": "FinishA"}, + {"signal": "FinishB"} + ], + "finalState": "outside", + "outputs": { + "log": {"type": "String", "value": "A(entry) outside(entry) "} + }, + "trace": true +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.sysml b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.sysml new file mode 100644 index 0000000000..9cb20b94d1 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.sysml @@ -0,0 +1,29 @@ +package InactiveParallelRegion { + item def Go; + item def Back; + item def Direct; + item def FinishA; + item def FinishB; + + state Machine { + attribute log : String = ""; + entry; then idle; + state idle; + state working parallel { + state a { + entry; then A; + state A { entry { assign log := log + "A(entry) "; } } + transition first A accept FinishA then done; + } + state b { + state B { entry { assign log := log + "B(entry) "; } } + transition first B accept FinishB then done; + } + } + state outside { entry { assign log := log + "outside(entry) "; } } + transition first idle accept Go then working; + transition first idle accept Direct then B; + transition first working accept Back then idle; + transition first working then outside; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.trace.golden b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.trace.golden new file mode 100644 index 0000000000..7477351412 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_region_without_entry_inactive.trace.golden @@ -0,0 +1,21 @@ +exit: idle +enter: working +enter: A (entry action) +stmt action body + stmt assign log + eval feature log -> "" + eval literal "A(entry) " -> "A(entry) " + eval operator + -> "A(entry) " +transition: idle -> working (event: accept Go) +exit: A +enter: done +transition: A -> done (event: accept FinishA) +exit: done +exit: working +enter: outside (entry action) +stmt action body + stmt assign log + eval feature log -> "A(entry) " + eval literal "outside(entry) " -> "outside(entry) " + eval operator + -> "A(entry) outside(entry) " +transition: working -> outside diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.expected.json b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.expected.json new file mode 100644 index 0000000000..3bad1c5645 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.expected.json @@ -0,0 +1,8 @@ +{ + "type": "state", + "trace": true, + "finalState": "finished", + "outputs": { + "log": {"type": "String", "value": "do1;do2;S(exit);finished(entry);"} + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.sysml b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.sysml new file mode 100644 index 0000000000..d6f0293474 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.sysml @@ -0,0 +1,25 @@ +package Test { + state Machine { + attribute log : String = ""; + + entry; then S; + state S parallel { + exit { assign log := log + "S(exit);"; } + state R { + do action work { + first start; + then action firstStep { assign log := log + "do1;"; } + then action secondStep { assign log := log + "do2;"; } + then done; + } + state A { + entry { assign log := log + "A(entry);"; } + } + } + } + state finished { + entry { assign log := log + "finished(entry);"; } + } + transition first S then finished; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.trace.golden b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.trace.golden new file mode 100644 index 0000000000..3c24f9bacd --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_parallel_stand_in_do_action_defers_completion.trace.golden @@ -0,0 +1,26 @@ +do: R +stmt action body +enter action node: state behavior work + stmt assign log + eval feature log -> "" + eval literal "do1;" -> "do1;" + eval operator + -> "do1;" +do: R + stmt assign log + eval feature log -> "do1;" + eval literal "do2;" -> "do2;" + eval operator + -> "do1;do2;" +leave action node: state behavior work +exit: S (exit action) +stmt action body + stmt assign log + eval feature log -> "do1;do2;" + eval literal "S(exit);" -> "S(exit);" + eval operator + -> "do1;do2;S(exit);" +enter: finished (entry action) +stmt action body + stmt assign log + eval feature log -> "do1;do2;S(exit);" + eval literal "finished(entry);" -> "finished(entry);" + eval operator + -> "do1;do2;S(exit);finished(entry);" +transition: S -> finished diff --git a/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.expected.json b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.expected.json new file mode 100644 index 0000000000..452970fed4 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.expected.json @@ -0,0 +1,10 @@ +{ + "type": "state", + "trace": true, + "events": [{"signal": "Step"}], + "finalState": "Other", + "outputs": { + "entries": {"type": "Integer", "value": 1}, + "exits": {"type": "Integer", "value": 1} + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.sysml b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.sysml new file mode 100644 index 0000000000..cefc153ec2 --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.sysml @@ -0,0 +1,27 @@ +package StateSingleRegionParallelWrapper { + private import ScalarValues::*; + + attribute def Step; + + state def Machine { + attribute entries : Integer = 0; + attribute exits : Integer = 0; + entry; then S; + + state S parallel { + entry { assign entries := entries + 1; } + exit { assign exits := exits + 1; } + + state R { + entry; then A; + state A; + state B; + transition first A accept Step then B; + transition first B then done; + } + } + + state Other; + transition first S then Other; + } +} diff --git a/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.trace.golden b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.trace.golden new file mode 100644 index 0000000000..e74363080d --- /dev/null +++ b/internal/exec/runtime/testdata/conformance/state_single_region_parallel_wrapper.trace.golden @@ -0,0 +1,15 @@ +exit: A +enter: B +transition: A -> B (event: accept Step) +exit: B +enter: done +transition: B -> done +exit: done +exit: S (exit action) +stmt action body + stmt assign exits + eval feature exits -> 0 + eval literal 1 -> 1 + eval operator + -> 1 +enter: Other +transition: S -> Other diff --git a/internal/ir/lower/calc_body.go b/internal/ir/lower/calc_body.go index 4d12dfba0c..910f54b410 100644 --- a/internal/ir/lower/calc_body.go +++ b/internal/ir/lower/calc_body.go @@ -117,8 +117,8 @@ func calcStep(member ast.Node, scope *symbols.Scope, resolver *resolve.Resolver) return Unsupported{Description: statedFlowKeyword(member), Node: member, Scope: scope}, true } return usageStatement(m, scope) - case *ast.Definition, *ast.Documentation, *ast.Comment, *ast.Import, *ast.Alias: - // Declares a member of the calculation, not a step of it. + case *ast.Definition, *ast.Documentation, *ast.Comment, *ast.TextualRepresentation, *ast.Import, *ast.Alias: + // An annotation or declaration describes the calculation, not a step of it. return nil, false case *ast.SuccessionEdge: return nil, false diff --git a/internal/ir/lower/entry_transition.go b/internal/ir/lower/entry_transition.go index 0443b40238..4c1f928af5 100644 --- a/internal/ir/lower/entry_transition.go +++ b/internal/ir/lower/entry_transition.go @@ -89,10 +89,14 @@ func (g *StateGraph) UnconditionalStart(owner ast.Node) *ast.StateNode { if len(transitions) == 0 || transitions[0].Guard != nil { return nil } - if transitions[0].Via != nil { - return g.unconditionalEntryTarget(transitions[0].Via, nil) + switch target := entryTransitionTarget(transitions[0]).(type) { + case *ast.StateNode: + return target + case *ast.PseudostateNode: + return g.unconditionalEntryTarget(target, nil) + default: + return nil } - return transitions[0].Target } func (g *StateGraph) unconditionalEntryTarget(target ast.Node, seen map[*ast.PseudostateNode]bool) *ast.StateNode { diff --git a/internal/ir/lower/fork_plan.go b/internal/ir/lower/fork_plan.go index 76db769bfe..062e9da33f 100644 --- a/internal/ir/lower/fork_plan.go +++ b/internal/ir/lower/fork_plan.go @@ -114,148 +114,11 @@ func (g *StateGraph) enclosingRegion(state *ast.StateNode) *ast.StateRegion { return nil } -// ForkStarted reports whether a fork enters region, so it may start there -// without an entry transition of its own. +// ForkStarted reports whether a fork branch enters region. func (g *StateGraph) ForkStarted(region *ast.StateRegion) bool { return len(g.ForkEntered[region]) > 0 } -// checkForkOnlyRegion checks that a region without an entry transition is only -// ever started by a fork's branch: no other way into its composite state may -// leave the region to start by default. -func (g *StateGraph) checkForkOnlyRegion(owner *ast.StateNode, region *ast.StateRegion) error { - entry := g.defaultEntryInto(owner, region) - if entry == "" { - return nil - } - return fmt.Errorf("region %s in state %s has no initial state: only a fork's branch may start it, but %s enters %s without one; write `entry; then ;` inside the region", - region.Name, owner.Name, entry, owner.Name) -} - -// defaultEntryInto describes a way into owner that starts region by default, -// naming no state inside it, or is empty when only forks' branches enter owner. -func (g *StateGraph) defaultEntryInto(owner *ast.StateNode, region *ast.StateRegion) string { - for _, ps := range g.Pseudostates { - if plan := g.ForkPlans[ps]; plan != nil && plan.Owner == owner && plan.Branches[region] == nil { - return "fork " + ps.Name - } - } - for _, body := range g.entryBodies() { - if from := g.bodyState(body); from != nil && g.within(owner, from) { - continue - } - for _, t := range g.EntryTransitions[body] { - target := entryTransitionTarget(t) - if state := g.defaultStart(owner, region, nil, target, nil); state != nil { - return "the entry transition naming " + vertexName(state) - } - } - } - for _, source := range g.States { - for _, t := range g.Transitions[source] { - if target := g.defaultStart(owner, region, source, t.Target, nil); target != nil { - return fmt.Sprintf("the transition from %s to %s", source.Name, vertexName(target)) - } - } - } - return "" -} - -// defaultStart is the vertex a transition from source to target ends at when it -// enters owner in a way that starts region by default — owner itself or a state -// in another of its regions, reached from outside owner or from owner itself — -// following junctions, choices and joins to the states they lead to, and a fork -// to the composite state its branches enter or the states they end at; nil otherwise. -func (g *StateGraph) defaultStart(owner *ast.StateNode, region *ast.StateRegion, source *ast.StateNode, target ast.Node, seen map[ast.Node]bool) ast.Node { - switch v := target.(type) { - case *ast.StateNode: - if g.entersByDefault(owner, region, source, v) { - return v - } - case *ast.PseudostateNode: - switch v.Kind { - case ast.PseudostateShallowHistory, ast.PseudostateDeepHistory: - if g.entersByDefault(owner, region, source, g.PseudostateOwner[v]) { - return v - } - case ast.PseudostateFork: - if g.forkStartsByDefault(owner, region, source, v) { - return v - } - case ast.PseudostateJunction, ast.PseudostateChoice, ast.PseudostateJoin: - if seen[v] { - return nil - } - if seen == nil { - seen = map[ast.Node]bool{} - } - seen[v] = true - for _, t := range g.Transitions[v] { - if end := g.defaultStart(owner, region, source, t.Target, seen); end != nil { - return end - } - } - } - } - return nil -} - -// forkStartsByDefault reports whether fork's branches enter owner on their way to -// a state below it, or end at owner or in another of its regions, from source. -func (g *StateGraph) forkStartsByDefault(owner *ast.StateNode, region *ast.StateRegion, source *ast.StateNode, fork *ast.PseudostateNode) bool { - plan := g.ForkPlans[fork] - switch { - case plan == nil, plan.Owner == owner: - return false - case g.within(owner, plan.Owner): - return g.entersByDefault(owner, region, source, plan.Owner) - } - for _, branch := range plan.Branches { - if g.entersByDefault(owner, region, source, branch.Target.(*ast.StateNode)) { - return true - } - } - return false -} - -// entryBodies lists the bodies whose entry transitions are on record, in -// declaration order: the machine's own, then each state's and its regions'. -func (g *StateGraph) entryBodies() []ast.Node { - bodies := []ast.Node{nil} - for _, state := range g.States { - bodies = append(bodies, state) - for _, region := range g.CompositeStates[state] { - bodies = append(bodies, region) - } - } - return bodies -} - -// bodyState is the state a body's entry transitions start inside: the state -// itself, a region's owner, nil for the machine's own body. -func (g *StateGraph) bodyState(body ast.Node) *ast.StateNode { - switch b := body.(type) { - case *ast.StateNode: - return b - case *ast.StateRegion: - return g.RegionOwner[b] - } - return nil -} - -// entersByDefault reports whether a move from source to target starts region by -// default: target is owner or lies in another of its regions, and the move comes -// from outside owner or from owner itself, so the whole of owner is entered. -func (g *StateGraph) entersByDefault(owner *ast.StateNode, region *ast.StateRegion, source, target *ast.StateNode) bool { - if target == nil || !g.within(owner, target) { - return false - } - if source != owner && g.within(owner, source) && target != owner { - return false - } - return g.regionUnder(owner, target) != region -} - // within reports whether state is owner or lies below it. func (g *StateGraph) within(owner, state *ast.StateNode) bool { for s := state; s != nil; s = g.ParentState[s] { diff --git a/internal/ir/lower/fork_plan_test.go b/internal/ir/lower/fork_plan_test.go index 61af9c0cce..cb81ef5c7b 100644 --- a/internal/ir/lower/fork_plan_test.go +++ b/internal/ir/lower/fork_plan_test.go @@ -191,9 +191,9 @@ func TestToStateGraph_ForkBranchesNestedInOneRegionFail(t *testing.T) { } } -// A region with neither an entry transition nor a fork branch has no way in. -func TestToStateGraph_RegionWithoutInitialOrForkFails(t *testing.T) { - _, err := ToStateGraph(stateDefinitionIn(t, ` +// A region with neither an entry transition nor a fork branch stays inactive. +func TestToStateGraph_RegionWithoutInitialOrForkRemainsInactive(t *testing.T) { + graph, err := ToStateGraph(stateDefinitionIn(t, ` package test { state def Machine { entry; then idle; @@ -210,61 +210,50 @@ func TestToStateGraph_RegionWithoutInitialOrForkFails(t *testing.T) { } } `), nil) - if err == nil { - t.Fatal("region no fork enters succeeded") - } - if !strings.Contains(err.Error(), "region third has no initial state") { - t.Fatalf("error = %q, want missing initial for third", err) + if err != nil { + t.Fatalf("ToStateGraph: %v", err) } + requireRegionInitial(t, graph, "work", "third", false, false) } -// A region only a fork enters must be entered only by that fork: any other way -// into its composite state would start the region by default, which it cannot. -func TestToStateGraph_ForkOnlyRegionEnteredByDefaultFails(t *testing.T) { - for name, tc := range map[string]struct{ entry, extra, want string }{ +// A fork-only region stays inactive on default entry, whatever names the move. +func TestToStateGraph_ForkOnlyRegionEnteredByDefaultLeavesItInactive(t *testing.T) { + for name, tc := range map[string]struct{ entry, extra string }{ "transition into the composite state": { `entry; then idle;`, `transition first idle accept Go then work;`, - "the transition from idle to work enters work", }, "transition into the other region": { `entry; then idle;`, `transition first idle accept Go then b;`, - "the transition from idle to b enters work", }, "self-transition of the composite state": { `entry; then idle;`, `transition first work accept Go then work;`, - "the transition from work to work enters work", }, "transition into the composite state's history": { `entry; then idle;`, `transition first idle accept Go then back;`, - "the transition from idle to back enters work", }, "entry transition naming the composite state": { `entry; then work;`, ``, - "the entry transition naming work", }, "entry transition naming a state in the other region": { `entry; then b;`, ``, - "the entry transition naming b", }, "guarded entry transition naming a state in the other region": { `entry; if false then idle; then b;`, ``, - "the entry transition naming b", }, "junction outside leading into the other region": { `entry; then idle;`, `junction j; transition first idle accept Go then j; transition first j then b;`, - "the transition from idle to b enters work", }, } { t.Run(name, func(t *testing.T) { - _, err := ToStateGraph(stateDefinitionIn(t, ` + graph, err := ToStateGraph(stateDefinitionIn(t, ` package test { attribute def Go; state def Machine { @@ -283,19 +272,16 @@ func TestToStateGraph_ForkOnlyRegionEnteredByDefaultFails(t *testing.T) { } } `), nil) - if err == nil { - t.Fatal("default entry into a fork-only region succeeded") - } - if !strings.Contains(err.Error(), "region left in state work has no initial state") || !strings.Contains(err.Error(), tc.want) { - t.Fatalf("error = %q, want %q", err, tc.want) + if err != nil { + t.Fatalf("ToStateGraph: %v", err) } + requireRegionInitial(t, graph, "work", "left", false, true) }) } } -// An enclosing state's entry transition naming a state in the other region enters -// the composite state from outside it too; the other region's own is fine. -func TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryFails(t *testing.T) { +// An enclosing entry may enter a sibling region while the fork-only region stays inactive. +func TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryLeavesItInactive(t *testing.T) { machine := func(outerEntry, rightEntry string) string { return ` package test { @@ -320,18 +306,21 @@ func TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryFails(t *testing.T) } ` } - _, err := ToStateGraph(stateDefinitionIn(t, machine(`entry; then b;`, ``)), nil) - if err == nil || !strings.Contains(err.Error(), "the entry transition naming b") { - t.Fatalf("outer's entry naming b: error = %v, want the entry transition naming b", err) + graph, err := ToStateGraph(stateDefinitionIn(t, machine(`entry; then b;`, ``)), nil) + if err != nil { + t.Fatalf("outer entry naming b: %v", err) } - if _, err := ToStateGraph(stateDefinitionIn(t, machine(`entry; then start;`, `entry; then b;`)), nil); err != nil { + requireRegionInitial(t, graph, "work", "left", false, true) + graph, err = ToStateGraph(stateDefinitionIn(t, machine(`entry; then start;`, `entry; then b;`)), nil) + if err != nil { t.Fatalf("right's own entry naming b: %v", err) } + requireRegionInitial(t, graph, "work", "left", false, true) + requireRegionInitial(t, graph, "work", "right", true, true) } -// A fork that omits a region starts it by default, so a region another fork -// alone enters is refused; with an entry transition the omission is fine. -func TestToStateGraph_ForkOnlyRegionOmittedByAnotherForkFails(t *testing.T) { +// A fork that omits a region leaves it inactive, even when another fork enters it. +func TestToStateGraph_ForkOmissionLeavesRegionInactive(t *testing.T) { machine := func(third string) string { return ` package test { @@ -356,22 +345,21 @@ func TestToStateGraph_ForkOnlyRegionOmittedByAnotherForkFails(t *testing.T) { } ` } - _, err := ToStateGraph(stateDefinitionIn(t, machine(``)), nil) - if err == nil || !strings.Contains(err.Error(), "region left in state work has no initial state") || !strings.Contains(err.Error(), "fork f2 enters work") { - t.Fatalf("f2 omitting left: error = %v, want fork f2 enters work", err) + graph, err := ToStateGraph(stateDefinitionIn(t, machine(``)), nil) + if err != nil { + t.Fatalf("f2 omitting left: %v", err) } - _, err = ToStateGraph(stateDefinitionIn(t, machine(`entry; then c;`)), nil) - if err == nil || !strings.Contains(err.Error(), "region left in state work has no initial state") { - t.Fatalf("third with its own entry: error = %v, want left still refused", err) + requireRegionInitial(t, graph, "work", "left", false, true) + graph, err = ToStateGraph(stateDefinitionIn(t, machine(`entry; then c;`)), nil) + if err != nil { + t.Fatalf("third with its own entry: %v", err) } + requireRegionInitial(t, graph, "work", "left", false, true) + requireRegionInitial(t, graph, "work", "third", true, true) } -// A fork's branches enter every composite state above their targets, so a fork -// into a nested composite state starts an outer sibling region by default, and a -// fork above a composite state whose branch names it starts the regions of that -// state the nearer fork alone enters. A fork reached from inside the outer state -// leaves its other region as it is. -func TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails(t *testing.T) { +// Nested forks leave every omitted region inactive until a branch or transition enters it. +func TestToStateGraph_NestedForkLeavesUnenteredRegionsInactive(t *testing.T) { machine := func(o2Entry, leftEntry, routes string) string { return ` package test { @@ -401,19 +389,21 @@ func TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails(t *testing.T) { } ` } - _, err := ToStateGraph(stateDefinitionIn(t, machine(``, ``, + graph, err := ToStateGraph(stateDefinitionIn(t, machine(``, ``, `transition first idle then split; transition first split2 then hold; transition first idle accept Go then split2;`)), nil) - if err == nil || !strings.Contains(err.Error(), "region o2 in state outer has no initial state") || !strings.Contains(err.Error(), "the transition from idle to split enters outer") { - t.Fatalf("nested fork from outside: error = %v, want the transition from idle to split", err) + if err != nil { + t.Fatalf("nested fork from outside: %v", err) } - _, err = ToStateGraph(stateDefinitionIn(t, machine(``, `entry; then a;`, + requireRegionInitial(t, graph, "outer", "o2", false, true) + graph, err = ToStateGraph(stateDefinitionIn(t, machine(``, `entry; then a;`, `transition first idle then split2; transition first split2 then inner;`)), nil) - if err == nil || !strings.Contains(err.Error(), "region right in state inner has no initial state") || !strings.Contains(err.Error(), "the transition from idle to split2 enters inner") { - t.Fatalf("outer fork naming inner: error = %v, want the transition from idle to split2", err) + if err != nil { + t.Fatalf("outer fork naming inner: %v", err) } + requireRegionInitial(t, graph, "inner", "right", false, true) if _, err := ToStateGraph(stateDefinitionIn(t, machine(``, ``, `transition first idle then split2; transition first split2 then hold;`)), nil); err != nil { @@ -421,6 +411,24 @@ func TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails(t *testing.T) { } } +func requireRegionInitial(t *testing.T, graph *StateGraph, ownerName, regionName string, wantStart, wantFork bool) { + t.Helper() + for region, start := range graph.RegionInitials { + owner := graph.RegionOwner[region] + if owner == nil || owner.Name != ownerName || region.Name != regionName { + continue + } + if (start != nil) != wantStart { + t.Fatalf("region %s.%s start = %v, want initial present %t", ownerName, regionName, start, wantStart) + } + if graph.ForkStarted(region) != wantFork { + t.Fatalf("region %s.%s fork-started = %t, want %t", ownerName, regionName, graph.ForkStarted(region), wantFork) + } + return + } + t.Fatalf("region %s.%s not recorded", ownerName, regionName) +} + // A transition that names a state inside the fork-only region, or one that stays // inside the composite state — directly or through a junction declared there — // starts no region by default and is accepted. diff --git a/internal/ir/lower/state_graph.go b/internal/ir/lower/state_graph.go index 0f956aac40..a8b1e4171d 100644 --- a/internal/ir/lower/state_graph.go +++ b/internal/ir/lower/state_graph.go @@ -130,7 +130,7 @@ type StateGraph struct { // selection of an active composite owner. CompositeStateOrder []*ast.StateNode - // RegionInitials: region → initial state + // RegionInitials: region → initial state, nil when default entry leaves it inactive. RegionInitials map[*ast.StateRegion]*ast.StateNode // ParentState: child → parent @@ -163,8 +163,7 @@ type StateGraph struct { // ForkPlans: fork → where its branches lead, checked when the graph is built. ForkPlans map[*ast.PseudostateNode]*ForkPlan - // ForkEntered: region → the forks whose branches enter it, in declaration - // order. A region only forks enter needs no entry transition of its own. + // ForkEntered: region → the forks whose branches enter it, in declaration order. ForkEntered map[*ast.StateRegion][]*ast.PseudostateNode // JoinPlans: join → where its segments come from, checked when the graph is built. @@ -410,9 +409,8 @@ func (g *StateGraph) collectRegions(body []inheritedMember) { } } -// recordRegionInitials records where every region starts and refuses one that -// nothing starts: a top-level region needs an entry transition, a composite -// state's region an entry transition or a fork branch into it. +// recordRegionInitials records where every region starts, refusing only a +// top-level region without an entry transition. func (g *StateGraph) recordRegionInitials() error { for _, region := range g.TopRegions { g.RegionInitials[region] = g.UnconditionalStart(region) @@ -423,14 +421,10 @@ func (g *StateGraph) recordRegionInitials() error { for _, state := range g.CompositeStateOrder { for _, region := range g.CompositeStates[state] { g.RegionInitials[region] = g.UnconditionalStart(region) - if len(g.EntryTransitions[region]) > 0 || g.stateless(region) { - continue - } - if !g.ForkStarted(region) { - return g.noInitialState(region, fmt.Sprintf("region %s in state %s", region.Name, state.Name)) - } - if err := g.checkForkOnlyRegion(state, region); err != nil { - return err + if len(g.EntryTransitions[region]) == 0 && !g.stateless(region) { + if wrapper := g.RegionState[region]; wrapper != nil && g.parallelState[wrapper] { + return g.noInitialState(region, "") + } } } } @@ -1003,6 +997,18 @@ func collectStateContents(graph *StateGraph, state *ast.StateNode, scope *symbol return nil } +// recordCompositeState registers a state's regions while retaining their +// declaration order for deterministic consumers. +func (g *StateGraph) recordCompositeState(state *ast.StateNode) { + if _, exists := g.CompositeStates[state]; !exists { + g.CompositeStateOrder = append(g.CompositeStateOrder, state) + } + g.CompositeStates[state] = state.Regions + for _, region := range state.Regions { + g.RegionOwner[region] = state + } +} + // stateless reports whether region is stood for by a state declaring no substates // (behaviors and transitions are not states): such a region // starts in, and stays in, that state, so it needs no initial. @@ -1027,18 +1033,6 @@ func (g *StateGraph) stateless(region *ast.StateRegion) bool { return true } -// recordCompositeState registers a state's regions while retaining their -// declaration order for deterministic consumers. -func (g *StateGraph) recordCompositeState(state *ast.StateNode) { - if _, exists := g.CompositeStates[state]; !exists { - g.CompositeStateOrder = append(g.CompositeStateOrder, state) - } - g.CompositeStates[state] = state.Regions - for _, region := range state.Regions { - g.RegionOwner[region] = state - } -} - // stateScope returns the scope state's body was declared in, given the scope of // the body that declares it. A state lowering synthesized from a usage or a bare // substate declaration is keyed in the scope tree by that declaration. @@ -1142,15 +1136,16 @@ func isParallelRegionMember(member ast.Node) bool { // parallelOwnedMember reports whether a parallel state may own a member itself // rather than contribute it to a region: its behaviors, the // pseudostates its regions branch through, the edges between them, a metadata -// usage annotating it, and a definition written in its body, which declares a -// type rather than a region. +// usage or prefix annotation annotating it, and a definition written in its +// body, which declares a type rather than a region. func parallelOwnedMember(member ast.Node) bool { switch n := member.(type) { case *ast.Comment, *ast.Documentation, *ast.TextualRepresentation, + *ast.PrefixMetadata, *ast.EntryMember, *ast.DoMember, *ast.ExitMember, *ast.PseudostateNode, *ast.SuccessionEdge, *ast.TransitionEdge, *ast.TransitionMember, - *ast.PrefixMetadata, *ast.Definition, *ast.Package, *ast.ErrorNode: + *ast.Definition, *ast.Package, *ast.ErrorNode: return true case *ast.Usage: switch n.Kind { diff --git a/internal/ir/lower/state_metadata_test.go b/internal/ir/lower/state_metadata_test.go index c8e3895ee7..d11214df98 100644 --- a/internal/ir/lower/state_metadata_test.go +++ b/internal/ir/lower/state_metadata_test.go @@ -180,3 +180,46 @@ func TestMetadataLocalAliasSpelling(t *testing.T) { t.Fatalf("pseudostates are %v, want [choice:pick]", got) } } + +func TestDeferredSignalsOnParallelState(t *testing.T) { + src := `package M { + item def Ping; + state m parallel { + @MigrationMetadata::DeferredEvent { ref :>> signal : Ping; } + state r { + entry; then active; + state active; + } + } + }` + p := parser.New(source.New("m.sysml", []byte(src))) + root := p.ParseFile() + if len(p.Diagnostics) > 0 { + t.Fatalf("parse errors: %v", p.Diagnostics) + } + idx := libs.NewModelIndex() + idx.AddDocument("m.sysml", root) + idx.ExpandWildcardImports() + pkg, ok := idx.DocumentRoot("m.sysml").LookupLocal("M") + if !ok { + t.Fatal("package M not indexed") + } + sym, ok := pkg.Scope.LookupLocal("m") + if !ok { + t.Fatal("state m not indexed") + } + usage, ok := sym.Decl.(*ast.Usage) + if !ok { + t.Fatalf("state m declaration is %T, want *ast.Usage", sym.Decl) + } + deferred := DeferredSignals(resolve.New(idx), sym.Scope, usage) + if len(deferred) != 1 { + t.Fatalf("DeferredSignals = %v, want one signal", deferred) + } + if got := deferred[0].Type.Text(); got != "Ping" { + t.Fatalf("deferred signal type = %q, want Ping", got) + } + if deferred[0].Scope == nil { + t.Fatal("deferred signal has no state body scope") + } +} diff --git a/internal/translate/migrate/activity.go b/internal/translate/migrate/activity.go index b9daac3b00..616190f7cb 100644 --- a/internal/translate/migrate/activity.go +++ b/internal/translate/migrate/activity.go @@ -2497,17 +2497,25 @@ func (a *activity) unassigned(pin *sysmlv1.Element) bool { return pin.Parent != nil && pin.Parent.Type == "OpaqueAction" && !a.opaqueOf(pin.Parent).assigned[pin] } -// opaqueAction writes an opaque action as an action of assignments when its body is -// a sequence of them, else with the body kept as a comment; its pins stay unwritten. +// opaqueAction writes translated assignments and keeps every opaque body as text. func (a *activity) opaqueAction(n *sysmlv1.Element, name string) { r := a.opaqueOf(n) a.m.w.block(actionKw+name, func() { a.pins(n, nil) + a.m.textualReps(n) if !r.ok { a.inert[n] = true - body, lang := opaqueBody(n) - a.m.opaqueComment(body, lang, r.note) - a.m.add(n, Approximated, name, "the body is kept as a comment: "+r.note) + if r.note == "the body is empty" && opaqueBodiesEmpty(n) { + a.m.add(n, Mapped, name, "its body is empty, so it is written as an action that does nothing") + return + } + a.m.opaqueBodyComments(n, r.note) + _, lang := opaqueBody(n) + keptAs := "a textual representation, which is not executed" + if lang == "" { + keptAs = "a comment" + } + a.m.add(n, Approximated, name, "the body is kept as "+keptAs+": "+r.note) return } a.m.w.lines(r.lines) diff --git a/internal/translate/migrate/behavior.go b/internal/translate/migrate/behavior.go index 07dbca20d1..7849b9f571 100644 --- a/internal/translate/migrate/behavior.go +++ b/internal/translate/migrate/behavior.go @@ -985,24 +985,32 @@ func (m *migration) calcBody(e *sysmlv1.Element) { m.parameters(e, e) expr, _, _, translated := m.calcExprHow(e) _, lang := opaqueBody(e) + m.textualReps(e) m.w.line(expr) if lang != "" && !translated { m.downgrade(e, "the "+lang+" body is written verbatim as the result expression, since it is also v2 expression syntax") } } -// opaqueBehaviorBody writes an opaque or function behavior's body that is no single -// expression: as assignments when every statement is one, else as a comment. +// opaqueBehaviorBody writes assignments when possible and keeps every body as text. func (m *migration) opaqueBehaviorBody(e, scope *sysmlv1.Element) { m.unwrittenMembers(e) + m.textualReps(e) body, lang := opaqueBody(e) lines, ok, note := m.statements(body, lang, scope) if !ok { + if note == "the body is empty" && opaqueBodiesEmpty(e) { + return + } if r := m.resultRefusal(e); r != "" && r != note { note = "as the result expression, " + r + "; as statements, " + note } - m.opaqueComment(body, lang, note) - m.downgrade(e, "the body is kept as a comment: "+note) + m.opaqueBodyComments(e, note) + keptAs := "a textual representation, which is not executed" + if lang == "" { + keptAs = "a comment" + } + m.downgrade(e, "the body is kept as "+keptAs+": "+note) return } name := m.freshName(scope, "body") @@ -1014,6 +1022,22 @@ func (m *migration) opaqueBehaviorBody(e, scope *sysmlv1.Element) { } } +func (m *migration) textualReps(e *sysmlv1.Element) { + for _, pair := range opaqueBodyPairs(e) { + if strings.TrimSpace(pair.body) != "" && pair.language != "" { + m.w.lines(prefixFirst("rep language "+stringLiteral(pair.language)+" ", opaqueRepresentationCommentLines(pair.body))) + } + } +} + +func (m *migration) opaqueBodyComments(e *sysmlv1.Element, note string) { + for _, pair := range opaqueBodyPairs(e) { + if pair.body != "" && pair.language == "" { + m.opaqueComment(pair.body, "", note) + } + } +} + // opaqueComment keeps an opaque body the mapping cannot write as a comment. func (m *migration) opaqueComment(body, lang, note string) { text := "body not migrated" diff --git a/internal/translate/migrate/diagrams_internal_test.go b/internal/translate/migrate/diagrams_internal_test.go index 850c8ec26c..92b1de30a5 100644 --- a/internal/translate/migrate/diagrams_internal_test.go +++ b/internal/translate/migrate/diagrams_internal_test.go @@ -151,7 +151,7 @@ func TestDiagramViews(t *testing.T) { `, diagram("_d", "Shutting", "_shutting", "SysML Activity Diagram", "_pump"), - []string{"action def Shut {\n view Shutting {\n expose Sys::Pump;\n render Views::asTextualNotation;\n }\n /* body not migrated"}, Mapped, + []string{"action def Shut {\n view Shutting {\n expose Sys::Pump;\n render Views::asTextualNotation;\n }\n rep language \"JavaScript\" /* 1;*/"}, Mapped, "its owner OpaqueBehavior Valve::Shutting is written as the body of action def Valve::Shut, whose method it is"}, {"a diagram owned by an action node is written in the body of the node's activity", ` @@ -250,7 +250,7 @@ func TestDiagramViews(t *testing.T) { `, diagram("_d", "Busy", "_busy", "SysML State Machine Diagram", "_read"), - []string{"state def Modes {\n view 'Busy 2' : StandardViewDefinitions::StateTransitionView {\n expose Modes;\n render Views::asInterconnectionDiagram;\n }\n /* the region has no initial pseudostate: nothing enters it */\n state Busy {\n /* the region has no initial pseudostate: nothing enters it */\n state Read;\n }\n}"}, + []string{"state def Modes {\n view 'Busy 2' : StandardViewDefinitions::StateTransitionView {\n expose Modes;\n render Views::asInterconnectionDiagram;\n }\n /* the region has no initial pseudostate: nothing enters it */\n state Busy parallel {\n state region {\n /* the region has no initial pseudostate: nothing enters it */\n state Read;\n }\n metadata MigrationMetadata::SynthesizedName about region;\n }\n}"}, Approximated, "the view exposes state def Modes, whose graph the rendering draws with the 1 shown nodes and edges of it; its owner State Modes::::Busy has no v2 body; written in state def Modes"}, {"a state shown by another diagram is exposed as the member its state def declares", machineMembers, diff --git a/internal/translate/migrate/edges.go b/internal/translate/migrate/edges.go index a66527600c..16986dbb45 100644 --- a/internal/translate/migrate/edges.go +++ b/internal/translate/migrate/edges.go @@ -169,10 +169,10 @@ func (m *migration) wroteEdgeEnding(e, owner *sysmlv1.Element, keyword, name str } // edgePlaceIn locates a member declared in owner's body within the actions nest -// names; a lone region's members are its owner's, a method's its operation's. +// names; an inline region's members are its owner's, a method's its operation's. func (m *migration) edgePlaceIn(owner *sysmlv1.Element, nest []string, name string) edgePlace { for owner != nil && owner.Type == "Region" && owner.Role == "region" { - if _, parallel := m.parallel[owner]; parallel { + if m.regionWrittenAsState(owner) { break } owner = owner.Parent @@ -588,7 +588,7 @@ func (m *migration) drawsNode(el *sysmlv1.Element, f viewForm) bool { k := nodeKind(el) return el.Role == "node" && k != nodePin && k != nodeParam case "StateTransitionView": - return el.Role == "subvertex" || el.Type == "Region" && m.parallel[el] != "" + return el.Role == "subvertex" || el.Type == "Region" && m.regionWrittenAsState(el) } return false } diff --git a/internal/translate/migrate/migrate.go b/internal/translate/migrate/migrate.go index cafee1b7b3..1921599450 100644 --- a/internal/translate/migrate/migrate.go +++ b/internal/translate/migrate/migrate.go @@ -156,6 +156,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { concerns: map[*sysmlv1.Element]*concernInfo{}, concernLists: map[*sysmlv1.Element][]*sysmlv1.Element{}, taken: map[*sysmlv1.Element]map[string]bool{}, + regionStates: map[*sysmlv1.Element]string{}, takenBy: map[*sysmlv1.Element]map[string]*sysmlv1.Element{}, parallel: map[*sysmlv1.Element]string{}, exposed: map[*sysmlv1.Element]string{}, @@ -385,9 +386,10 @@ type migration struct { // opened holds the member names of each synthesized declaration being // written, outermost first; a reference written inside them avoids those names. opened []columnNames - // parallel names the parallel state each region of an orthogonal state is - // written in; a lone region is written inline and has no name of its own. - parallel map[*sysmlv1.Element]string + // regionStates marks regions written as state usages; orthogonal regions + // name their parallel host, while a wrapped single region has no host name. + regionStates map[*sysmlv1.Element]string + parallel map[*sysmlv1.Element]string // exposed notes, for each feature reached from outside its owner (through // a connector path, a slot or a redefinition), what reaches it. exposed map[*sysmlv1.Element]string diff --git a/internal/translate/migrate/names.go b/internal/translate/migrate/names.go index ea771c6a54..61e7bddb3d 100644 --- a/internal/translate/migrate/names.go +++ b/internal/translate/migrate/names.go @@ -162,6 +162,10 @@ func (m *migration) nameTaken(owner *sysmlv1.Element, name string) bool { return m.nameTakenBut(nil, owner, name) } +func (m *migration) nameTakenExcept(owner *sysmlv1.Element, name string, except *sysmlv1.Element) bool { + return m.nameTakenBut(except, owner, name) +} + // nameTakenBut is nameTaken disregarding e, whose own name the declaration // written for it is free to keep. func (m *migration) nameTakenBut(e, owner *sysmlv1.Element, name string) bool { @@ -288,8 +292,7 @@ func upperFirst(s string) string { } // segments returns the v2 qualified-name segments of an element: the names -// from the top-level declaration down, the root Model not being written. A -// lone region is its owner's body; one of several is a sub-state of a parallel state. +// from the top-level declaration down, the root Model not being written. // A connection point is a member of its owner, whichever region a tool listed it in; // a method is the body of its operation. func (m *migration) segments(e *sysmlv1.Element) []string { @@ -325,9 +328,20 @@ func (m *migration) path(e *sysmlv1.Element) []segment { if cur.Parent == nil && cur.Type == "Model" { break } + var wrappedPointRegion *sysmlv1.Element + if owner := pointOwner(cur); owner != nil && owner.Type == "State" { + if regions := m.populatedRegions(owner); len(regions) == 1 && + m.regionWrittenAsState(regions[0]) && m.regionStates[regions[0]] == "" { + wrappedPointRegion = regions[0] + } + } if cur.Type == "Region" && cur.Role == "region" { - if p, ok := m.parallel[cur]; ok { - segs = append([]segment{{name: p}, {name: m.nameFor(cur)}}, segs...) + if m.regionWrittenAsState(cur) { + if p := m.regionStates[cur]; p != "" { + segs = append([]segment{{name: p}, {name: m.nameFor(cur)}}, segs...) + } else { + segs = append([]segment{{name: m.nameFor(cur), feature: true, elem: cur}}, segs...) + } } continue } @@ -338,6 +352,9 @@ func (m *migration) path(e *sysmlv1.Element) []segment { if within, ok := m.nestedIn[cur]; ok { segs = append([]segment{{name: within, feature: true}}, segs...) } + if wrappedPointRegion != nil { + segs = append([]segment{{name: m.nameFor(wrappedPointRegion), feature: true, elem: wrappedPointRegion}}, segs...) + } } return segs } diff --git a/internal/translate/migrate/states.go b/internal/translate/migrate/states.go index cc9f824a52..a3aa9e6b6e 100644 --- a/internal/translate/migrate/states.go +++ b/internal/translate/migrate/states.go @@ -126,7 +126,7 @@ func (m *migration) stateMachineBody(sm *sysmlv1.Element) { for _, cp := range sm.Owned("connectionPoint") { m.connectionPoint(cp) } - m.regions(sm, m.populatedRegions(sm), false, func() { /* no extra nesting to write */ }) + m.regions(sm, m.populatedRegions(sm), false, func() {}, nil) m.writeRelocatedTransitions(sm) }) } @@ -199,10 +199,8 @@ func (m *migration) indexTransitions(sm *sysmlv1.Element) { walk(sm) } -// nameRegions names the vertices of the regions of a state machine or state: -// one region shares its owner's body, several become the sub-states of one -// parallel state, each with a body of its own; owner is the element whose body -// used names, nil for a body no v1 element owns. +// nameRegions names a machine or state's regions: a State's one region and +// several regions become state usages; a StateMachine's one stays inline. func (m *migration) nameRegions(regions []*sysmlv1.Element, owner *sysmlv1.Element, used map[string]bool) { if len(regions) > 1 { name := m.freshMember(owner, used, "regions") @@ -213,11 +211,27 @@ func (m *migration) nameRegions(regions []*sysmlv1.Element, owner *sysmlv1.Eleme rname, m.synthesized[r] = "region", true } m.names[r] = freshIn(inner, rname) - m.parallel[r] = name + m.regionStates[r] = name m.nameRegion(r, nil, inheritedStateNamesSet()) } return } + if len(regions) == 1 && owner.Type == "State" { + r := regions[0] + rname := m.nameOf(r) + if rname == "" { + rname, m.synthesized[r] = "region", true + } + m.names[r] = m.freshMemberExcept(owner, r, used, rname) + m.regionStates[r] = "" + inner := inheritedStateNamesSet() + m.namePoints(owner, inner) + m.nameRegion(r, nil, inner) + for _, pr := range pointRegions(owner) { + m.regionUsed[pr] = inner + } + return + } for _, r := range regions { m.nameRegion(r, owner, used) } @@ -366,10 +380,10 @@ func (m *migration) entryPointForm(v, owner *sysmlv1.Element) pointForm { note := "no transition leaves the entry point, so entering through it enters " + describe(owner) + " by its default entry; a transition to it is written to the state" withoutInitial, withoutEntry := m.regionsWithoutEntry(m.populatedRegions(owner)) if len(withoutInitial) > 0 { - note += "; no initial pseudostate starts the " + pluralRegion(len(withoutInitial)) + " " + strings.Join(withoutInitial, ", ") + ", which v1 too leaves inactive on entering the state" + note += "; no initial pseudostate starts the state stand-in for " + pluralRegion(len(withoutInitial)) + " " + strings.Join(withoutInitial, ", ") + ", so its body has no active substate, as in v1" } if len(withoutEntry) > 0 { - note += "; no initial pseudostate of the " + pluralRegion(len(withoutEntry)) + " " + strings.Join(withoutEntry, ", ") + " enters it, so no entry is written for it" + note += "; no initial pseudostate starts the state stand-in for " + pluralRegion(len(withoutEntry)) + " " + strings.Join(withoutEntry, ", ") + ", so its body has no active substate, as in v1" } return pointForm{defaultEntry: true, note: note} } @@ -572,15 +586,26 @@ func (m *migration) vertexWritten(v *sysmlv1.Element) bool { return ok } -// regionWritten reports whether region r is written as a member of its own: a sub-state -// of the parallel state an orthogonal state's regions become, in a machine that is written. +// regionWritten reports whether r is written as a sub-state in a written machine. func (m *migration) regionWritten(r *sysmlv1.Element) bool { sm := machineOf(r) if sm == nil || !m.written(sm) { return false } m.nameMachine(sm) - return m.parallel[r] != "" + return m.regionWrittenAsState(r) +} + +// regionWrittenAsState reports whether r is a sub-state that supplies a +// region of a parallel state rather than an inline region body. +func (m *migration) regionWrittenAsState(r *sysmlv1.Element) bool { + _, ok := m.regionStates[r] + return ok +} + +func (m *migration) wrappedRegion(owner *sysmlv1.Element, regions []*sysmlv1.Element) bool { + return owner.Type == "State" && len(regions) == 1 && + m.regionWrittenAsState(regions[0]) && m.regionStates[regions[0]] == "" } // nameVertex gives a vertex that is written as a member its name in the body @@ -616,6 +641,15 @@ func (m *migration) freshMember(owner *sysmlv1.Element, used map[string]bool, ba return name } +func (m *migration) freshMemberExcept(owner, except *sysmlv1.Element, used map[string]bool, base string) string { + name := base + for i := 2; used[name] || m.nameTakenExcept(owner, name, except); i++ { + name = base + strconv.Itoa(i) + } + used[name] = true + return name +} + // vertexBase is the name a vertex of a kind that is written as a member takes // when anonymous; "" for a kind that is not. func vertexBase(v *sysmlv1.Element) string { @@ -628,16 +662,16 @@ func vertexBase(v *sysmlv1.Element) string { return k case "shallowHistory", "deepHistory": return "history" + case "terminate": + return "terminated" } } return "" } -// regions writes the regions of a state machine or composite state: one inline, -// several as the sub-states of one parallel state. The entry succession follows -// the body's entry action when entered says one was written; between writes -// the members that come after it and before the states. -func (m *migration) regions(owner *sysmlv1.Element, regions []*sysmlv1.Element, entered bool, between func()) { +// regions writes the regions of a state machine or composite state: a State's +// one region is a parallel sub-state, while one machine region stays inline. +func (m *migration) regions(owner *sysmlv1.Element, regions []*sysmlv1.Element, entered bool, between func(), wrapped func(*stateRegion)) { switch len(regions) { case 0: between() @@ -648,22 +682,21 @@ func (m *migration) regions(owner *sysmlv1.Element, regions []*sysmlv1.Element, } case 1: st := m.region(regions[0]) - st.enter(entered) + if wrapped == nil { + st.enter(entered) + between() + st.write() + return + } between() - st.write() + name := writeName(m.names[regions[0]]) + m.w.block(stateKw+name, func() { wrapped(st) }) + m.madeUp(regions[0], name) + state := writeName(m.nameFor(owner)) + m.add(regions[0], Mapped, name, "the region is written as the sub-state "+name+" of the parallel state "+state+", as a composite state's regions are") default: - name := m.parallel[regions[0]] - withoutInitial, withoutEntry := m.regionsWithoutEntry(regions) - switch { - case len(withoutInitial) == 0 && len(withoutEntry) == 0: - m.w.line(entryThen(entered, "", writeName(name))) - case len(withoutEntry) == 0: - m.w.lines(commentLines("no default entry: the " + pluralRegion(len(withoutInitial)) + " " + strings.Join(withoutInitial, ", ") + - " have no initial pseudostate, so only a fork or a transition naming a nested state enters the regions")) - default: - m.w.lines(commentLines("no default entry: these regions have no written entry: " + strings.Join(slices.Concat(withoutInitial, withoutEntry), ", ") + - "; only a fork or a transition naming a nested state enters them")) - } + name := m.regionStates[regions[0]] + m.w.line(entryThen(entered, "", writeName(name))) between() m.w.block(stateKw+writeName(name)+" parallel", func() { for _, r := range regions { @@ -682,22 +715,6 @@ func (m *migration) regions(owner *sysmlv1.Element, regions []*sysmlv1.Element, } } -// regionsWithoutEntry names the regions nothing enters: those no initial pseudostate -// starts, and apart those whose initial pseudostates enter other regions or nothing. -func (m *migration) regionsWithoutEntry(regions []*sysmlv1.Element) (withoutInitial, withoutEntry []string) { - for _, r := range regions { - e := m.regionEntry(r) - switch { - case e.init != nil: - case len(e.strays) == 0 && len(e.dangling) == 0: - withoutInitial = append(withoutInitial, describe(r)) - default: - withoutEntry = append(withoutEntry, describe(r)) - } - } - return withoutInitial, withoutEntry -} - // regionEntry is what enters a region: the initial pseudostate written as its // entry — one of the region's own whose transition enters it, or one a stray // initial of an orthogonal region donates, its donor — with the region's further @@ -896,10 +913,25 @@ func pluralRegion(n int) string { return "regions" } +// regionsWithoutEntry names regions no initial pseudostate enters. +func (m *migration) regionsWithoutEntry(regions []*sysmlv1.Element) (withoutInitial, withoutEntry []string) { + for _, r := range regions { + e := m.regionEntry(r) + switch { + case e.init != nil: + case len(e.strays) == 0 && len(e.dangling) == 0: + withoutInitial = append(withoutInitial, describe(r)) + default: + withoutEntry = append(withoutEntry, describe(r)) + } + } + return withoutInitial, withoutEntry +} + // region prepares to write region r of the machine that nameMachine named. func (m *migration) region(r *sysmlv1.Element) *stateRegion { s := &stateRegion{m: m, r: r, owner: r.Parent, used: m.regionUsed[r], machine: machineOf(r)} - if _, parallel := m.parallel[r]; parallel { + if m.regionWrittenAsState(r) { s.owner = nil } return s @@ -972,7 +1004,9 @@ func (s *stateRegion) write() { } } s.m.writeComments(s.r, false) - s.m.add(s.r, Mapped, "", "the one region is written as the body of its owner") + if !s.m.regionWrittenAsState(s.r) { + s.m.add(s.r, Mapped, "", "the one region is written as the body of its owner") + } } // name returns the v2 name nameMachine gave a vertex. @@ -1169,7 +1203,10 @@ func (s *stateRegion) vertex(v *sysmlv1.Element) { s.m.madeUp(v, name) s.m.add(v, Mapped, name, "written as a `#StateMachines::"+kind+" state` "+historyWords[kind]) case "terminate": - s.m.add(v, Approximated, "done", "a terminate pseudostate ends the machine; a transition to it is written to done, which ends its region") + name := writeName(s.name(v)) + s.m.w.line(actionKw + name + " terminate;") + s.m.madeUp(v, name) + s.m.add(v, Mapped, name, "written as a terminate action, which ends the state machine's performance in every region") case "entryPoint", "exitPoint": if pointOwner(v).Type == "State" { // Written in the body of the state it belongs to. @@ -1245,6 +1282,10 @@ func (s *stateRegion) state(v *sysmlv1.Element) { defers := s.deferrals(v, do, exit) head := s.stateHead(v, name) regions := s.m.populatedRegions(v) + wrapped := s.m.wrappedRegion(v, regions) + if wrapped { + head += " parallel" + } inv := firstOwned(v, "stateInvariant") points := s.m.connectionPoints(v) pointRegs := pointRegions(v) @@ -1274,20 +1315,39 @@ func (s *stateRegion) state(v *sysmlv1.Element) { s.m.invariant(inv) } entered := entry != nil && s.m.inlineBehavior("entry action", entry, v) - between := s.betweenActions(v, do, exit, points, defers) + between := s.betweenActions(v, do, exit, defers) + if !wrapped && len(points) > 0 { + writePoints := between + between = func() { + writePoints() + s.m.statePoints(v) + } + } if len(regions) == 0 { between() } else { - s.m.regions(v, regions, entered, between) + var writeWrapped func(*stateRegion) + if wrapped { + writeWrapped = func(region *stateRegion) { + s.m.statePoints(v) + region.enter(false) + region.write() + s.pointTransitions(regions, pointRegs) + s.m.writeRelocatedTransitions(v) + } + } + s.m.regions(v, regions, entered, between, writeWrapped) + } + if !wrapped { + s.pointTransitions(regions, pointRegs) + s.m.writeRelocatedTransitions(v) } - s.pointTransitions(regions, pointRegs) - s.m.writeRelocatedTransitions(v) }) } // betweenActions is a state's block body after its entry action: its do and -// exit actions and its connection points. -func (s *stateRegion) betweenActions(v, do, exit *sysmlv1.Element, points []*sysmlv1.Element, defers *deferrals) func() { +// exit actions and the encoding of any deferred triggers. +func (s *stateRegion) betweenActions(v, do, exit *sysmlv1.Element, defers *deferrals) func() { return func() { switch { case defers.encoded(): @@ -1301,9 +1361,6 @@ func (s *stateRegion) betweenActions(v, do, exit *sysmlv1.Element, points []*sys s.m.inlineBehavior(exitAction, exit, v) } } - if len(points) > 0 { - s.m.statePoints(v) - } } } @@ -1862,10 +1919,9 @@ func (s *stateRegion) acceptsSignal(t, sig *sysmlv1.Element) bool { // transitionWritten reports whether transition t is one the output writes, so // it accepts its trigger or completes its source: its ends resolve as -// transitionEnds requires, and its target is one target names — a state of the -// machine, a final or terminate state of the transition's own region, a -// pseudostate with a v2 form, or a connection point reference into a -// submachine state. It decides as target does, noting nothing. +// transitionEnds requires, and its target is one target names — a state, final +// or terminate action, pseudostate with a v2 form, or connection point reference +// into a submachine state. It decides as target does. func (s *stateRegion) transitionWritten(t *sysmlv1.Element) bool { src, tgt := s.m.model.Ref(t, "source"), s.m.model.Ref(t, "target") internal := t.Attrs["kind"] == "internal" @@ -1890,7 +1946,7 @@ func (s *stateRegion) transitionWritten(t *sysmlv1.Element) bool { case "Pseudostate": switch pseudoKind(tgt) { case "terminate": - return true + return named(tgt) case "exitPoint": return s.m.points[tgt].why == "" && (named(tgt) || pointOwner(tgt).Type != "State") case "entryPoint": @@ -2230,13 +2286,15 @@ func (m *migration) writeInlineBody(kw string, b, owner *sysmlv1.Element, header case "OpaqueBehavior", "FunctionBehavior": body, lang := opaqueBody(b) lines, ok, note := m.statements(body, lang, b) + empty := !ok && note == "the body is empty" && opaqueBodiesEmpty(b) enclose(header, func() { m.comments(b) m.parameters(b, b) + m.textualReps(b) if ok { m.w.lines(lines) - } else { - m.opaqueComment(body, lang, note) + } else if !empty { + m.opaqueBodyComments(b, note) } if m.keeping != "" { m.w.line(m.keeping) @@ -2244,8 +2302,14 @@ func (m *migration) writeInlineBody(kw string, b, owner *sysmlv1.Element, header }) if ok { m.add(b, Approximated, m.v2Name(b), "the "+langName(lang)+" body is written as v2 assignments") + } else if empty { + m.add(b, Mapped, m.v2Name(b), "") } else { - m.add(b, Approximated, m.v2Name(b), "the body is kept as a comment: "+note) + keptAs := "a textual representation, which is not executed" + if lang == "" { + keptAs = "a comment" + } + m.add(b, Approximated, m.v2Name(b), "the body is kept as "+keptAs+": "+note) } return true } @@ -2340,7 +2404,8 @@ func (s *stateRegion) path(v *sysmlv1.Element) (string, bool) { if !ok || machineOf(v) != s.machine { return "", false } - if owner := memberOwner(v); owner == s.machine || owner == s.r && !s.ownerScope { + owner := memberOwner(v) + if owner == s.machine || owner == s.r && !s.ownerScope || s.pointInWrappedRegion(v) { return writeName(name), true } segs := s.m.segments(v) @@ -2351,6 +2416,12 @@ func (s *stateRegion) path(v *sysmlv1.Element) (string, bool) { return qualifiedStatePathWithSeparator(segs[len(s.m.segments(base)):], "."), true } +func (s *stateRegion) pointInWrappedRegion(v *sysmlv1.Element) bool { + return !s.ownerScope && s.r.Parent != nil && s.r.Parent.Type == "State" && + s.m.regionWrittenAsState(s.r) && s.m.regionStates[s.r] == "" && + pointOwner(v) == s.r.Parent +} + // qualifiedStatePathWithSeparator joins state path segments with the requested separator. func qualifiedStatePathWithSeparator(segs []string, separator string) string { parts := make([]string, len(segs)) @@ -2367,15 +2438,15 @@ func (s *stateRegion) endpoint(t, v *sysmlv1.Element, role string) (string, bool if !ok { return "", false } - if owner := memberOwner(v); owner != s.r && owner != s.machine { + if owner := memberOwner(v); owner != s.r && owner != s.machine && !s.pointInWrappedRegion(v) { s.m.add(t, Mapped, "", "the "+role+" "+describe(v)+" lies in another region and is named by its path "+p) } return p, true } // target names what a transition leads to: a state or pseudostate of the machine -// by its path, done for a final or terminate state of the region, or the entry -// state of a submachine state a connection point reference enters. +// by its path, done for a final state of the region, or the entry state of a +// submachine state a connection point reference enters. func (s *stateRegion) target(t, v *sysmlv1.Element) (string, bool) { if v == nil { return "", false @@ -2392,7 +2463,7 @@ func (s *stateRegion) target(t, v *sysmlv1.Element) (string, bool) { case "Pseudostate": switch pseudoKind(v) { case "terminate": - return "done", true + return s.endpoint(t, v, "target") case "exitPoint": if s.m.points[v].why != "" { return "", false @@ -2658,9 +2729,13 @@ func (m *migration) writeRelocatedTransitions(host *sysmlv1.Element) { return } s := m.region(regions[0]) - s.owner = host - s.ownerScope = true - if host.Type == "State" { + if m.wrappedRegion(host, regions) { + s.owner = host + } else { + s.owner = host + s.ownerScope = true + } + if host.Type == "State" && !m.wrappedRegion(host, regions) { s.used = m.stateUsed[host] } for _, transition := range transitions { diff --git a/internal/translate/migrate/values.go b/internal/translate/migrate/values.go index b7fca450a6..c676d5eb25 100644 --- a/internal/translate/migrate/values.go +++ b/internal/translate/migrate/values.go @@ -350,6 +350,58 @@ func opaqueBody(v *sysmlv1.Element) (body, lang string) { return body, lang } +type opaqueBodyPair struct { + body string + language string +} + +func opaqueBodyPairs(v *sysmlv1.Element) []opaqueBodyPair { + bodies := opaqueTexts(v, "body") + languages := opaqueTexts(v, "language") + n := max(len(bodies), len(languages)) + pairs := make([]opaqueBodyPair, n) + for i := range pairs { + if i < len(bodies) { + pairs[i].body = bodies[i] + } + if i < len(languages) { + pairs[i].language = languages[i] + } + } + return pairs +} + +func opaqueTexts(v *sysmlv1.Element, role string) []string { + owned := v.Owned(role) + if len(owned) > 0 { + texts := make([]string, len(owned)) + for i, e := range owned { + texts[i] = e.Text + if role == "language" { + texts[i] = strings.TrimSpace(texts[i]) + } + } + return texts + } + text := v.Attrs[role] + if role == "language" { + text = strings.TrimSpace(text) + } + if text != "" { + return []string{text} + } + return nil +} + +func opaqueBodiesEmpty(v *sysmlv1.Element) bool { + for _, pair := range opaqueBodyPairs(v) { + if strings.TrimSpace(pair.body) != "" { + return false + } + } + return true +} + // reference is one name an expression refers to: the segments of a qualified // name, then the members feature chains select from it (chain is true). A // chain on a name the expression's own body declares (local) starts from that @@ -1222,3 +1274,10 @@ func commentLines(text string) []string { out = append(out, " */") return out } + +func opaqueRepresentationCommentLines(text string) []string { + text = strings.ReplaceAll(text, "*/", "* /") + text = strings.ReplaceAll(strings.ReplaceAll(text, "\r\n", "\n"), "\r", "\n") + comment, _ := source.CommentText(text, "") + return strings.Split(comment, "\n") +} diff --git a/tests/corpus/testdata/pssm_migration_expected.txt b/tests/corpus/testdata/pssm_migration_expected.txt index cab32f40ce..a30b823d2a 100644 --- a/tests/corpus/testdata/pssm_migration_expected.txt +++ b/tests/corpus/testdata/pssm_migration_expected.txt @@ -5,8 +5,8 @@ # ratchet whose every movement is adjudicated. See docs/project/pssm-migration.md. # Regenerate with: # go test ./tests/corpus -run TestPSSMSuiteMigration -update-pssm-migration -7325 approximated -33013 mapped +7329 approximated +33086 mapped 6 skipped -8764 unmapped +8759 unmapped 12 validation-errors diff --git a/tests/migrate/behavior_more_test.go b/tests/migrate/behavior_more_test.go index 581a9828a6..40efbd9b9b 100644 --- a/tests/migrate/behavior_more_test.go +++ b/tests/migrate/behavior_more_test.go @@ -854,7 +854,7 @@ func TestStrictDeferralSurvivesInactiveSubstateTransition(t *testing.T) { r := migrateDocumentOptions(t, machine, ``, migrate.Options{Strict: true}) wantNoLine(t, r.Notation, "defer Door;") for _, line := range []string{ - "state Busy {", + "state Busy parallel {", "item deferred : Door[*] ordered;", "#MigrationMetadata::DeferredKeeper action receive accept kept : Door;", "transition first Heating accept Door then Opened;", @@ -2074,22 +2074,21 @@ const loggingMachine = ` const loggingApplications = ` ` -// A state's entry and exit behaviors and the states of its one region are -// members of one v2 body, so those sharing a name are told apart the way any -// clashing members are; the region's entry follows the state's own entry action. +// A state's entry and exit behaviors stay on the owner, and its one region is a +// nested state whose entry remains inside that region. func TestStateBehaviorsSharingANameAreDistinguished(t *testing.T) { r := migrateDocument(t, loggingMachine, loggingApplications) for _, line := range []string{ + "state On parallel {", "entry action log {", "exit action 'log 2' {", + "state region {", + "entry; then 'log 3';", "then 'log 3';", "state 'log 3';", } { wantLine(t, r.Notation, line) } - if strings.Contains(string(r.Notation), "entry; then 'log 3'") { - t.Errorf("the region's entry is written as a second entry action:\n%s", r.Notation) - } wantNote(t, r, "_onExit", migrate.Approximated, "written as v2 assignments") if es := entriesFor(r, "_onExit"); len(es) == 1 && es[0].Target != "Logger::Logging::On::'log 2'" { t.Errorf("the exit behavior's report target is %q, not the name written", es[0].Target) @@ -2381,12 +2380,10 @@ func TestDeferralNotesTransitionsItCannotOutrank(t *testing.T) { } } -// A transition the writer names by a route other than a vertex's own name — -// into a terminate pseudostate, written to done, or into a submachine state -// through a connection point reference, written to the entry point's state — -// counts as written for the deferral analysis as any other: an unguarded -// completion transition of either kind drops the deferral, since the accept -// loop would never let it fire, and a triggered one takes the signal. +// A transition to a terminate pseudostate, written as a terminate action, or +// through a submachine state by a connection point reference counts as written +// for deferral analysis: an unguarded completion transition drops the deferral, +// since its accept loop would never let it fire, and a triggered one takes it. func TestDeferralCountsTransitionsToUnnamedTargets(t *testing.T) { const terminating = ` @@ -2405,7 +2402,8 @@ func TestDeferralCountsTransitionsToUnnamedTargets(t *testing.T) { ` r := migrateDocumentOptions(t, terminating, ``, migrate.Options{}) - wantLine(t, r.Notation, "transition first Off then done;") + wantLine(t, r.Notation, "action terminated terminate;") + wantLine(t, r.Notation, "transition first Off then terminated;") wantNoLine(t, r.Notation, "item deferred : Door[*] ordered;") wantNote(t, r, "_dDoor", migrate.Unmapped, "the completion transition (_tEnd) leaves the state once its do action ends, which the accept loop that would keep Door never lets it, so the deferral is dropped") diff --git a/tests/migrate/behavior_test.go b/tests/migrate/behavior_test.go index c86791fede..02d69dee07 100644 --- a/tests/migrate/behavior_test.go +++ b/tests/migrate/behavior_test.go @@ -153,7 +153,7 @@ func TestActivityMigratesToAnExecutableActionDef(t *testing.T) { } { wantLine(t, r.Notation, line) } - wantNote(t, r, "_stamp", migrate.Approximated, "the body is kept as a comment") + wantNote(t, r, "_stamp", migrate.Approximated, "the body is kept as a textual representation, which is not executed") wantNote(t, r, "_e8", migrate.Approximated, "the guard [Focus lost] is kept as a comment and the edge written unguarded") wantNote(t, r, "_dcA", migrate.Approximated, "written as a fixed wait of 3.0 s before 'Point'") wantNote(t, r, "_dcB", migrate.Approximated, "written as a wait drawn uniformly over [1.0, 8.0] s before 'Focus'") @@ -696,7 +696,7 @@ func TestStateMachineMigratesToAnExecutableStateDef(t *testing.T) { for _, line := range []string{ "state def Control {", "entry; then Idle;", - "state Busy {", + "state Busy parallel {", "entry; then Warm;", "transition first Warm accept after 2.0 [SI::s] then Hot;", "state Cool : Cooling { in ref :>> context = Control::context; }", @@ -889,7 +889,7 @@ func TestActivityWithSendAcceptAndOperationCalls(t *testing.T) { wantNote(t, r, "_twice", migrate.Mapped, "") wantNote(t, r, "_tr", migrate.Approximated, "the return parameter is written as an out parameter") wantNote(t, r, "_rcv", migrate.Approximated, "the reception has no method, so it only accepts the signal") - wantNote(t, r, "_js", migrate.Approximated, "the body is kept as a comment") + wantNote(t, r, "_js", migrate.Approximated, "the body is kept as a textual representation, which is not executed") wantNote(t, r, "_log", migrate.Approximated, "the pin 't' it passes for the parameter t of Station::Logging receives none: 'compute', which feeds it, produces no value; v1 runs the callee without the value, so the parameter is declared admitting none") wantNote(t, r, "_point", migrate.Mapped, "written as an action usage of Telescope, which a call on an object performs, so its body runs on the object and reaches its features") wantNote(t, r, "_callTgt", migrate.Mapped, "the call performs the usage point of the target this.tel") @@ -1001,7 +1001,7 @@ func TestNestedDefProbabilityReadsThroughContext(t *testing.T) { t.Run("held by a part a node of the body is named like", func(t *testing.T) { r := migrateFixtureFile(t, "probability_part_shadowed") wantLine(t, r.Notation, "part mission : Mission;") - wantLine(t, r.Notation, "action mission {") + wantLine(t, r.Notation, "action mission;") wantLine(t, r.Notation, "first 'decide' then a { @Stochastic::Probability { p = Sub::mission.pr; } }") wantLine(t, r.Notation, "first 'decide' then b { @Stochastic::Probability { p = 1.0 - Sub::mission.pr; } }") wantNote(t, r, "_ea", migrate.Mapped, "the probability reads the property pr of the object performing the action") diff --git a/tests/migrate/empty_region_test.go b/tests/migrate/empty_region_test.go index 6b45987797..271418705f 100644 --- a/tests/migrate/empty_region_test.go +++ b/tests/migrate/empty_region_test.go @@ -36,29 +36,29 @@ const emptyRegionApplications = ` ` -// A region with no vertex is not written: a state for it would be a region -// nothing enters, which the runtime refuses. The populated sibling region is -// then the state's only region and is written inline, and the machine runs. +// A region with no vertex is not written. The populated sibling region is +// wrapped under the composite state, and the machine runs. func TestEmptyRegionsAreNotWritten(t *testing.T) { r := migrateDocument(t, emptyRegionMachine, emptyRegionApplications) for _, line := range []string{ "state def Life {", "entry; then On;", - "state On {", + "state On parallel {", + "state work {", "entry; then Run;", "state def Idle {", "/* the StateMachine's regions hold no vertex: nothing enters them */", } { wantLine(t, r.Notation, line) } - for _, line := range []string{"parallel", "state spare"} { + for _, line := range []string{"state spare"} { if strings.Contains(string(r.Notation), line) { t.Errorf("notation writes %q for an empty region:\n%s", line, r.Notation) } } wantNote(t, r, "_r2", migrate.Skipped, "the region holds no vertex, so nothing enters it and no state is written for it") wantNote(t, r, "_r3", migrate.Skipped, "the region holds no vertex, so nothing enters it and no state is written for it") - wantNote(t, r, "_r1", migrate.Mapped, "the one region is written as the body of its owner") + wantNote(t, r, "_r1", migrate.Mapped, "the region is written as the sub-state work of the parallel state On, as a composite state's regions are") s := session(t, r) meta(t, s, "%instantiate Device") diff --git a/tests/migrate/migrate_test.go b/tests/migrate/migrate_test.go index e39ba4f948..f0876f3abc 100644 --- a/tests/migrate/migrate_test.go +++ b/tests/migrate/migrate_test.go @@ -395,6 +395,7 @@ var constructFixtures = []string{ "decision_else", "calc_context", "interaction_context", + "terminate_orthogonal", "viewpoint_concerns", "parameter_sets", } diff --git a/tests/migrate/opaque_migration_test.go b/tests/migrate/opaque_migration_test.go index 9ffa54bf34..e2d2c5434b 100644 --- a/tests/migrate/opaque_migration_test.go +++ b/tests/migrate/opaque_migration_test.go @@ -284,8 +284,8 @@ func TestTranslatorRefusalsAndConfiguredClockName(t *testing.T) { "_mixed": `the construct "+" is outside the translated subset: string concatenation has no v2 form in the subset`, "_python": `the Python body is written as v2 assignments`, "_partial": `the call "label.trim" is not in the translated function table`, - "_root": `the body is kept as a comment: as the result expression, the types at "Math.sqrt(x)" disagree: the expression is a Real, not the Boolean wanted; as statements, the call "Math.sqrt" is not in the translated function table: a call is not a statement of the subset`, - "_split": `the body is kept as a comment: as the result expression, the behavior has 2 output parameters; one result expression can stand for none of them; as statements, the construct "x" is outside the translated subset: an expression that assigns nothing is not a statement of the subset`, + "_root": `the body is kept as a textual representation, which is not executed: as the result expression, the types at "Math.sqrt(x)" disagree: the expression is a Real, not the Boolean wanted; as statements, the call "Math.sqrt" is not in the translated function table: a call is not a statement of the subset`, + "_split": `the body is kept as a textual representation, which is not executed: as the result expression, the behavior has 2 output parameters; one result expression can stand for none of them; as statements, the construct "x" is outside the translated subset: an expression that assigns nothing is not a statement of the subset`, } { wantNote(t, r, id, migrate.Approximated, want) } @@ -394,8 +394,8 @@ func TestTranslatedOutputPinsFeedTheirFlows(t *testing.T) { wantNote(t, r, "_log", migrate.Approximated, "the JavaScript body is translated to v2; the console print print(…) is left out, as it writes to the tool's console and changes nothing of the model; the console print println(…) is left out, as it writes to the tool's console and changes nothing of the model") wantNote(t, r, "_shout", migrate.Approximated, "the JavaScript body is translated to v2; the console print println(…) is left out, as it writes to the tool's console and changes nothing of the model") wantLine(t, r.Notation, "assign ticks := ticks + 1;") - wantLine(t, r.Notation, "action shout;") - wantNoLine(t, r.Notation, "print(") + wantLine(t, r.Notation, "action shout {") + wantLine(t, r.Notation, `rep language "JavaScript" /* println("done");*/`) s := session(t, r) meta(t, s, "%instantiate Meter") @@ -438,7 +438,7 @@ func TestNonScalarFeaturesAndScriptLiterals(t *testing.T) { `/* default value not migrated: {JavaScript} Math.sqrt(total) — the types at "Math.sqrt(total)" disagree: the expression is a Real, not the Boolean wanted */`, "in limit : ScalarValues::Integer[1] default = count + 1;", "/* guard not migrated: [{JavaScript} ready and enabled] — the text \"and\" is not expression syntax: text follows the expression */", - "/* body not migrated (the text \"and\" is not expression syntax: a statement ends at `;` or a newline) {JavaScript}:", + `rep language "JavaScript" /* flag = ready and enabled*/`, "assign flag := ready and enabled;", } { wantLine(t, r.Notation, line) @@ -510,7 +510,7 @@ func TestPluralPathsStayCollections(t *testing.T) { wantNote(t, r, "_peak", migrate.Mapped, "the JavaScript body is translated to v2") for id, want := range map[string]string{ "_spread": `the types at "+" disagree: an operand is a collection, not a number`, - "_reset": `the body is kept as a comment: the construct "cells.reading" is outside the translated subset: cells is a collection, so the assignment would write through several objects`, + "_reset": `the body is kept as a textual representation, which is not executed: the construct "cells.reading" is outside the translated subset: cells is a collection, so the assignment would write through several objects`, "_calib": `the construct "reading" is outside the translated subset: this.cells is a collection, so the assignment would write through several objects`, "_span": `the types at "total =" disagree: one side is a collection and the other a single value`, "_ticking": "its swimlane represents this.cells, a collection of objects, so none of them performs the call, which runs in the caller's context; the behavior acts on a Gauge through its parameter context, which is left unbound: the caller is a Meter, which is no Gauge and has 4 parts that are one, so no one of them is chosen; the behavior belongs to Gauge and runs here in the caller's context", diff --git a/tests/migrate/opaque_textual_rep_test.go b/tests/migrate/opaque_textual_rep_test.go new file mode 100644 index 0000000000..9c36f4a5e2 --- /dev/null +++ b/tests/migrate/opaque_textual_rep_test.go @@ -0,0 +1,173 @@ +package migrate_test + +import ( + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/syntax/ast" + "github.com/Open-MBEE/OpenSysML/internal/syntax/parser" + "github.com/Open-MBEE/OpenSysML/internal/syntax/source" + "github.com/Open-MBEE/OpenSysML/internal/translate/migrate" +) + +func opaqueActionDocument(t *testing.T, body string) *migrate.Result { + t.Helper() + return migrateDocument(t, ` + + + `+body+` + + + + `, "") +} + +func TestOpaqueActionTextualRepresentations(t *testing.T) { + t.Run("translated", func(t *testing.T) { + r := opaqueActionDocument(t, `JavaScriptx = 1;`) + wantLine(t, r.Notation, `rep language "JavaScript" /* x = 1;*/`) + wantLine(t, r.Notation, "assign x := 1;") + wantClean(t, "t.sysml", r) + }) + + t.Run("untranslated", func(t *testing.T) { + r := opaqueActionDocument(t, `Javawork();`) + wantLine(t, r.Notation, `rep language "Java" /* work();*/`) + wantNoLine(t, r.Notation, "body not migrated") + wantNote(t, r, "_opaque", migrate.Approximated, "the body is kept as a textual representation, which is not executed") + wantClean(t, "t.sysml", r) + }) + + t.Run("without language", func(t *testing.T) { + r := opaqueActionDocument(t, `not a statement`) + wantLine(t, r.Notation, "body not migrated") + wantLine(t, r.Notation, "not a statement") + wantNote(t, r, "_opaque", migrate.Approximated, "the body is kept as a comment") + wantClean(t, "t.sysml", r) + }) + + t.Run("pairs by index", func(t *testing.T) { + r := opaqueActionDocument(t, `Javafirst();Pythonsecond()`) + wantLine(t, r.Notation, `rep language "Java" /* first();*/`) + wantLine(t, r.Notation, `rep language "Python" /* second()*/`) + wantNoLine(t, r.Notation, "body not migrated") + wantClean(t, "t.sysml", r) + }) + + t.Run("attribute fallback", func(t *testing.T) { + r := migrateDocument(t, ` + + + + + + + `, "") + wantLine(t, r.Notation, `rep language "Java" /* work();*/`) + wantClean(t, "t.sysml", r) + }) + + t.Run("empty", func(t *testing.T) { + r := opaqueActionDocument(t, "") + wantNoLine(t, r.Notation, "body not migrated") + wantNoLine(t, r.Notation, "rep language") + wantNote(t, r, "_opaque", migrate.Mapped, "its body is empty, so it is written as an action that does nothing") + wantClean(t, "t.sysml", r) + }) + + t.Run("comment terminator", func(t *testing.T) { + r := opaqueActionDocument(t, `Pythonprint("*/")`) + wantLine(t, r.Notation, `rep language "Python" /* print("* /")*/`) + wantClean(t, "t.sysml", r) + }) +} + +func TestOpaqueBehaviorTextualRepresentations(t *testing.T) { + t.Run("action definition", func(t *testing.T) { + r := migrateDocument(t, ` + + Java + work(); + `, "") + wantLine(t, r.Notation, "action def Work {") + wantLine(t, r.Notation, `rep language "Java" /* work();*/`) + wantNoLine(t, r.Notation, "body not migrated") + wantNote(t, r, "_behavior", migrate.Approximated, "the body is kept as a textual representation, which is not executed") + wantClean(t, "t.sysml", r) + }) + + t.Run("calculation definition", func(t *testing.T) { + r := migrateDocument(t, ` + + JavaScript + 1 + 2 + `, "") + wantLine(t, r.Notation, `rep language "JavaScript" /* 1 + 2*/`) + rep := strings.Index(string(r.Notation), `rep language "JavaScript"`) + result := strings.LastIndex(string(r.Notation), "1 + 2") + if rep < 0 || result < 0 || rep >= result { + t.Errorf("textual representation must precede the calc result:\n%s", r.Notation) + } + wantClean(t, "t.sysml", r) + }) + + t.Run("empty behaviors", func(t *testing.T) { + for _, kind := range []string{"OpaqueBehavior", "FunctionBehavior"} { + t.Run(kind, func(t *testing.T) { + r := migrateDocument(t, ` + `, "") + wantNoLine(t, r.Notation, "body not migrated") + wantNoLine(t, r.Notation, "rep language") + wantNote(t, r, "_behavior", migrate.Mapped, "") + wantClean(t, "t.sysml", r) + }) + } + }) + + t.Run("operation-written definition", func(t *testing.T) { + r := migrateDocument(t, ` + + + + Java + work(); + + `, ``) + wantLine(t, r.Notation, `rep language "Java" /* work();*/`) + wantClean(t, "t.sysml", r) + }) +} + +func TestOpaqueRepresentationBodyRoundTrip(t *testing.T) { + for _, body := range []struct { + name string + text string + }{ + {name: "leading indentation and trailing spaces", text: " command\n argument \n"}, + {name: "leading blank line", text: "\n command\n argument \n"}, + } { + t.Run(body.name, func(t *testing.T) { + r := opaqueActionDocument(t, `Java`+body.text+``) + sf := source.New("migrated.sysml", r.Notation) + p := parser.New(sf) + root := p.ParseFile() + if len(p.Diagnostics) != 0 { + t.Fatalf("migrated notation did not parse: %v", p.Diagnostics) + } + var bodies []string + ast.Inspect(root, func(n ast.Node) bool { + if rep, ok := n.(*ast.TextualRepresentation); ok { + bodies = append(bodies, source.CommentBody(sf.Text(rep.BodySpan))) + } + return true + }) + // source.CommentBody drops the comment delimiters, opener whitespace through its first line terminator, and each continuation line's indentation, `*`, and one following space. + if len(bodies) != 1 { + t.Fatalf("found %d textual representations, want one", len(bodies)) + } + if bodies[0] != body.text { + t.Errorf("parsed representation body = %q, want %q\n%s", bodies[0], body.text, r.Notation) + } + }) + } +} diff --git a/tests/migrate/owner_reads_test.go b/tests/migrate/owner_reads_test.go index be48c66804..206c620d84 100644 --- a/tests/migrate/owner_reads_test.go +++ b/tests/migrate/owner_reads_test.go @@ -331,7 +331,7 @@ func TestLocalsABodyDeclaresAreNoReadsOfTheOwner(t *testing.T) { r := migrateDocument(t, localsModel, ``) for _, line := range []string{ "action def Compute {", - `the construct "let count" is outside the translated subset: count is already a feature here, which a declaration would shadow`, + `rep language "JavaScript" /* let count = 1; count += 1;*/`, "action tally {", "attribute n : ScalarValues::Integer;", "assign count := n + 1;", diff --git a/tests/migrate/region_wrapper_test.go b/tests/migrate/region_wrapper_test.go new file mode 100644 index 0000000000..e3f560d0cd --- /dev/null +++ b/tests/migrate/region_wrapper_test.go @@ -0,0 +1,195 @@ +package migrate_test + +import ( + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/translate/migrate" +) + +const singleRegionWrapperMachine = ` + + + + + + + + + + + + + + + + + JavaScript + visits = visits + 1; + + + JavaScript + visits = visits + 10; + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + +const singleRegionWrapperApplications = ` + ` + +func TestSingleRegionCompositeStateIsWrapped(t *testing.T) { + r := migrateDocument(t, singleRegionWrapperMachine, singleRegionWrapperApplications) + for _, line := range []string{ + "state S parallel {", + "state R {", + "entry; then A;", + "#StateMachines::shallowHistory state history;", + "transition first Outside accept Open then S.R.A;", + "transition first A accept Step then B;", + "transition first S.R.B accept Leave then Outside;", + "transition first B accept Finish then done;", + "entry action remember", + "exit action record", + } { + wantLine(t, r.Notation, line) + } + wantNote(t, r, "_inner", migrate.Mapped, "the region is written as the sub-state R of the parallel state S, as a composite state's regions are") + wantClean(t, "single-region composite state wrapper", r) + + s := session(t, r) + meta(t, s, "%instantiate Rig") + meta(t, s, "%state Rig::Run #1") + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: Outside") { + t.Fatalf("initial state: %s", out) + } + meta(t, s, "%send Open") + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: A") { + t.Fatalf("Open did not enter S.R.A:\n%s", out) + } + meta(t, s, "%send Step") + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: B") { + t.Fatalf("Step did not enter S.R.B:\n%s", out) + } + meta(t, s, "%send Leave") + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: Outside") { + t.Fatalf("Leave did not exit S:\n%s", out) + } + meta(t, s, "%send Open") + meta(t, s, "%step") + meta(t, s, "%send Step") + meta(t, s, "%step") + meta(t, s, "%send Finish") + meta(t, s, "%step") + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: Other") { + t.Fatalf("R's final state did not complete S and fire its completion transition:\n%s", out) + } +} + +func TestSingleRegionCompositeStateConnectionPointsStayInRegion(t *testing.T) { + xmi := ` + + + + + + + + + + + + + + + + + + + + + + + + + + ` + r := migrateDocument(t, xmi, singleRegionWrapperApplications) + for _, line := range []string{"state S parallel {", "state R {", "#StateMachines::junction state 'in';", "#StateMachines::junction state 'out';", "transition first 'in' then Active;", "transition first Active then 'out';", "transition first Idle accept Go then S.R.'in';", "transition first S.R.'out' then Idle;"} { + wantLine(t, r.Notation, line) + } + wantClean(t, "wrapped state connection points", r) +} + +func TestStateMachineSingleRegionRemainsFlattened(t *testing.T) { + xmi := ` + + + + + + + + + ` + r := migrateDocument(t, xmi, singleRegionWrapperApplications) + wantLine(t, r.Notation, "entry; then Active;") + wantLine(t, r.Notation, "state Active;") + if strings.Contains(string(r.Notation), "parallel") || strings.Contains(string(r.Notation), "state main {") { + t.Errorf("the StateMachine's single region was wrapped:\n%s", r.Notation) + } + wantClean(t, "single-region state machine", r) +} + +func TestUnnamedCompositeRegionGetsFreshStateName(t *testing.T) { + xmi := ` + + + + + + ` + integerHref + ` + + + + + + + + + + ` + r := migrateDocument(t, xmi, singleRegionWrapperApplications) + for _, line := range []string{"state S parallel {", "state region2 {", "entry; then Active;"} { + wantLine(t, r.Notation, line) + } + wantNote(t, r, "_inner", migrate.Mapped, "the region is written as the sub-state region2 of the parallel state S") + wantClean(t, "unnamed composite region", r) +} diff --git a/tests/migrate/script_locals_test.go b/tests/migrate/script_locals_test.go index 248657a4a8..ec5d585364 100644 --- a/tests/migrate/script_locals_test.go +++ b/tests/migrate/script_locals_test.go @@ -28,7 +28,10 @@ func TestScriptAssignmentsDeclareUndeclaredNames(t *testing.T) { } wantNoLine(t, r.Notation, "attribute stepsOld") wantClean(t, "t.sysml", r) + wantLine(t, r.Notation, `rep language "JavaScript" /* t1 = simtime;`) + wantLine(t, r.Notation, `rep language "Java" /* tdiffOld = tdiff;`) + wantLine(t, r.Notation, `rep language "JavaScript" /* steps = steps + stepsOld;`) wantNote(t, r, "_diffTime", migrate.Approximated, "the JavaScript body is translated to v2; the clock variable simtime, named by the configuration Timing, reads the local clock; t1 is declared nowhere, so it is declared a local attribute of the action: a script's assignment to an undeclared name creates it; tdiffOld is declared nowhere, so it is declared a local attribute of the action: a script's assignment to an undeclared name creates it; the console print print(…) is left out, as it writes to the tool's console and changes nothing of the model; the JavaScript body is written as v2 assignments") - wantNote(t, r, "_javaDiff", migrate.Approximated, `the body is kept as a comment: the name "tdiffOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::javaDiff is called tdiffOld`) - wantNote(t, r, "_readFirst", migrate.Approximated, `the body is kept as a comment: the name "stepsOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::readFirst is called stepsOld`) + wantNote(t, r, "_javaDiff", migrate.Approximated, `the body is kept as a textual representation, which is not executed: the name "tdiffOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::javaDiff is called tdiffOld`) + wantNote(t, r, "_readFirst", migrate.Approximated, `the body is kept as a textual representation, which is not executed: the name "stepsOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::readFirst is called stepsOld`) } diff --git a/tests/migrate/states_test.go b/tests/migrate/states_test.go index 70b351da45..1d09b85b35 100644 --- a/tests/migrate/states_test.go +++ b/tests/migrate/states_test.go @@ -18,7 +18,7 @@ func TestStateMachineCrossRegionTransitionsAndPseudostates(t *testing.T) { "state def Line {", "attribute dawn : Time::TimeInstantValue = 21600.0 [SI::s];", "entry; then Idle;", - "state Work {", + "state Work parallel {", "entry; then Prep;", "#StateMachines::shallowHistory state last;", "#StateMachines::deepHistory state deepest;", @@ -29,11 +29,12 @@ func TestStateMachineCrossRegionTransitionsAndPseudostates(t *testing.T) { "join gather;", "state Both {", "state regions parallel {", - "transition first Work.Run accept Stop then Idle;", - "transition first Idle accept Resume then Work.Run;", + "transition first Work.steps.Run accept Stop then Idle;", + "transition first Idle accept Resume then Work.steps.Run;", "transition first Pause accept Resume", - "do action log { }", - "then Work.last;", + "do action log {", + `rep language "JavaScript" /* println("resuming at " + count);*/`, + "then Work.steps.last;", "transition first Idle accept Enter then Cell.warmStart;", "transition first Cell.spent then Idle;", "transition first route if context.count < 2 then Work;", @@ -44,7 +45,7 @@ func TestStateMachineCrossRegionTransitionsAndPseudostates(t *testing.T) { "transition first Both.regions.b.B2 then gather;", "transition first gather then Idle;", "transition first Idle accept Ping then Idle;", - "transition first Work accept Bump then Work.Prep;", + "transition first Work accept Bump then Work.steps.Prep;", "transition first Pause accept at dawn then Idle;", "state def CellMachine {", "state warmStart;", @@ -57,8 +58,8 @@ func TestStateMachineCrossRegionTransitionsAndPseudostates(t *testing.T) { if strings.Contains(string(r.Notation), "accept at never") || strings.Contains(string(r.Notation), "accept Unused") { t.Errorf("an unresolvable instant or an unreferenced event was written:\n%s", r.Notation) } - wantNote(t, r, "_tStop", migrate.Mapped, "the source 'Run' lies in another region and is named by its path Work.Run") - wantNote(t, r, "_tDirect", migrate.Mapped, "the target 'Run' lies in another region and is named by its path Work.Run") + wantNote(t, r, "_tStop", migrate.Mapped, "the source 'Run' lies in another region and is named by its path Work.steps.Run") + wantNote(t, r, "_tDirect", migrate.Mapped, "the target 'Run' lies in another region and is named by its path Work.steps.Run") wantNote(t, r, "_junc", migrate.Mapped, "written as a #StateMachines::junction state pseudostate") wantNote(t, r, "_gSpent", migrate.Mapped, "an else guard is written as the unguarded transition out of the junction") wantNote(t, r, "_fork", migrate.Mapped, "written as a fork pseudostate") @@ -185,10 +186,10 @@ func TestStateMachineCrossRegionTransitionsAndPseudostates(t *testing.T) { func TestNestedTransitionRelocatesToCommonAncestor(t *testing.T) { for _, opts := range []migrate.Options{{}, {Strict: true}} { r := migrateFixtureFileOptions(t, "transition_relocation", opts) - wantLine(t, r.Notation, "transition first Work.Run accept Stop then Parked.Ready;") + wantLine(t, r.Notation, "transition first Work.work.Run accept Stop then Parked.parked.Ready;") wantNote(t, r, "_region4Init", migrate.Approximated, "coincides with region3's own entry into 'wait3'") wantNote(t, r, "_crossRegionInit", migrate.Approximated, "coincides with region3's own entry into 'wait3'") - wantLine(t, r.Notation, "no default entry: these regions have no written entry: 'region4'") + wantLine(t, r.Notation, "entry; then regions;") wantLine(t, r.Notation, "every initial pseudostate of the region enters another region: nothing enters this one") if strings.Count(string(r.Notation), "then wait3;") != 1 { t.Errorf("region3's entry into wait3 is not written exactly once:\n%s", r.Notation) @@ -258,13 +259,18 @@ func TestInitialIntoOrthogonalRegionEntersThatRegion(t *testing.T) { } // (d) two initials both entering the region: the first is written, the second refused. - wantLine(t, r.Notation, "state Duplicate {\n /* not migrated: Pseudostate (_uaInit2) — a region has one initial pseudostate; (_uaInit1) is written as it */\n entry; then ua1;") + wantLine(t, r.Notation, "state Duplicate parallel {") + wantLine(t, r.Notation, "state ua {") + wantLine(t, r.Notation, "/* not migrated: Pseudostate (_uaInit2) — a region has one initial pseudostate; (_uaInit1) is written as it */") + wantLine(t, r.Notation, "entry; then ua1;") wantNote(t, r, "_uaInit1", migrate.Mapped, "written as the entry of the region") - // (e) the target lies in a nested composite state's one region, written as that state's body. - wantLine(t, r.Notation, "state Inner {\n entry; then inner1;") - wantNote(t, r, "_naStray", migrate.Approximated, "written as the entry of the body of 'Inner', which owns its target 'inner1', not of na") - wantNote(t, r, "_naStrayT", migrate.Approximated, "so it is written as the entry of the body of 'Inner'") + // (e) the target lies in a nested composite state's wrapped region. + wantLine(t, r.Notation, "state Inner parallel {") + wantLine(t, r.Notation, "state ni {") + wantLine(t, r.Notation, "entry; then inner1;") + wantNote(t, r, "_naStray", migrate.Approximated, "written as the entry of ni, which owns its target 'inner1', not of na") + wantNote(t, r, "_naStrayT", migrate.Approximated, "so it is written as the entry of ni") // (f) the target region's own initial has no transition, so it is no entry: the stray one is donated. wantLine(t, r.Notation, "state gb {\n /* not migrated: Pseudostate (_gbInit) — no transition leaves the initial pseudostate */\n entry; then gb1;") @@ -272,7 +278,9 @@ func TestInitialIntoOrthogonalRegionEntersThatRegion(t *testing.T) { wantNote(t, r, "_gaStray", migrate.Approximated, "written as the entry of gb, which owns its target 'gb1', not of ga") // (g) a target in a region of another, non-orthogonal state is refused, not donated. - wantLine(t, r.Notation, "state Work {\n /* not migrated: Pseudostate (_wInit) — the initial transition's target has no v2 form here */") + wantLine(t, r.Notation, "state Work parallel {") + wantLine(t, r.Notation, "state wr {") + wantLine(t, r.Notation, "/* not migrated: Pseudostate (_wInit) — the initial transition's target has no v2 form here */") wantNote(t, r, "_wInitT", migrate.Unmapped, "its target 'idle1' lies in 'ir', a region of 'Idle' and no orthogonal region of the pseudostate's; an initial transition enters its own region") if strings.Count(s, "then idle1;") != 1 { t.Errorf("Idle's own entry into idle1 is not written exactly once:\n%s", s) @@ -340,7 +348,7 @@ func TestSubmachineStateBlockRedeclaresParametersForEntry(t *testing.T) { func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { r := migrateFixtureFile(t, "station_points") for _, line := range []string{ - "state Work {", + "state Work parallel {", "#StateMachines::junction state Start;", "#StateMachines::junction state Leave;", "#StateMachines::junction state Deep;", @@ -352,17 +360,17 @@ func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { "transition first Start", "transition first Run accept Finish", "then Leave;", - "transition first Run.Out", + "transition first steps.Run.pace.Out", "fork Both;", "join Gather;", "transition first Both then regions.a.A1;", "transition first Both then regions.b.B1;", "then Gather;", - "then Work.Start;", + "then Work.steps.Start;", "transition first Idle accept Enter then Work;", - "then Work.Run.Deep;", - "transition first Idle accept Resume then Work.H;", - "transition first Work.Leave", + "then Work.steps.Run.pace.Deep;", + "transition first Idle accept Resume then Work.steps.H;", + "transition first Work.steps.Leave", "transition first Idle accept Split then Sync.Both;", "transition first Sync.Gather", "/* not migrated: Pseudostate 'Through' — (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */", @@ -380,8 +388,8 @@ func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { wantNote(t, r, "_tEnter", migrate.Mapped, "written to Work: no transition leaves the entry point 'Plain'") wantNote(t, r, "_both", migrate.Mapped, "written as a fork of its state, whose branches start its regions; a transition entering through it runs the state's entry behavior, then the branches") wantNote(t, r, "_gather", migrate.Mapped, "written as a join of its state, which its regions leave through together; the transitions into the join run, then the state's exit behavior, then the transition leaving it") - wantNote(t, r, "_tGo", migrate.Mapped, "named by its path Work.Start") - wantNote(t, r, "_tDive", migrate.Mapped, "named by its path Work.Run.Deep") + wantNote(t, r, "_tGo", migrate.Mapped, "named by its path Work.steps.Start") + wantNote(t, r, "_tDive", migrate.Mapped, "named by its path Work.steps.Run.pace.Deep") wantNote(t, r, "_tBothA", migrate.Mapped, "named by its path Both") wantNote(t, r, "_tAg", migrate.Mapped, "named by its path Gather") wantNote(t, r, "_hist", migrate.Mapped, "written as a `#StateMachines::shallowHistory state` shallow history") @@ -428,7 +436,7 @@ func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { // then the transition out of it. one := station() drive("Go", "Idle -> Start") - current("Slow") + current("Run | Slow") trace(one, "41112314") drive("Finish", "Run -> Leave") current("Idle") @@ -441,7 +449,7 @@ func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { // then the transition out of it into Prep, still inside Work. two := station() drive("Dive", "Idle -> Deep") - current("Fast") + current("Run | Fast") trace(two, "4211142117") drive("Back", "Fast -> Out") current("Prep") @@ -459,7 +467,7 @@ func TestCompositeStateConnectionPointsKeepTheUMLOrder(t *testing.T) { drive("Stop", "Work -> Idle") trace(three, "111314151244") drive("Resume", "Idle -> H") - current("Slow") + current("Run | Slow") trace(three, "1113141512441114") meta(t, s, "%stop") @@ -522,18 +530,18 @@ const gateMachine = ` const gateApplications = ` ` -// An empty region is skipped when the machine's vertices are named as when they are -// written, so the remaining region's states are inline and a transition across nesting -// levels names its far end by a path that exists. +// An empty region is skipped while the populated region remains nested under its +// composite owner; transitions name vertices through the generated region state. func TestEmptyRegionLeavesNoPhantomPath(t *testing.T) { r := migrateDocument(t, gateMachine, gateApplications) for _, line := range []string{ "state def Latch {", "entry; then Idle;", - "state Busy {", + "state Busy parallel {", + "state region {", "entry; then Inner;", - "transition first Idle accept Open then Busy.Inner;", - "transition first Busy.Inner accept Shut then Idle;", + "transition first Idle accept Open then Busy.region.Inner;", + "transition first Busy.region.Inner accept Shut then Idle;", } { wantLine(t, r.Notation, line) } @@ -541,7 +549,7 @@ func TestEmptyRegionLeavesNoPhantomPath(t *testing.T) { t.Errorf("a parallel state was named for a machine with one populated region:\n%s", r.Notation) } wantNote(t, r, "_gUnused", migrate.Skipped, "the region holds no vertex, so nothing enters it and no state is written for it") - wantNote(t, r, "_gtIn", migrate.Mapped, "the target 'Inner' lies in another region and is named by its path Busy.Inner") + wantNote(t, r, "_gtIn", migrate.Mapped, "the target 'Inner' lies in another region and is named by its path Busy.region.Inner") s := session(t, r) meta(t, s, "%instantiate Gate") @@ -1047,7 +1055,7 @@ const guardedEntryApplications = ` // the owning state's entry behavior falsifying it does not turn the route. func TestGuardedEntryPointRouteIsKept(t *testing.T) { r := migrateDocument(t, guardedEntryMachine, guardedEntryApplications) - for _, line := range []string{"#StateMachines::junction state arm;", "transition first arm if context.armed then W2;", "transition first Idle accept Go then Work.arm;"} { + for _, line := range []string{"#StateMachines::junction state arm;", "transition first arm if context.armed then W2;", "transition first Idle accept Go then Work.r.arm;"} { if !strings.Contains(string(r.Notation), line) { t.Errorf("missing %q in:\n%s", line, r.Notation) } @@ -1257,24 +1265,25 @@ const pointOnlyRegion = ` const pointOnlyRegionApplications = ` ` -// A region listing only its state's connection points holds nothing to enter: it is skipped, so the -// state has one region and its points are junctions, not a fork and a join into a parallel state -// with an empty branch; the transitions the region holds are written in the state's body. +// A point-only region is skipped, leaving one populated region wrapped under the +// composite state; its connection points are named through that region. func TestPointOnlyRegionIsNotAParallelBranch(t *testing.T) { r := migrateDocument(t, pointOnlyRegion, pointOnlyRegionApplications) for _, line := range []string{ + "state Sync parallel {", + "state a {", "#StateMachines::junction state Both;", "#StateMachines::junction state Gather;", - "transition first Idle accept Go then Sync.Both;", + "transition first Idle accept Go then Sync.a.Both;", "transition first Both then A2;", "transition first A2 accept Stop then Gather;", - "transition first Sync.Gather then Idle;", + "transition first Sync.a.Gather then Idle;", } { if !strings.Contains(string(r.Notation), line) { t.Errorf("missing %q in:\n%s", line, r.Notation) } } - for _, bad := range []string{"parallel", "regions::", "fork ", "join "} { + for _, bad := range []string{"regions::", "fork ", "join "} { if strings.Contains(string(r.Notation), bad) { t.Errorf("the point-only region was written as a parallel branch (%q):\n%s", bad, r.Notation) } @@ -1330,9 +1339,9 @@ const noDefaultEntryMachine = ` const noDefaultEntryApplications = ` ` -// An entry point no transition leaves enters its state by the state's default entry, which, with no -// initial pseudostate in the region, enters the state and leaves the region inactive in v1 and in -// the runtime alike; the transition is written to the state and the report says so. +// An entry point no transition leaves enters its state by default. With no initial +// pseudostate in the region, the wrapper stand-in is active but its body has no active +// substate; the transition is written to the state and the report says so. func TestDefaultEntryPointOnOwnerWithoutInitialEntersTheState(t *testing.T) { r := migrateDocument(t, noDefaultEntryMachine, noDefaultEntryApplications) for _, line := range []string{ @@ -1347,15 +1356,15 @@ func TestDefaultEntryPointOnOwnerWithoutInitialEntersTheState(t *testing.T) { if strings.Contains(string(r.Notation), "#StateMachines::junction state via;") { t.Errorf("an entry point no transition leaves was written as a junction:\n%s", r.Notation) } - wantNote(t, r, "_dIn", migrate.Mapped, "no initial pseudostate starts the region 'r', which v1 too leaves inactive on entering the state") + wantNote(t, r, "_dIn", migrate.Mapped, "no initial pseudostate starts the state stand-in for region 'r', so its body has no active substate, as in v1") wantNote(t, r, "_dT1", migrate.Mapped, "written to Work: no transition leaves the entry point 'via'") s := session(t, r) meta(t, s, "%instantiate Rig") meta(t, s, "%state Rig::Main #1") meta(t, s, "%send Go") meta(t, s, "%step") - if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: Work") || strings.Contains(out, "W1") { - t.Errorf("entering Work by its default entry did not leave its region inactive:\n%s", out) + if out := meta(t, s, "%current"); !strings.Contains(out, "Current state: r") || strings.Contains(out, "W1") { + t.Errorf("entering Work by its default entry did not leave the stand-in's body inactive:\n%s", out) } meta(t, s, "%send Stop") meta(t, s, "%step") @@ -1540,7 +1549,7 @@ func TestExitPointReachedFromOutsideIsRefused(t *testing.T) { if n := strings.Count(string(r.Notation), "#StateMachines::junction state leave;"); n != 1 { t.Errorf("want Self's exit point alone written as a junction, got %d:\n%s", n, r.Notation) } - for _, line := range []string{"transition first Self accept Go then Self.leave;", "transition first Self.leave then Idle;"} { + for _, line := range []string{"transition first Self accept Go then Self.r.leave;", "transition first Self.r.leave then Idle;"} { if !strings.Contains(string(r.Notation), line) { t.Errorf("missing %q in:\n%s", line, r.Notation) } diff --git a/tests/migrate/terminate_test.go b/tests/migrate/terminate_test.go new file mode 100644 index 0000000000..b9bef1a006 --- /dev/null +++ b/tests/migrate/terminate_test.go @@ -0,0 +1,128 @@ +package migrate_test + +import ( + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/translate/migrate" +) + +func TestTerminatePseudostateEndsOrthogonalMachine(t *testing.T) { + r := migrateFixtureFile(t, "terminate_orthogonal") + for _, line := range []string{ + "state def Run {", + "entry; then regions;", + "state regions parallel {", + "state R1 {", + "state R2 {", + "action terminated terminate;", + "transition first A accept Stop then terminated;", + "transition first B accept Ping then C;", + } { + wantLine(t, r.Notation, line) + } + wantNote(t, r, "_end", migrate.Mapped, "written as a terminate action, which ends the state machine's performance in every region") + wantClean(t, "terminateOrthogonal", r) + + s := session(t, r) + meta(t, s, "%instantiate Machine") + meta(t, s, "%state Machine::Run #1") + if out := meta(t, s, "%send Stop"); !strings.Contains(out, "transition A -> terminated fires on it") { + t.Fatalf("%%send Stop: %s", out) + } + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "Execution state: Terminated") { + t.Fatalf("Stop did not end the machine:\n%s", out) + } + if out := meta(t, s, "%send Ping"); strings.Contains(out, "Accepted by") || strings.Contains(out, "transition B -> C fires") { + t.Fatalf("the terminated machine still accepted Ping:\n%s", out) + } + if out := meta(t, s, "%current"); !strings.Contains(out, "Execution state: Terminated") { + t.Fatalf("the sibling region remained active after Stop:\n%s", out) + } + + prior := &migrate.Result{Notation: []byte(` +item def Stop; +item def Ping; +part def Machine { + state def Run { + entry; then regions; + state regions parallel { + state R1 { + entry; then A; + state A; + transition first A accept Stop then done; + } + state R2 { + entry; then B; + state B; + state C; + transition first B accept Ping then C; + } + } + transition first regions then done; + } + exhibit state run : Run; +}`)} + s = session(t, prior) + meta(t, s, "%instantiate Machine") + meta(t, s, "%state Machine::Run #1") + stop := meta(t, s, "%send Stop") + step := meta(t, s, "%step") + ping := meta(t, s, "%send Ping") + if !strings.Contains(ping, "transition B -> C fires on it") { + t.Fatalf("the prior done-target form did not preserve sibling behavior:\nsend Stop:\n%s\nstep:\n%s\nsend Ping:\n%s", stop, step, ping) + } + meta(t, s, "%step") + if out := meta(t, s, "%current"); !strings.Contains(out, "C") { + t.Fatalf("the prior done-target form did not advance the sibling region:\n%s", out) + } +} + +func TestTerminatePseudostateInsideCompositeState(t *testing.T) { + r := migrateDocument(t, ` + + + + + + + + + + + + + + + + + + + + + `, + ``) + wantLine(t, r.Notation, "action terminated terminate;") + wantLine(t, r.Notation, "transition first A accept Stop then terminated;") + wantNote(t, r, "_end", migrate.Mapped, "written as a terminate action") + wantClean(t, "terminateInsideComposite", r) +} + +func TestUntargetedTerminatePseudostateIsWrittenCleanly(t *testing.T) { + r := migrateDocument(t, ` + + + + + + + + + + `, + ``) + wantLine(t, r.Notation, "action terminated terminate;") + wantNote(t, r, "_end", migrate.Mapped, "written as a terminate action") + wantClean(t, "untargetedTerminate", r) +} diff --git a/tests/migrate/testdata/xmi/acquisition.golden.sysml b/tests/migrate/testdata/xmi/acquisition.golden.sysml index bc219ed125..395716ab74 100644 --- a/tests/migrate/testdata/xmi/acquisition.golden.sysml +++ b/tests/migrate/testdata/xmi/acquisition.golden.sysml @@ -44,11 +44,13 @@ part def Observatory { } first stamp9 then 'start timer'; action 'start timer' { + rep language "JavaScript" /* Time_Acq_Total = simtime;*/ assign Time_Acq_Total := localClock.currentTime; } first 'start timer' then seed; action seed { in Retries : ScalarValues::Integer[1] = 2; + rep language "JavaScript" /* GS_Found = Retries < 1;*/ assign tcs.GS_Found := Retries < 1; } first seed then stamp; @@ -60,6 +62,8 @@ part def Observatory { } first stamp then 'first attempt'; action 'first attempt' { + rep language "JavaScript" /* i = 1; + * GS_Found = false;*/ assign tcs.i := 1; assign tcs.GS_Found := false; } @@ -115,6 +119,7 @@ part def Observatory { } first stamp4 then 'guide star found'; action 'guide star found' { + rep language "JavaScript" /* GS_Found = true;*/ assign tcs.GS_Found := true; } first 'guide star found' then stamp2; @@ -128,6 +133,7 @@ part def Observatory { } first stamp2 then merge4; action 'next attempt' { + rep language "JavaScript" /* i += 1;*/ assign tcs.i := tcs.i + 1; } first 'next attempt' then 'merge'; @@ -136,6 +142,7 @@ part def Observatory { } first stamp7 then abort; action abort { + rep language "JavaScript" /* GS_Found = false;*/ assign tcs.GS_Found := false; } first abort then stamp8; @@ -149,6 +156,7 @@ part def Observatory { first fork2 then merge3; first fork2 then merge2; action 'stop timer' { + rep language "JavaScript" /* Time_Acq_Total = simtime - Time_Acq_Total;*/ assign Time_Acq_Total := localClock.currentTime - Time_Acq_Total; } first 'stop timer' then stamp10; diff --git a/tests/migrate/testdata/xmi/calc_context.golden.sysml b/tests/migrate/testdata/xmi/calc_context.golden.sysml index 5d697c3f1b..8d2dbf3af1 100644 --- a/tests/migrate/testdata/xmi/calc_context.golden.sysml +++ b/tests/migrate/testdata/xmi/calc_context.golden.sysml @@ -4,6 +4,7 @@ part def Tank { in ref context : Tank[1]; in factor : ScalarValues::Real[1]; out result : ScalarValues::Real[1]; + rep language "English" /* level * factor*/ context.level * factor } } diff --git a/tests/migrate/testdata/xmi/control_nodes.golden.report.txt b/tests/migrate/testdata/xmi/control_nodes.golden.report.txt index b8b13e5df3..56df8aa51f 100644 --- a/tests/migrate/testdata/xmi/control_nodes.golden.report.txt +++ b/tests/migrate/testdata/xmi/control_nodes.golden.report.txt @@ -1,12 +1,8 @@ # SysML v1 to v2 migration report: control_nodes.xmi # exported by Example UML Tool -# migrated 20 element(s): 18 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 20 mapped, 0 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (2) -OpaqueAction Control::Controller::Run::cool _cn_cool -> cool (the body is kept as a comment: the body is empty) -OpaqueAction Control::Controller::Run::heat _cn_heat -> heat (the body is kept as a comment: the body is empty) - -## mapped (18) +## mapped (20) Package Control _pkg_control -> Control «Block» Class Control::Controller _blk_controller -> Control::Controller Activity Control::Controller::Run _act_run -> Control::Controller::Run @@ -24,4 +20,6 @@ InitialNode Control::Controller::Run:: _cn_init -> start JoinNode Control::Controller::Run:: _cn_join -> 'join' Diagram Control::Controller::Run::Run _diag_run -> Control::Controller::Run::Run (a SysML Activity Diagram written as a view rendered asInterconnectionDiagram of ActionFlowView; the view exposes action def Control::Controller::Run, whose graph the rendering draws with the 16 shown nodes and edges of it) DecisionNode Control::Controller::Run::check _cn_check -> check +OpaqueAction Control::Controller::Run::cool _cn_cool -> cool (its body is empty, so it is written as an action that does nothing) +OpaqueAction Control::Controller::Run::heat _cn_heat -> heat (its body is empty, so it is written as an action that does nothing) Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/control_nodes.golden.sysml b/tests/migrate/testdata/xmi/control_nodes.golden.sysml index 38a8ee0275..a4a1858010 100644 --- a/tests/migrate/testdata/xmi/control_nodes.golden.sysml +++ b/tests/migrate/testdata/xmi/control_nodes.golden.sysml @@ -9,13 +9,9 @@ package Control { fork 'fork'; succession 'fork to heat' first 'fork' then heat; succession 'fork to cool' first 'fork' then cool; - action heat { - /* body not migrated (the body is empty) */ - } + action heat; succession 'heat to join' first heat then 'join'; - action cool { - /* body not migrated (the body is empty) */ - } + action cool; succession 'cool to join' first cool then 'join'; join 'join'; succession 'join to check' first 'join' then check; diff --git a/tests/migrate/testdata/xmi/control_nodes.layout.golden.report.txt b/tests/migrate/testdata/xmi/control_nodes.layout.golden.report.txt index c54ea8de0f..c42fef120f 100644 --- a/tests/migrate/testdata/xmi/control_nodes.layout.golden.report.txt +++ b/tests/migrate/testdata/xmi/control_nodes.layout.golden.report.txt @@ -1,6 +1,6 @@ # SysML v1 to v2 migration report: control_nodes.xmi # exported by Example UML Tool -# migrated 20 element(s): 18 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to); laid out 1 of 1 diagrams from control_nodes.layout.xml: 6 elements positioned, 8 connectors routed, 0 styled, 0 notes +# migrated 20 element(s): 20 mapped, 0 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to); laid out 1 of 1 diagrams from control_nodes.layout.xml: 6 elements positioned, 8 connectors routed, 0 styled, 0 notes ## layout # source: control_nodes.layout.xml (2022x v1.0.0, cameo 2022x Refresh2, exported 2025-06-12T21:38:50.779458500Z) @@ -9,11 +9,7 @@ # styles: 0 of 0 written; notes: 0 written (0 anchored, 0 freed of an anchor the view does not lay out) # routes of ControlFlow: 8 written -## approximated (2) -OpaqueAction Control::Controller::Run::cool _cn_cool -> cool (the body is kept as a comment: the body is empty) -OpaqueAction Control::Controller::Run::heat _cn_heat -> heat (the body is kept as a comment: the body is empty) - -## mapped (18) +## mapped (20) Package Control _pkg_control -> Control «Block» Class Control::Controller _blk_controller -> Control::Controller Activity Control::Controller::Run _act_run -> Control::Controller::Run @@ -31,4 +27,6 @@ InitialNode Control::Controller::Run:: _cn_init -> start JoinNode Control::Controller::Run:: _cn_join -> 'join' Diagram Control::Controller::Run::Run _diag_run -> Control::Controller::Run::Run (a SysML Activity Diagram written as a view rendered asInterconnectionDiagram of ActionFlowView; the view exposes action def Control::Controller::Run, whose graph the rendering draws with the 16 shown nodes and edges of it; laid out from control_nodes.layout.xml: 6 of 8 shown elements positioned (2 not exposed), 8 of 8 connectors routed) DecisionNode Control::Controller::Run::check _cn_check -> check +OpaqueAction Control::Controller::Run::cool _cn_cool -> cool (its body is empty, so it is written as an action that does nothing) +OpaqueAction Control::Controller::Run::heat _cn_heat -> heat (its body is empty, so it is written as an action that does nothing) Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/control_nodes.layout.golden.sysml b/tests/migrate/testdata/xmi/control_nodes.layout.golden.sysml index b986a0f31b..6ffed5b7da 100644 --- a/tests/migrate/testdata/xmi/control_nodes.layout.golden.sysml +++ b/tests/migrate/testdata/xmi/control_nodes.layout.golden.sysml @@ -24,13 +24,9 @@ package Control { fork 'fork'; succession 'fork to heat' first 'fork' then heat; succession 'fork to cool' first 'fork' then cool; - action heat { - /* body not migrated (the body is empty) */ - } + action heat; succession 'heat to join' first heat then 'join'; - action cool { - /* body not migrated (the body is empty) */ - } + action cool; succession 'cool to join' first cool then 'join'; join 'join'; succession 'join to check' first 'join' then check; diff --git a/tests/migrate/testdata/xmi/decision_else.golden.report.txt b/tests/migrate/testdata/xmi/decision_else.golden.report.txt index 064b353d2e..8f9bd98221 100644 --- a/tests/migrate/testdata/xmi/decision_else.golden.report.txt +++ b/tests/migrate/testdata/xmi/decision_else.golden.report.txt @@ -1,11 +1,8 @@ # SysML v1 to v2 migration report: decision_else.xmi # exported by Example UML Tool -# migrated 15 element(s): 14 mapped, 1 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 15 element(s): 15 mapped, 0 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (1) -OpaqueAction Recovery::Retrier::Recover::retry _de_retry -> retry (the body is kept as a comment: the body is empty) - -## mapped (14) +## mapped (15) Model Model _m (the root model's members are written at the top level) Package Recovery _pkg -> Recovery «Block» Class Recovery::Retrier _blk_retrier -> Recovery::Retrier @@ -19,4 +16,5 @@ InitialNode Recovery::Retrier::Recover:: _de_init -> start DecisionNode Recovery::Retrier::Recover::decide _de_decide -> 'decide' Comment Recovery::Retrier::Recover::decide:: _de_cmt MergeNode Recovery::Retrier::Recover::merge _de_merge -> 'merge' +OpaqueAction Recovery::Retrier::Recover::retry _de_retry -> retry (its body is empty, so it is written as an action that does nothing) Property Recovery::Retrier::attempts _attempts -> Recovery::Retrier::attempts diff --git a/tests/migrate/testdata/xmi/decision_else.golden.sysml b/tests/migrate/testdata/xmi/decision_else.golden.sysml index 8b326a9a48..25492ffb78 100644 --- a/tests/migrate/testdata/xmi/decision_else.golden.sysml +++ b/tests/migrate/testdata/xmi/decision_else.golden.sysml @@ -7,9 +7,7 @@ package Recovery { else 'merge'; comment /* Retries until the budget is spent. */ first 'decide' if attempts < 3 then retry; - action retry { - /* body not migrated (the body is empty) */ - } + action retry; first retry then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/decision_property_probability.golden.report.txt b/tests/migrate/testdata/xmi/decision_property_probability.golden.report.txt index 430957d4af..78b567ce65 100644 --- a/tests/migrate/testdata/xmi/decision_property_probability.golden.report.txt +++ b/tests/migrate/testdata/xmi/decision_property_probability.golden.report.txt @@ -1,15 +1,11 @@ # SysML v1 to v2 migration report: decision_property_probability.xmi -# migrated 20 element(s): 14 mapped, 6 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 18 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (6) +## approximated (2) ControlFlow Chooser::Route:: _eRemainder (the edge carries no «Probability»: it is weighted 1.0 - pDefault, its share of what the marked branches leave of 1, read when the decision is reached) ControlFlow Chooser::Route:: _eMissingRemainder (the edge carries no «Probability»: it is weighted 1.0 - pMissing, its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Chooser::Route::defaulted _defaulted -> defaulted (the body is kept as a comment: the body is empty) -OpaqueAction Chooser::Route::missing default _missingDefault -> 'missing default' (the body is kept as a comment: the body is empty) -OpaqueAction Chooser::Route::missing remainder _missingRemainder -> 'missing remainder' (the body is kept as a comment: the body is empty) -OpaqueAction Chooser::Route::remainder _remainder -> remainder (the body is kept as a comment: the body is empty) -## mapped (14) +## mapped (18) «Block» Class Chooser _chooser -> Chooser Activity Chooser::Route _route -> Chooser::route (written as an action usage every object of Chooser performs from creation, so its body runs on the object and reaches its features) ActivityFinalNode Chooser::Route:: _done -> final @@ -19,6 +15,10 @@ ControlFlow Chooser::Route:: _next «Probability» ControlFlow Chooser::Route:: _eMissing (the probability reads the property pMissing of the object performing the action, so a run checks it lies in [0, 1] and the branches sum to 1 when the decision is reached) ControlFlow Chooser::Route:: _finishMissing InitialNode Chooser::Route:: _initial -> start +OpaqueAction Chooser::Route::defaulted _defaulted -> defaulted (its body is empty, so it is written as an action that does nothing) +OpaqueAction Chooser::Route::missing default _missingDefault -> 'missing default' (its body is empty, so it is written as an action that does nothing) +OpaqueAction Chooser::Route::missing remainder _missingRemainder -> 'missing remainder' (its body is empty, so it is written as an action that does nothing) +OpaqueAction Chooser::Route::remainder _remainder -> remainder (its body is empty, so it is written as an action that does nothing) DecisionNode Chooser::Route::with default _withDefault -> 'with default' DecisionNode Chooser::Route::without default _withoutDefault -> 'without default' Property Chooser::pDefault _default -> Chooser::pDefault diff --git a/tests/migrate/testdata/xmi/decision_property_probability.golden.sysml b/tests/migrate/testdata/xmi/decision_property_probability.golden.sysml index 3f374c10f5..9472934ba5 100644 --- a/tests/migrate/testdata/xmi/decision_property_probability.golden.sysml +++ b/tests/migrate/testdata/xmi/decision_property_probability.golden.sysml @@ -6,24 +6,16 @@ part def Chooser { decide 'with default'; first 'with default' then defaulted { @Stochastic::Probability { p = pDefault; } } first 'with default' then remainder { @Stochastic::Probability { p = 1.0 - pDefault; } } - action defaulted { - /* body not migrated (the body is empty) */ - } + action defaulted; first defaulted then 'merge'; - action remainder { - /* body not migrated (the body is empty) */ - } + action remainder; first remainder then 'without default'; decide 'without default'; first 'without default' then 'missing default' { @Stochastic::Probability { p = pMissing; } } first 'without default' then 'missing remainder' { @Stochastic::Probability { p = 1.0 - pMissing; } } - action 'missing default' { - /* body not migrated (the body is empty) */ - } + action 'missing default'; first 'missing default' then 'merge'; - action 'missing remainder' { - /* body not migrated (the body is empty) */ - } + action 'missing remainder'; merge 'merge'; first 'merge' then final; action final terminate; diff --git a/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.report.txt b/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.report.txt index 01d06b976c..ff3384d98e 100644 --- a/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.report.txt +++ b/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.report.txt @@ -1,11 +1,7 @@ # SysML v1 to v2 migration report: decision_rejected_probability_default.xmi -# migrated 14 element(s): 12 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 14 element(s): 14 mapped, 0 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (2) -OpaqueAction Chooser::Route::defaulted _defaulted -> defaulted (the body is kept as a comment: the body is empty) -OpaqueAction Chooser::Route::missing _missingBranch -> missing (the body is kept as a comment: the body is empty) - -## mapped (12) +## mapped (14) «Block» Class Chooser _chooser -> Chooser Activity Chooser::Route _route -> Chooser::route (written as an action usage every object of Chooser performs from creation, so its body runs on the object and reaches its features) ActivityFinalNode Chooser::Route:: _final -> final @@ -15,6 +11,8 @@ ControlFlow Chooser::Route:: _finishDefault ControlFlow Chooser::Route:: _finishMissing InitialNode Chooser::Route:: _initial -> start DecisionNode Chooser::Route::decide _decide -> 'decide' +OpaqueAction Chooser::Route::defaulted _defaulted -> defaulted (its body is empty, so it is written as an action that does nothing) +OpaqueAction Chooser::Route::missing _missingBranch -> missing (its body is empty, so it is written as an action that does nothing) Property Chooser::pDefault _default -> Chooser::pDefault Property Chooser::pMissing _missing -> Chooser::pMissing Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.sysml b/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.sysml index b3349df836..965cb3a712 100644 --- a/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.sysml +++ b/tests/migrate/testdata/xmi/decision_rejected_probability_default.golden.sysml @@ -6,13 +6,9 @@ part def Chooser { decide 'decide'; first 'decide' then defaulted { @Stochastic::Probability { p = pDefault; } } first 'decide' if pMissing > 0.0 then missing { @Stochastic::Probability { p = pMissing; } } - action defaulted { - /* body not migrated (the body is empty) */ - } + action defaulted; first defaulted then 'merge'; - action missing { - /* body not migrated (the body is empty) */ - } + action missing; first missing then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/diagram_edges.golden.report.txt b/tests/migrate/testdata/xmi/diagram_edges.golden.report.txt index 37d17a744e..4f292558d2 100644 --- a/tests/migrate/testdata/xmi/diagram_edges.golden.report.txt +++ b/tests/migrate/testdata/xmi/diagram_edges.golden.report.txt @@ -4,7 +4,7 @@ ## approximated (11) Transition Behavior::Modes:::: _t_restart -> Behavior::Modes::'Halted accept Go then Running' (written as 2 transitions, one per trigger) -OpaqueBehavior Behavior::Modes::::Running::spin _st_running_do -> Behavior::Modes::Running::spin (the body is kept as a comment: the statement "keep turning" is not an assignment of a v2 expression (language English)) +OpaqueBehavior Behavior::Modes::::Running::spin _st_running_do -> Behavior::Modes::Running::spin (the body is kept as a textual representation, which is not executed: the statement "keep turning" is not an assignment of a v2 expression (language English)) Package Requirements _pkg_reqs -> RequirementsModel (written as RequirementsModel since a root package named Requirements would be hidden by the standard library's Requirements) «Satisfy» Abstraction Requirements:: _dep_satisfy_pairs (written as 2 relationships, one per client–supplier pair) «TestCase» Activity Requirements::Mass Test _tc_mass -> RequirementsModel::'Mass Test' (the test case's behavior is not migrated; only its verified requirements are) diff --git a/tests/migrate/testdata/xmi/diagram_edges.golden.sysml b/tests/migrate/testdata/xmi/diagram_edges.golden.sysml index 67bf44258a..0f5154d98f 100644 --- a/tests/migrate/testdata/xmi/diagram_edges.golden.sysml +++ b/tests/migrate/testdata/xmi/diagram_edges.golden.sysml @@ -103,9 +103,7 @@ package Behavior { state Idle; state Running { do action spin { - /* body not migrated (the statement "keep turning" is not an assignment of a v2 expression (language English)) {English}: - * keep turning - */ + rep language "English" /* keep turning*/ } } state Halted; diff --git a/tests/migrate/testdata/xmi/diagram_edges.layout.golden.report.txt b/tests/migrate/testdata/xmi/diagram_edges.layout.golden.report.txt index ef40006853..44e244a8c1 100644 --- a/tests/migrate/testdata/xmi/diagram_edges.layout.golden.report.txt +++ b/tests/migrate/testdata/xmi/diagram_edges.layout.golden.report.txt @@ -27,7 +27,7 @@ ## approximated (11) Transition Behavior::Modes:::: _t_restart -> Behavior::Modes::'Halted accept Go then Running' (written as 2 transitions, one per trigger) -OpaqueBehavior Behavior::Modes::::Running::spin _st_running_do -> Behavior::Modes::Running::spin (the body is kept as a comment: the statement "keep turning" is not an assignment of a v2 expression (language English)) +OpaqueBehavior Behavior::Modes::::Running::spin _st_running_do -> Behavior::Modes::Running::spin (the body is kept as a textual representation, which is not executed: the statement "keep turning" is not an assignment of a v2 expression (language English)) Package Requirements _pkg_reqs -> RequirementsModel (written as RequirementsModel since a root package named Requirements would be hidden by the standard library's Requirements) «Satisfy» Abstraction Requirements:: _dep_satisfy_pairs (written as 2 relationships, one per client–supplier pair) «TestCase» Activity Requirements::Mass Test _tc_mass -> RequirementsModel::'Mass Test' (the test case's behavior is not migrated; only its verified requirements are) diff --git a/tests/migrate/testdata/xmi/diagram_edges.layout.golden.sysml b/tests/migrate/testdata/xmi/diagram_edges.layout.golden.sysml index e65da95a9f..74abd4ce1a 100644 --- a/tests/migrate/testdata/xmi/diagram_edges.layout.golden.sysml +++ b/tests/migrate/testdata/xmi/diagram_edges.layout.golden.sysml @@ -132,9 +132,7 @@ package Behavior { state Idle; state Running { do action spin { - /* body not migrated (the statement "keep turning" is not an assignment of a v2 expression (language English)) {English}: - * keep turning - */ + rep language "English" /* keep turning*/ } } state Halted; diff --git a/tests/migrate/testdata/xmi/diagrams.golden.sysml b/tests/migrate/testdata/xmi/diagrams.golden.sysml index a0ce342786..db76b7dd82 100644 --- a/tests/migrate/testdata/xmi/diagrams.golden.sysml +++ b/tests/migrate/testdata/xmi/diagrams.golden.sysml @@ -33,6 +33,7 @@ package Structure { } succession 'start to ignite' first start then ignite; action ignite { + rep language "JavaScript" /* ignited = true;*/ attribute ignited : ScalarValues::Boolean; assign ignited := true; } @@ -47,6 +48,7 @@ package Structure { expose Engine; render Views::asTextualNotation; } + rep language "JavaScript" /* ignited = false;*/ first start then body; action body { attribute ignited : ScalarValues::Boolean; @@ -102,6 +104,7 @@ package Behavior { } succession 'start to start engine' first start then 'start engine'; action 'start engine' { + rep language "JavaScript" /* running = true;*/ attribute running : ScalarValues::Boolean; assign running := true; } diff --git a/tests/migrate/testdata/xmi/empty_behaviors.golden.report.txt b/tests/migrate/testdata/xmi/empty_behaviors.golden.report.txt index f5010d6b51..2d65cc24b6 100644 --- a/tests/migrate/testdata/xmi/empty_behaviors.golden.report.txt +++ b/tests/migrate/testdata/xmi/empty_behaviors.golden.report.txt @@ -14,14 +14,14 @@ StateMachine Gate::Blink _blink -> Gate::Blink ('Warm' names it as its entry act StateMachine Gate::Cycle _cycle -> Gate::Cycle (the classifier behavior is run by every object of Gate as its usage cycle) Transition Gate::Cycle::main:: _tStopOff (its do action Gate::Blink is not run: it is written as a state def) Activity Gate::Cycle::main::::effect _effGoCool -> Gate::Cycle::unnamed2::effect (written as the do action of (_tGoCool); none of its 1 action is migrated, so it runs nothing) -OpaqueBehavior Gate::Cycle::main::::effect _effGoWarm -> Gate::Cycle::unnamed3::effect (the body is kept as a comment: the statement "CALL WARM(count)" is not an assignment of a v2 expression (language Fortran)) +OpaqueBehavior Gate::Cycle::main::::effect _effGoWarm -> Gate::Cycle::unnamed3::effect (the body is kept as a textual representation, which is not executed: the statement "CALL WARM(count)" is not an assignment of a v2 expression (language Fortran)) Activity Gate::Cycle::main::::effect _effColdOff -> Gate::Cycle::unnamed5::effect (written as the do action of (_tColdOff); none of its 1 action is migrated, so it runs nothing) -OpaqueBehavior Gate::Cycle::main::::effect _effOffIdle -> Gate::Cycle::unnamed6::effect (the body is kept as a comment: the statement "CALL RESTART(count)" is not an assignment of a v2 expression (language Fortran)) +OpaqueBehavior Gate::Cycle::main::::effect _effOffIdle -> Gate::Cycle::unnamed6::effect (the body is kept as a textual representation, which is not executed: the statement "CALL RESTART(count)" is not an assignment of a v2 expression (language Fortran)) Activity Gate::Cycle::main::Busy::open _busyEntry -> Gate::Cycle::Busy::open (written as the entry action of 'Busy'; none of its 2 actions is migrated, so it runs nothing) -OpaqueBehavior Gate::Cycle::main::Busy::poll _busyDo -> Gate::Cycle::Busy::poll (the body is kept as a comment: the statement "CALL POLL(count)" is not an assignment of a v2 expression (language Fortran)) +OpaqueBehavior Gate::Cycle::main::Busy::poll _busyDo -> Gate::Cycle::Busy::poll (the body is kept as a textual representation, which is not executed: the statement "CALL POLL(count)" is not an assignment of a v2 expression (language Fortran)) Activity Gate::Cycle::main::Cool::seal _coolExit -> Gate::Cycle::Cool::seal (written as the exit action of 'Cool'; none of its 1 action is migrated, so it runs nothing) -OpaqueBehavior Gate::Cycle::main::Cool::vent _coolEntry -> Gate::Cycle::Cool::vent (the body is kept as a comment: the statement "CALL VENT(count)" is not an assignment of a v2 expression (language Fortran)) -OpaqueBehavior Gate::Cycle::main::Idle::flush _idleExit -> Gate::Cycle::Idle::flush (the body is kept as a comment: the statement "CALL FLUSH(count)" is not an assignment of a v2 expression (language Fortran)) +OpaqueBehavior Gate::Cycle::main::Cool::vent _coolEntry -> Gate::Cycle::Cool::vent (the body is kept as a textual representation, which is not executed: the statement "CALL VENT(count)" is not an assignment of a v2 expression (language Fortran)) +OpaqueBehavior Gate::Cycle::main::Idle::flush _idleExit -> Gate::Cycle::Idle::flush (the body is kept as a textual representation, which is not executed: the statement "CALL FLUSH(count)" is not an assignment of a v2 expression (language Fortran)) Activity Gate::Cycle::main::Idle::spin _idleDo -> Gate::Cycle::Idle::spin (written as the do action of 'Idle'; none of its 1 action is migrated, so it runs nothing) State Gate::Cycle::main::Warm _warm -> Warm (its entry action Gate::Blink is not run: it is written as a state def) Activity Gate::Reset _reset -> Gate::Reset (also run as the do action of (_tGoHot); also run as the do action of (_tHotIdle)) diff --git a/tests/migrate/testdata/xmi/empty_behaviors.golden.sysml b/tests/migrate/testdata/xmi/empty_behaviors.golden.sysml index ad389db61e..72242ecd6a 100644 --- a/tests/migrate/testdata/xmi/empty_behaviors.golden.sysml +++ b/tests/migrate/testdata/xmi/empty_behaviors.golden.sysml @@ -19,9 +19,7 @@ occurrence def Gate { } } exit action flush { - /* body not migrated (the statement "CALL FLUSH(count)" is not an assignment of a v2 expression (language Fortran)) {Fortran}: - * CALL FLUSH(count) - */ + rep language "Fortran" /* CALL FLUSH(count)*/ } } state Busy { @@ -40,17 +38,13 @@ occurrence def Gate { metadata MigrationMetadata::StandIn about 'fork'; } do action poll { - /* body not migrated (the statement "CALL POLL(count)" is not an assignment of a v2 expression (language Fortran)) {Fortran}: - * CALL POLL(count) - */ + rep language "Fortran" /* CALL POLL(count)*/ } exit action close; } state Cool { entry action vent { - /* body not migrated (the statement "CALL VENT(count)" is not an assignment of a v2 expression (language Fortran)) {Fortran}: - * CALL VENT(count) - */ + rep language "Fortran" /* CALL VENT(count)*/ } do action wait; exit action seal { @@ -79,9 +73,7 @@ occurrence def Gate { then Cool; transition first Cool accept Go do action effect { - /* body not migrated (the statement "CALL WARM(count)" is not an assignment of a v2 expression (language Fortran)) {Fortran}: - * CALL WARM(count) - */ + rep language "Fortran" /* CALL WARM(count)*/ } then Warm; transition first Warm @@ -97,9 +89,7 @@ occurrence def Gate { then Off; transition first Off do action effect { - /* body not migrated (the statement "CALL RESTART(count)" is not an assignment of a v2 expression (language Fortran)) {Fortran}: - * CALL RESTART(count) - */ + rep language "Fortran" /* CALL RESTART(count)*/ } then Idle; transition first Off accept Go diff --git a/tests/migrate/testdata/xmi/figures.golden.report.txt b/tests/migrate/testdata/xmi/figures.golden.report.txt index e807c66ff3..e130107d6f 100644 --- a/tests/migrate/testdata/xmi/figures.golden.report.txt +++ b/tests/migrate/testdata/xmi/figures.golden.report.txt @@ -1,12 +1,12 @@ # SysML v1 to v2 migration report: figures.xmi # exported by Example UML Tool -# migrated 90 element(s): 71 mapped, 17 approximated, 2 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 90 element(s): 73 mapped, 15 approximated, 2 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) ## unmapped (2) «CollectThingsOnDiagram» CallBehaviorAction Plant Viewpoints::Shown Viewpoint::Shown Method::Collect Things On Diagram _st_shown_collect (it collects what the SysML Block Definition Diagram 'Partial' shows beyond the 1 element the tool lists, whose symbols cannot be read and what the SysML Block Definition Diagram 'Unlisted' shows, which the archive does not record) «TableStructure» StructuredActivityNode Plant Viewpoints::Shown Viewpoint::Shown Method::Shown Blocks _st_shown_table (the elements it shows pass through «CollectThingsOnDiagram» Plant Viewpoints::Shown Viewpoint::Shown Method::Collect Things On Diagram is not migrated: it collects what the SysML Block Definition Diagram 'Partial' shows beyond the 1 element the tool lists, whose symbols cannot be read and what the SysML Block Definition Diagram 'Unlisted' shows, which the archive does not record) -## approximated (17) +## approximated (15) Activity Plant Viewpoints::Figures Viewpoint::Figures Method _act_figures -> 'Plant Viewpoints'::'Figures Viewpoint'::'Figures Method' (the classifier behavior is run by every object of Plant Viewpoints::Figures Viewpoint as its usage figures Method) «Image» CallBehaviorAction Plant Viewpoints::Figures Viewpoint::Figures Method::Image _figures_image -> Image (a step with no behavior and no duration; it passes the token on) Activity Plant Viewpoints::Pictures Viewpoint::Pictures Method _act_pictures -> 'Plant Viewpoints'::'Pictures Viewpoint'::'Pictures Method' (the classifier behavior is run by every object of Plant Viewpoints::Pictures Viewpoint as its usage pictures Method) @@ -20,12 +20,10 @@ Activity Plant Viewpoints::Shown Viewpoint::Shown Method _act_shown -> 'Plant Vi «TableAttributeColumn» CallBehaviorAction Plant Viewpoints::Shown Viewpoint::Shown Method::Shown Blocks::Name _shown_col_name -> Name (a step with no behavior and no duration; it passes the token on) Diagram Plant::Blank _diag_blank -> Plant::Blank (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram shows nothing; the view exposes nothing) Diagram Plant::Poster _diag_poster -> Plant::Poster (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram shows nothing; the view exposes nothing) -OpaqueAction Plant::Pump::Prime::Fill _prime_fill -> Fill (the body is kept as a comment: the body is empty) -OpaqueAction Plant::Pump::Prime::Vent _prime_vent -> Vent (the body is kept as a comment: the body is empty) Diagram Plant::Silent _diag_silent -> Plant::Silent (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram shows nothing; the view exposes nothing) Diagram Plant::Unlisted _diag_unlisted -> Plant::Unlisted (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram shows nothing; the view exposes nothing) -## mapped (71) +## mapped (73) Model Model _m (the root model's members are written at the top level) Package Plant _pkg_plant -> Plant Package Plant Documents _pkg_docs -> 'Plant Documents' @@ -92,7 +90,9 @@ ActivityFinalNode Plant::Pump::Prime:: _prime_final -> final ControlFlow Plant::Pump::Prime:: _prime_e2 ControlFlow Plant::Pump::Prime:: _prime_e3 InitialNode Plant::Pump::Prime:: _prime_init -> start +OpaqueAction Plant::Pump::Prime::Fill _prime_fill -> Fill (its body is empty, so it is written as an action that does nothing) Diagram Plant::Pump::Prime::Priming _diag_prime -> Plant::Pump::Prime::Priming (a SysML Activity Diagram written as a view rendered asInterconnectionDiagram of ActionFlowView; the view exposes action def Plant::Pump::Prime, whose graph the rendering draws with the 2 shown nodes and edges of it) +OpaqueAction Plant::Pump::Prime::Vent _prime_vent -> Vent (its body is empty, so it is written as an action that does nothing) Diagram Plant::Roster _diag_roster -> Plant::Roster (a Generic Table written as a view rendered asElementTable) Diagram Plant::Stale _diag_stale -> Plant::Stale (a SysML Block Definition Diagram written as a view rendered asTreeDiagram) Signal Plant::Start _sig_start -> Plant::Start diff --git a/tests/migrate/testdata/xmi/figures.golden.sysml b/tests/migrate/testdata/xmi/figures.golden.sysml index c612cc9168..a81223594a 100644 --- a/tests/migrate/testdata/xmi/figures.golden.sysml +++ b/tests/migrate/testdata/xmi/figures.golden.sysml @@ -19,13 +19,9 @@ package Plant { render Views::asInterconnectionDiagram; } first start then Fill; - action Fill { - /* body not migrated (the body is empty) */ - } + action Fill; first Fill then Vent; - action Vent { - /* body not migrated (the body is empty) */ - } + action Vent; first Vent then final; action final terminate; metadata MigrationMetadata::SynthesizedName about final; diff --git a/tests/migrate/testdata/xmi/figures.mdzip.golden.report.txt b/tests/migrate/testdata/xmi/figures.mdzip.golden.report.txt index 8005ad6d97..ec2c2ed2c5 100644 --- a/tests/migrate/testdata/xmi/figures.mdzip.golden.report.txt +++ b/tests/migrate/testdata/xmi/figures.mdzip.golden.report.txt @@ -1,6 +1,6 @@ # SysML v1 to v2 migration report: figures.mdzip # exported by Example UML Tool -# migrated 90 element(s): 75 mapped, 14 approximated, 1 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to); laid out 7 of 9 diagrams from the diagrams' own symbol streams: 7 elements positioned, 2 connectors routed, 2 styled, 4 notes; wrote 1 image file(s) +# migrated 90 element(s): 77 mapped, 12 approximated, 1 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to); laid out 7 of 9 diagrams from the diagrams' own symbol streams: 7 elements positioned, 2 connectors routed, 2 styled, 4 notes; wrote 1 image file(s) ## layout # source: the diagrams' own symbol streams @@ -15,7 +15,7 @@ ## unmapped (1) ImageShape Plant::Pump::Modes::Pump Modes _sym_torn (the pasted image's bytes do not read: octet 19 is "xx", not a hexadecimal byte) -## approximated (14) +## approximated (12) Activity Plant Viewpoints::Figures Viewpoint::Figures Method _act_figures -> 'Plant Viewpoints'::'Figures Viewpoint'::'Figures Method' (the classifier behavior is run by every object of Plant Viewpoints::Figures Viewpoint as its usage figures Method) «Image» CallBehaviorAction Plant Viewpoints::Figures Viewpoint::Figures Method::Image _figures_image -> Image (a step with no behavior and no duration; it passes the token on) Activity Plant Viewpoints::Pictures Viewpoint::Pictures Method _act_pictures -> 'Plant Viewpoints'::'Pictures Viewpoint'::'Pictures Method' (the classifier behavior is run by every object of Plant Viewpoints::Pictures Viewpoint as its usage pictures Method) @@ -26,12 +26,10 @@ Activity Plant Viewpoints::Shown Viewpoint::Shown Method _act_shown -> 'Plant Vi «TableAttributeColumn» CallBehaviorAction Plant Viewpoints::Shown Viewpoint::Shown Method::Shown Blocks::Name _shown_col_name -> Name (a step with no behavior and no duration; it passes the token on) Diagram Plant::Blank _diag_blank -> Plant::Blank (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram draws nothing at all; the view exposes nothing) Diagram Plant::Pump::Modes::Pump Modes _diag_modes -> Plant::Pump::Modes::'Pump Modes' (a SysML State Machine Diagram written as a view rendered asInterconnectionDiagram of StateTransitionView; the view exposes state def Plant::Pump::Modes, whose graph the rendering draws with the 5 shown nodes and edges of it; laid out from the diagram's own symbol stream: 2 of 3 shown elements positioned (1 not exposed), 2 of 2 connectors routed, 2 pasted images written as images/Plant_from_the_north.png, 2 pasted images not written: the pasted image "logo.png" is not in the archive and the pasted image of symbol _sym_torn has bytes that do not read (octet 19 is "xx", not a hexadecimal byte), 2 of 2 symbols drawn in their own colours or font styled, 3 notes written, 2 anchored, free symbols not represented: 2 ImageShape) -OpaqueAction Plant::Pump::Prime::Fill _prime_fill -> Fill (the body is kept as a comment: the body is empty) -OpaqueAction Plant::Pump::Prime::Vent _prime_vent -> Vent (the body is kept as a comment: the body is empty) Diagram Plant::Roster _diag_roster -> Plant::Roster (a Generic Table written as a view rendered asElementTable; laid out from the diagram's own symbol stream: 1 of 1 shown elements positioned, 0 of 0 connectors routed, 1 pasted image written as images/Plant_from_the_north.png, which a view rendered asElementTable does not draw) Diagram Plant::Silent _diag_silent -> Plant::Silent (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; the diagram shows nothing; the view exposes nothing) -## mapped (75) +## mapped (77) Model Model _m (the root model's members are written at the top level) Package Plant _pkg_plant -> Plant Package Plant Documents _pkg_docs -> 'Plant Documents' @@ -102,7 +100,9 @@ ActivityFinalNode Plant::Pump::Prime:: _prime_final -> final ControlFlow Plant::Pump::Prime:: _prime_e2 ControlFlow Plant::Pump::Prime:: _prime_e3 InitialNode Plant::Pump::Prime:: _prime_init -> start +OpaqueAction Plant::Pump::Prime::Fill _prime_fill -> Fill (its body is empty, so it is written as an action that does nothing) Diagram Plant::Pump::Prime::Priming _diag_prime -> Plant::Pump::Prime::Priming (a SysML Activity Diagram written as a view rendered asInterconnectionDiagram of ActionFlowView; the view exposes action def Plant::Pump::Prime, whose graph the rendering draws with the 2 shown nodes and edges of it) +OpaqueAction Plant::Pump::Prime::Vent _prime_vent -> Vent (its body is empty, so it is written as an action that does nothing) Diagram Plant::Stale _diag_stale -> Plant::Stale (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; laid out from the diagram's own symbol stream: 1 of 1 shown elements positioned, 0 of 0 connectors routed) Signal Plant::Start _sig_start -> Plant::Start «Block» Class Plant::Tank _blk_tank -> Plant::Tank diff --git a/tests/migrate/testdata/xmi/figures.mdzip.golden.sysml b/tests/migrate/testdata/xmi/figures.mdzip.golden.sysml index e4bbabe462..6572e3f252 100644 --- a/tests/migrate/testdata/xmi/figures.mdzip.golden.sysml +++ b/tests/migrate/testdata/xmi/figures.mdzip.golden.sysml @@ -30,13 +30,9 @@ package Plant { render Views::asInterconnectionDiagram; } first start then Fill; - action Fill { - /* body not migrated (the body is empty) */ - } + action Fill; first Fill then Vent; - action Vent { - /* body not migrated (the body is empty) */ - } + action Vent; first Vent then final; action final terminate; metadata MigrationMetadata::SynthesizedName about final; diff --git a/tests/migrate/testdata/xmi/layout.golden.sysml b/tests/migrate/testdata/xmi/layout.golden.sysml index 75e80d2069..acc888abdf 100644 --- a/tests/migrate/testdata/xmi/layout.golden.sysml +++ b/tests/migrate/testdata/xmi/layout.golden.sysml @@ -30,6 +30,7 @@ package Behavior { action def Drive { succession 'start to step' first start then 'step'; action 'step' { + rep language "JavaScript" /* going = true;*/ attribute going : ScalarValues::Boolean; assign going := true; } diff --git a/tests/migrate/testdata/xmi/layout.layout.golden.sysml b/tests/migrate/testdata/xmi/layout.layout.golden.sysml index 3163651495..2d124e047c 100644 --- a/tests/migrate/testdata/xmi/layout.layout.golden.sysml +++ b/tests/migrate/testdata/xmi/layout.layout.golden.sysml @@ -38,6 +38,7 @@ package Behavior { action def Drive { succession 'start to step' first start then 'step'; action 'step' { + rep language "JavaScript" /* going = true;*/ attribute going : ScalarValues::Boolean; assign going := true; } diff --git a/tests/migrate/testdata/xmi/meter.golden.report.txt b/tests/migrate/testdata/xmi/meter.golden.report.txt index aa9fab426a..4cedaa1dd1 100644 --- a/tests/migrate/testdata/xmi/meter.golden.report.txt +++ b/tests/migrate/testdata/xmi/meter.golden.report.txt @@ -4,13 +4,13 @@ ## approximated (38) ControlFlow Meter::Arm:: _a2 (the guard [{JavaScript} ready and enabled] is kept as a comment and the edge written unguarded: the text "and" is not expression syntax: text follows the expression) Parameter Meter::Arm::armed _armed -> Meter::arm::armed (default value not migrated: the types at "Math.sqrt(total)" disagree: the expression is a Real, not the Boolean wanted) -OpaqueAction Meter::Arm::fuse _fuse -> fuse (the body is kept as a comment: the text "and" is not expression syntax: a statement ends at `;` or a newline) -OpaqueAction Meter::Flip::alias _alias -> 'alias' (the body is kept as a comment: the construct "==" is outside the translated subset: Java compares strings by identity with `==`, which a comparison of their values does not reproduce; `equals` compares their content) -OpaqueAction Meter::Flip::bump _bump -> bump (the body is kept as a comment: the types at "+" disagree: an operand is a Mode, not a number) -OpaqueAction Meter::Flip::huge _huge -> huge (the body is kept as a comment: the construct "9007199254740993" is outside the translated subset: a script rounds a whole number beyond 9007199254740991 to the nearest floating-point value) -OpaqueAction Meter::Flip::match _match -> match (the body is kept as a comment: the types at "mode =" disagree: a Gauge is assigned to the Mode mode holds) -OpaqueAction Meter::Flip::narrow _narrow -> narrow (the body is kept as a comment: the types at "hand =" disagree: a Gauge is assigned to the Needle hand holds) -OpaqueAction Meter::Flip::pick _pick -> pick (the body is kept as a comment: the types at "count =" disagree: a Mode is assigned to the Integer count holds) +OpaqueAction Meter::Arm::fuse _fuse -> fuse (the body is kept as a textual representation, which is not executed: the text "and" is not expression syntax: a statement ends at `;` or a newline) +OpaqueAction Meter::Flip::alias _alias -> 'alias' (the body is kept as a textual representation, which is not executed: the construct "==" is outside the translated subset: Java compares strings by identity with `==`, which a comparison of their values does not reproduce; `equals` compares their content) +OpaqueAction Meter::Flip::bump _bump -> bump (the body is kept as a textual representation, which is not executed: the types at "+" disagree: an operand is a Mode, not a number) +OpaqueAction Meter::Flip::huge _huge -> huge (the body is kept as a textual representation, which is not executed: the construct "9007199254740993" is outside the translated subset: a script rounds a whole number beyond 9007199254740991 to the nearest floating-point value) +OpaqueAction Meter::Flip::match _match -> match (the body is kept as a textual representation, which is not executed: the types at "mode =" disagree: a Gauge is assigned to the Mode mode holds) +OpaqueAction Meter::Flip::narrow _narrow -> narrow (the body is kept as a textual representation, which is not executed: the types at "hand =" disagree: a Gauge is assigned to the Needle hand holds) +OpaqueAction Meter::Flip::pick _pick -> pick (the body is kept as a textual representation, which is not executed: the types at "count =" disagree: a Mode is assigned to the Integer count holds) OpaqueAction Meter::Flip::same _same -> same (the Java body is translated to v2; label holds no initial value and the body reads it before assigning it, so a run reaching the read first stops) OpaqueAction Meter::Measure::cc _cc -> cc (the JavaCC body is written as v2 assignments) ValuePin Meter::Measure::judge::ok _judgeok -> Meter::measure::judge.ok (the value pin's value {JavaScript} Math.sqrt(total) is not written: the types at "Math.sqrt(total)" disagree: the expression is a Real, not the Boolean wanted) @@ -18,20 +18,20 @@ OpaqueAction Meter::Measure::log _log -> log (the JavaScript body is translated OpaqueAction Meter::Measure::shout _shout -> shout (the JavaScript body is translated to v2; the console print println(…) is left out, as it writes to the tool's console and changes nothing of the model) ObjectFlow Meter::Skip:: _o2 (the flow is kept as a comment: the body of 'idle' never assigns 'z', so no value leaves it) ObjectFlow Meter::Skip:: _o3 (the flow is kept as a comment: its source 'dark' is not migrated, so no value reaches 'q') -OpaqueAction Meter::Skip::dark _dark -> dark (the body is kept as a comment: the statement assigns q, which nothing visible from Meter::Skip::dark is called (language Python)) +OpaqueAction Meter::Skip::dark _dark -> dark (the body is kept as a textual representation, which is not executed: the statement assigns q, which nothing visible from Meter::Skip::dark is called (language Python)) OpaqueAction Meter::Skip::drain _drain -> drain (the JavaScript body is translated to v2; total must hold a value, so it is assigned only when w, which may hold none, holds one) InputPin Meter::Skip::drain::w _drainw -> Meter::skip::drain.w (it is declared admitting no value: 'dark', which feeds it, produces no value) OpaqueAction Meter::Skip::sink _sink -> sink (the JavaScript body is translated to v2; total must hold a value, so it is assigned only when w, which may hold none, holds one) InputPin Meter::Skip::sink::w _sinkw -> Meter::skip::sink.w (it is declared admitting no value: 'idle', which feeds it, produces no value) ActivityPartition Meter::Sweep::bank _bank_lane (a partition names who performs its nodes; it is kept as a comment; the partition represents a Bank through the part Meter::banks, but the multiplicity of Meter::banks is not written in numbers, so whether it holds one value cannot be told) ActivityPartition Meter::Sweep::banks _banks_lane (a partition names who performs its nodes; it is kept as a comment; the partition represents Meter::banks, but the multiplicity of Meter::banks is not written in numbers, so whether it holds one value cannot be told) -OpaqueAction Meter::Sweep::calib _calib -> calib (the body is kept as a comment: the construct "reading" is outside the translated subset: this.cells is a collection, so the assignment would write through several objects) -OpaqueAction Meter::Sweep::charge _charge -> charge (the body is kept as a comment: the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::charge is called level) -OpaqueAction Meter::Sweep::reset _reset -> reset (the body is kept as a comment: the construct "cells.reading" is outside the translated subset: cells is a collection, so the assignment would write through several objects) -OpaqueAction Meter::Sweep::settle _settle -> settle (the body is kept as a comment: the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::settle is called level) -OpaqueAction Meter::Sweep::span _span -> span (the body is kept as a comment: the types at "total =" disagree: one side is a collection and the other a single value) -OpaqueAction Meter::Sweep::spread _spread -> spread (the body is kept as a comment: the types at "+" disagree: an operand is a collection, not a number) -OpaqueAction Meter::Sweep::tally _tally -> tally (the body is kept as a comment: the construct "banks.level" is outside the translated subset: the multiplicity of Meter::banks is not written in numbers, so whether it holds one value cannot be told) +OpaqueAction Meter::Sweep::calib _calib -> calib (the body is kept as a textual representation, which is not executed: the construct "reading" is outside the translated subset: this.cells is a collection, so the assignment would write through several objects) +OpaqueAction Meter::Sweep::charge _charge -> charge (the body is kept as a textual representation, which is not executed: the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::charge is called level) +OpaqueAction Meter::Sweep::reset _reset -> reset (the body is kept as a textual representation, which is not executed: the construct "cells.reading" is outside the translated subset: cells is a collection, so the assignment would write through several objects) +OpaqueAction Meter::Sweep::settle _settle -> settle (the body is kept as a textual representation, which is not executed: the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::settle is called level) +OpaqueAction Meter::Sweep::span _span -> span (the body is kept as a textual representation, which is not executed: the types at "total =" disagree: one side is a collection and the other a single value) +OpaqueAction Meter::Sweep::spread _spread -> spread (the body is kept as a textual representation, which is not executed: the types at "+" disagree: an operand is a collection, not a number) +OpaqueAction Meter::Sweep::tally _tally -> tally (the body is kept as a textual representation, which is not executed: the construct "banks.level" is outside the translated subset: the multiplicity of Meter::banks is not written in numbers, so whether it holds one value cannot be told) CallBehaviorAction Meter::Sweep::ticking _ticking -> ticking (its swimlane represents this.cells, a collection of objects, so none of them performs the call, which runs in the caller's context; the behavior acts on a Gauge through its parameter context, which is left unbound: the caller is a Meter, which is no Gauge and has 4 parts that are one, so no one of them is chosen; the behavior belongs to Gauge and runs here in the caller's context) Property Meter::banks _banks -> Meter::banks (multiplicity 1..n is not a range of natural numbers and is not written) Property Meter::gain _gain -> Meter::gain (default value not migrated: the call "total" is not in the translated function table) diff --git a/tests/migrate/testdata/xmi/meter.golden.sysml b/tests/migrate/testdata/xmi/meter.golden.sysml index a93fab9198..f1f290389d 100644 --- a/tests/migrate/testdata/xmi/meter.golden.sysml +++ b/tests/migrate/testdata/xmi/meter.golden.sysml @@ -59,15 +59,18 @@ part def Meter { action sense { in x : ScalarValues::Real[1] = 1.5; out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = x * 2;*/ assign y := x * 2; } action record { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* total = v + 1; peak = Math.max(v);*/ assign total := v + 1; assign peak := v; } first record then split; action split { + rep language "Java" /* half = ticks / 2; ratio = total / 2; floored = Math.floor(total) / 8; rounded = Math.round(total) / 8;*/ assign half := OpenSysMLMathFunctions::quotient(ticks, 2); assign ratio := total / 2; assign floored := RealFunctions::floor(total) / 8; @@ -75,10 +78,12 @@ part def Meter { } first split then quarterly; action quarterly { + rep language "Java 1.8.0_202" /* quarter = ticks / 4;*/ assign quarter := OpenSysMLMathFunctions::quotient(ticks, 4); } first quarterly then cc; action cc { + rep language "JavaCC" /* eighth = ticks / 8;*/ assign eighth := ticks / 8; } first cc then judge; @@ -86,14 +91,20 @@ part def Meter { in ok : ScalarValues::Boolean[1]; in n : ScalarValues::Integer[1] = RealFunctions::floor(total); in m : ScalarValues::Integer[1] = 2; + rep language "JavaScript" /* count = n + m;*/ assign count := n + m; } first judge then log; action log { + rep language "JavaScript" /* print("total so far: " + (total + 1)); + * ticks = ticks + 1; + * println(ticks);*/ assign ticks := ticks + 1; } first log then shout; - action shout; + action shout { + rep language "JavaScript" /* println("done");*/ + } first shout then final; action final terminate; succession flow of ScalarValues::Real from sense.y to record.v; @@ -103,23 +114,24 @@ part def Meter { first start then idle; action idle { out z : ScalarValues::Real[1]; + rep language "JavaScript" /* total = total * 1;*/ assign total := total * 1; } first idle then sink; action sink { in w : ScalarValues::Real[0..1]; + rep language "JavaScript" /* total = w;*/ if w->SequenceFunctions::notEmpty() { assign total := w; } } first sink then dark; action dark { out q : ScalarValues::Real[1]; - /* body not migrated (the statement assigns q, which nothing visible from Meter::Skip::dark is called (language Python)) {Python}: - * q = 1 - */ + rep language "Python" /* q = 1*/ } first dark then drain; action drain { in w : ScalarValues::Real[0..1]; + rep language "JavaScript" /* total = w;*/ if w->SequenceFunctions::notEmpty() { assign total := w; } } first drain then final; @@ -131,52 +143,43 @@ part def Meter { action flip { first start then bump; action bump { - /* body not migrated (the types at "+" disagree: an operand is a Mode, not a number) {JavaScript}: - * count = mode + 1; - */ + rep language "JavaScript" /* count = mode + 1;*/ } first bump then pick; action pick { - /* body not migrated (the types at "count =" disagree: a Mode is assigned to the Integer count holds) {JavaScript}: - * count = mode; - */ + rep language "JavaScript" /* count = mode;*/ } first pick then match; action match { - /* body not migrated (the types at "mode =" disagree: a Gauge is assigned to the Mode mode holds) {JavaScript}: - * mode = dial; - */ + rep language "JavaScript" /* mode = dial;*/ } first match then widen; action widen { + rep language "JavaScript" /* dial = hand;*/ assign dial := hand; } first widen then narrow; action narrow { - /* body not migrated (the types at "hand =" disagree: a Gauge is assigned to the Needle hand holds) {JavaScript}: - * hand = dial; - */ + rep language "JavaScript" /* hand = dial;*/ } first narrow then huge; action huge { - /* body not migrated (the construct "9007199254740993" is outside the translated subset: a script rounds a whole number beyond 9007199254740991 to the nearest floating-point value) {JavaScript}: - * count = 9007199254740993; - */ + rep language "JavaScript" /* count = 9007199254740993;*/ } first huge then smile; action smile { + rep language "JavaScript" /* label = "\uD83D\uDE00"; count = 9007199254740991;*/ assign label := "😀"; assign count := 9007199254740991; } first smile then same; action same { + rep language "Java" /* lit = label.equals("\uD83D\uDE00");*/ assign lit := label == "😀"; } first same then 'alias'; action 'alias' { - /* body not migrated (the construct "==" is outside the translated subset: Java compares strings by identity with `==`, which a comparison of their values does not reproduce; `equals` compares their content) {Java}: - * lit = label == "\uD83D\uDE00"; - */ + rep language "Java" /* lit = label == "\uD83D\uDE00";*/ } first 'alias' then final; action final terminate; @@ -189,13 +192,12 @@ part def Meter { in limit : ScalarValues::Integer[1] default = count + 1; first start then fuse; action fuse { - /* body not migrated (the text "and" is not expression syntax: a statement ends at `;` or a newline) {JavaScript}: - * flag = ready and enabled - */ + rep language "JavaScript" /* flag = ready and enabled*/ } /* guard not migrated: [{JavaScript} ready and enabled] — the text "and" is not expression syntax: text follows the expression */ first fuse then latch; action latch { + rep language "JavaScript" /* flag = ready && enabled*/ assign flag := ready and enabled; } first latch then final; @@ -205,51 +207,38 @@ part def Meter { action sweep { first start then spread; action spread { - /* body not migrated (the types at "+" disagree: an operand is a collection, not a number) {JavaScript}: - * total = cells.reading + 1; - */ + rep language "JavaScript" /* total = cells.reading + 1;*/ } first spread then reset; action reset { - /* body not migrated (the construct "cells.reading" is outside the translated subset: cells is a collection, so the assignment would write through several objects) {JavaScript}: - * cells.reading = 1; - */ + rep language "JavaScript" /* cells.reading = 1;*/ } first reset then peak; action peak { + rep language "JavaScript" /* total = java.util.Collections.max(cells.reading);*/ assign total := cells.reading->ControlFunctions::reduce { in x; in y; RealFunctions::max(x, y) }; } first peak then calib; action calib { - /* body not migrated (the construct "reading" is outside the translated subset: this.cells is a collection, so the assignment would write through several objects) {JavaScript}: - * reading = 2; - */ + rep language "JavaScript" /* reading = 2;*/ } first calib then span; action span { - /* body not migrated (the types at "total =" disagree: one side is a collection and the other a single value) {JavaScript}: - * total = reading; - */ + rep language "JavaScript" /* total = reading;*/ } first span then ticking; action ticking : Gauge::Tick; first ticking then tally; action tally { - /* body not migrated (the construct "banks.level" is outside the translated subset: the multiplicity of Meter::banks is not written in numbers, so whether it holds one value cannot be told) {JavaScript}: - * total = banks.level + 1; - */ + rep language "JavaScript" /* total = banks.level + 1;*/ } first tally then charge; action charge { - /* body not migrated (the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::charge is called level) {JavaScript}: - * level = 1; - */ + rep language "JavaScript" /* level = 1;*/ } first charge then settle; action settle { - /* body not migrated (the name "level" resolves to nothing readable: nothing visible from Meter::Sweep::settle is called level) {JavaScript}: - * level = 0; - */ + rep language "JavaScript" /* level = 0;*/ } first settle then final; action final terminate; diff --git a/tests/migrate/testdata/xmi/montecarlo_case.golden.sysml b/tests/migrate/testdata/xmi/montecarlo_case.golden.sysml index 36ffe00a67..467f06c4af 100644 --- a/tests/migrate/testdata/xmi/montecarlo_case.golden.sysml +++ b/tests/migrate/testdata/xmi/montecarlo_case.golden.sysml @@ -7,6 +7,7 @@ part def Sensor { perform action settle { first start then 'start timer'; action 'start timer' { + rep language "JavaScript" /* settleTime = simtime;*/ assign settleTime := localClock.currentTime; } first 'start timer' then wait; @@ -15,6 +16,7 @@ part def Sensor { action settle; first settle then 'stop timer'; action 'stop timer' { + rep language "JavaScript" /* settleTime = simtime - settleTime;*/ assign settleTime := localClock.currentTime - settleTime; } first 'stop timer' then final; diff --git a/tests/migrate/testdata/xmi/nested_context.golden.sysml b/tests/migrate/testdata/xmi/nested_context.golden.sysml index 68a3aa0cef..d06fad3b1d 100644 --- a/tests/migrate/testdata/xmi/nested_context.golden.sysml +++ b/tests/migrate/testdata/xmi/nested_context.golden.sysml @@ -52,6 +52,7 @@ part def Host { action direct { first start then 'write state data'; action 'write state data' { + rep language "JavaScript Rhino" /* k = 2*/ assign 'aO Sequencer'.k := 2; } first 'write state data' then 'decide'; diff --git a/tests/migrate/testdata/xmi/object_flows.golden.report.txt b/tests/migrate/testdata/xmi/object_flows.golden.report.txt index afab679ce1..da6466c9f6 100644 --- a/tests/migrate/testdata/xmi/object_flows.golden.report.txt +++ b/tests/migrate/testdata/xmi/object_flows.golden.report.txt @@ -1,11 +1,10 @@ # SysML v1 to v2 migration report: object_flows.xmi -# migrated 126 element(s): 124 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 126 element(s): 125 mapped, 1 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (2) +## approximated (1) OpaqueAction Pipeline::Count::step _cs -> 'step' (the JavaScript body is translated to v2; several edges leave the node, which a fork fork carries) -OpaqueAction Pipeline::Pair::keep _pc -> keep (the body is kept as a comment: the body is empty) -## mapped (124) +## mapped (125) Model Model _m (the root model's members are written at the top level) «Block» Class Pipeline _pipe -> Pipeline Activity Pipeline::Count _count -> Pipeline::count (written as an action usage of Pipeline, which a call on an object performs, so its body runs on the object and reaches its features) @@ -87,6 +86,7 @@ ForkNode Pipeline::Pair::both _pfk -> both JoinNode Pipeline::Pair::gather _pj -> gather OpaqueAction Pipeline::Pair::high _ph -> high (the JavaScript body is translated to v2) OutputPin Pipeline::Pair::high::y _phy -> Pipeline::Pair::high.y +OpaqueAction Pipeline::Pair::keep _pc -> keep (its body is empty, so it is written as an action that does nothing) InputPin Pipeline::Pair::keep::v _pcv -> Pipeline::Pair::keep.v OpaqueAction Pipeline::Pair::low _pl -> low (the JavaScript body is translated to v2) OutputPin Pipeline::Pair::low::y _ply -> Pipeline::Pair::low.y diff --git a/tests/migrate/testdata/xmi/object_flows.golden.sysml b/tests/migrate/testdata/xmi/object_flows.golden.sysml index b2611d29f8..3e6acf4437 100644 --- a/tests/migrate/testdata/xmi/object_flows.golden.sysml +++ b/tests/migrate/testdata/xmi/object_flows.golden.sysml @@ -7,10 +7,12 @@ part def Pipeline { first start then produce; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 2.5;*/ assign y := 2.5; } action consume { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } first consume then final; @@ -22,10 +24,12 @@ part def Pipeline { first start then produce; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 4.0;*/ assign y := 4.0; } action consume { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } first consume then final; @@ -38,6 +42,7 @@ part def Pipeline { first start then produce; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 3.0;*/ assign y := 3.0; } fork copy { @@ -47,11 +52,13 @@ part def Pipeline { } action left { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } first left then both; action right { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* b = v * 2;*/ assign b := v * 2; } first right then both; @@ -67,14 +74,17 @@ part def Pipeline { first start then produce; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 5.0;*/ assign y := 5.0; } action left { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } action right { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* b = v + 1;*/ assign b := v + 1; } succession flow of ScalarValues::Real from produce.y to left.v; @@ -87,9 +97,11 @@ part def Pipeline { first 'start both' then warm; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 7.0;*/ assign y := 7.0; } action warm { + rep language "JavaScript" /* n = n + 1;*/ assign n := n + 1; } first warm then ready; @@ -99,6 +111,7 @@ part def Pipeline { } action consume { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } first consume then final; @@ -114,10 +127,12 @@ part def Pipeline { first which if not on then low; action high { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 9.0;*/ assign y := 9.0; } action low { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 1.0;*/ assign y := 1.0; } merge pick { @@ -127,6 +142,7 @@ part def Pipeline { } action consume { in v : ScalarValues::Real[1]; + rep language "JavaScript" /* a = v;*/ assign a := v; } first consume then final; @@ -140,6 +156,7 @@ part def Pipeline { first start then zero; action zero { out y : ScalarValues::Integer[1]; + rep language "JavaScript" /* y = 0;*/ assign y := 0; } merge again { @@ -150,6 +167,8 @@ part def Pipeline { action 'step' { in x : ScalarValues::Integer[1]; out y : ScalarValues::Integer[1]; + rep language "JavaScript" /* y = x + 1; + * n = y;*/ assign y := x + 1; assign n := y; } @@ -174,10 +193,12 @@ part def Pipeline { first both then high; action low { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 1.5;*/ assign y := 1.5; } action high { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 8.5;*/ assign y := 8.5; } join gather { @@ -187,7 +208,6 @@ part def Pipeline { } action keep { in v : ScalarValues::Real[2]; - /* body not migrated (the body is empty) */ } succession flow of ScalarValues::Real from low.y to gather.inputObject1; succession flow of ScalarValues::Real from high.y to gather.inputObject2; @@ -201,6 +221,7 @@ part def Pipeline { first start then produce; action produce { out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = 6.0;*/ assign y := 6.0; } first produce then feed; diff --git a/tests/migrate/testdata/xmi/orthogonal_initials.golden.report.txt b/tests/migrate/testdata/xmi/orthogonal_initials.golden.report.txt index fb253b1342..5d280de76a 100644 --- a/tests/migrate/testdata/xmi/orthogonal_initials.golden.report.txt +++ b/tests/migrate/testdata/xmi/orthogonal_initials.golden.report.txt @@ -16,8 +16,8 @@ Pseudostate Plant::Life::main::Dangle::ga:: _gaStray (written as th Transition Plant::Life::main::Dangle::ga:: _gaStrayT (its target 'gb1' lies in gb, an orthogonal region of the pseudostate's, so it is written as the entry of gb) Pseudostate Plant::Life::main::Donate::da:: _daStray (written as the entry of db, which owns its target 'db1', not of da, the orthogonal region that owns the pseudostate) Transition Plant::Life::main::Donate::da:: _daStrayT (its target 'db1' lies in db, an orthogonal region of the pseudostate's, so it is written as the entry of db) -Pseudostate Plant::Life::main::Nested::na:: _naStray (written as the entry of the body of 'Inner', which owns its target 'inner1', not of na, the orthogonal region that owns the pseudostate) -Transition Plant::Life::main::Nested::na:: _naStrayT (its target 'inner1' lies in the body of 'Inner', an orthogonal region of the pseudostate's, so it is written as the entry of the body of 'Inner') +Pseudostate Plant::Life::main::Nested::na:: _naStray (written as the entry of ni, which owns its target 'inner1', not of na, the orthogonal region that owns the pseudostate) +Transition Plant::Life::main::Nested::na:: _naStrayT (its target 'inner1' lies in ni, an orthogonal region of the pseudostate's, so it is written as the entry of ni) ## mapped (81) Model Model _m (the root model's members are written at the top level) @@ -38,54 +38,54 @@ Trigger Plant::Life::main:::: _toDuplicateTrigger Trigger Plant::Life::main:::: _toNestedTrigger Trigger Plant::Life::main:::: _toDangleTrigger State Plant::Life::main::Coincide _coincide -> Coincide -Region Plant::Life::main::Coincide::ca _ca -> ca (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Coincide::ca _ca -> ca (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Coincide::ca:: _caOwn (written as the entry of the region) Transition Plant::Life::main::Coincide::ca:: _caOwnT State Plant::Life::main::Coincide::ca::ca1 _ca1 -> ca1 -Region Plant::Life::main::Coincide::cb _cb -> cb (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Coincide::cb _cb -> cb (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Coincide::cb:: _cbInit (written as the entry of the region) Transition Plant::Life::main::Coincide::cb:: _cbInitT State Plant::Life::main::Coincide::cb::cb1 _cb1 -> cb1 State Plant::Life::main::Conflict _conflict -> Conflict -Region Plant::Life::main::Conflict::xa _xa -> xa (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Conflict::xa _xa -> xa (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Conflict::xa:: _xaOwn (written as the entry of the region) Transition Plant::Life::main::Conflict::xa:: _xaOwnT State Plant::Life::main::Conflict::xa::xa1 _xa1 -> xa1 -Region Plant::Life::main::Conflict::xb _xb -> xb (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Conflict::xb _xb -> xb (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Conflict::xb:: _xbInit (written as the entry of the region) Transition Plant::Life::main::Conflict::xb:: _xbInitT State Plant::Life::main::Conflict::xb::xb1 _xb1 -> xb1 State Plant::Life::main::Conflict::xb::xb2 _xb2 -> xb2 State Plant::Life::main::Dangle _dangle -> Dangle -Region Plant::Life::main::Dangle::ga _ga -> ga (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Dangle::ga _ga -> ga (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Dangle::ga:: _gaOwn (written as the entry of the region) Transition Plant::Life::main::Dangle::ga:: _gaOwnT State Plant::Life::main::Dangle::ga::ga1 _ga1 -> ga1 -Region Plant::Life::main::Dangle::gb _gb -> gb (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Dangle::gb _gb -> gb (an orthogonal region is written as a sub-state of the parallel state regions) State Plant::Life::main::Dangle::gb::gb1 _gb1 -> gb1 State Plant::Life::main::Donate _donate -> Donate -Region Plant::Life::main::Donate::da _da -> da (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Donate::da _da -> da (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Donate::da:: _daOwn (written as the entry of the region) Transition Plant::Life::main::Donate::da:: _daOwnT State Plant::Life::main::Donate::da::da1 _da1 -> da1 -Region Plant::Life::main::Donate::db _db -> db (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Donate::db _db -> db (an orthogonal region is written as a sub-state of the parallel state regions) State Plant::Life::main::Donate::db::db1 _db1 -> db1 State Plant::Life::main::Duplicate _duplicate -> Duplicate -Region Plant::Life::main::Duplicate::ua _ua (the one region is written as the body of its owner) +Region Plant::Life::main::Duplicate::ua _ua -> ua (the region is written as the sub-state ua of the parallel state Duplicate, as a composite state's regions are) Pseudostate Plant::Life::main::Duplicate::ua:: _uaInit1 (written as the entry of the region) Transition Plant::Life::main::Duplicate::ua:: _uaInit1T State Plant::Life::main::Duplicate::ua::ua1 _ua1 -> ua1 State Plant::Life::main::Duplicate::ua::ua2 _ua2 -> ua2 State Plant::Life::main::Nested _nested -> Nested -Region Plant::Life::main::Nested::na _na -> na (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Nested::na _na -> na (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Nested::na:: _naOwn (written as the entry of the region) Transition Plant::Life::main::Nested::na:: _naOwnT State Plant::Life::main::Nested::na::na1 _na1 -> na1 -Region Plant::Life::main::Nested::nb _nb -> nb (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Nested::nb _nb -> nb (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Nested::nb:: _nbInit (written as the entry of the region) Transition Plant::Life::main::Nested::nb:: _nbInitT State Plant::Life::main::Nested::nb::Inner _inner -> Inner -Region Plant::Life::main::Nested::nb::Inner::ni _ni (the one region is written as the body of its owner) +Region Plant::Life::main::Nested::nb::Inner::ni _ni -> ni (the region is written as the sub-state ni of the parallel state Inner, as a composite state's regions are) State Plant::Life::main::Nested::nb::Inner::ni::inner1 _inner1 -> inner1 «Block» Class Site _site -> Site StateMachine Site::Cross _cross -> Site::Cross @@ -93,11 +93,11 @@ Region Site::Cross::main _crossMain (the one region is written as the body of it Pseudostate Site::Cross::main:: _crossInit (written as the entry of the region) Transition Site::Cross::main:: _crossInitT State Site::Cross::main::Idle _idle -> Idle -Region Site::Cross::main::Idle::ir _ir (the one region is written as the body of its owner) +Region Site::Cross::main::Idle::ir _ir -> ir (the region is written as the sub-state ir of the parallel state Idle, as a composite state's regions are) Pseudostate Site::Cross::main::Idle::ir:: _iInit (written as the entry of the region) Transition Site::Cross::main::Idle::ir:: _iInitT State Site::Cross::main::Idle::ir::idle1 _idle1 -> idle1 State Site::Cross::main::Work _work -> Work -Region Site::Cross::main::Work::wr _wr (the one region is written as the body of its owner) +Region Site::Cross::main::Work::wr _wr -> wr (the region is written as the sub-state wr of the parallel state Work, as a composite state's regions are) State Site::Cross::main::Work::wr::work1 _work1 -> work1 SignalEvent nextEvent _nextEvent (written where a trigger refers to it, as accept Next) diff --git a/tests/migrate/testdata/xmi/orthogonal_initials.golden.sysml b/tests/migrate/testdata/xmi/orthogonal_initials.golden.sysml index 515259361c..0f3eda52c5 100644 --- a/tests/migrate/testdata/xmi/orthogonal_initials.golden.sysml +++ b/tests/migrate/testdata/xmi/orthogonal_initials.golden.sysml @@ -50,11 +50,13 @@ part def Plant { transition first regions then done; metadata MigrationMetadata::SynthesizedName about regions; } - state Duplicate { - /* not migrated: Pseudostate (_uaInit2) — a region has one initial pseudostate; (_uaInit1) is written as it */ - entry; then ua1; - state ua1; - state ua2; + state Duplicate parallel { + state ua { + /* not migrated: Pseudostate (_uaInit2) — a region has one initial pseudostate; (_uaInit1) is written as it */ + entry; then ua1; + state ua1; + state ua2; + } } state Nested { entry; then regions; @@ -65,9 +67,11 @@ part def Plant { } state nb { entry; then Inner; - state Inner { - entry; then inner1; - state inner1; + state Inner parallel { + state ni { + entry; then inner1; + state inner1; + } } } } @@ -100,15 +104,19 @@ part def Plant { part def Site { state def Cross { entry; then Idle; - state Work { - /* not migrated: Pseudostate (_wInit) — the initial transition's target has no v2 form here */ - /* not migrated: Transition (_wInitT) — its target 'idle1' lies in 'ir', a region of 'Idle' and no orthogonal region of the pseudostate's; an initial transition enters its own region */ - /* every initial pseudostate of the region enters another region: nothing enters this one */ - state work1; + state Work parallel { + state wr { + /* not migrated: Pseudostate (_wInit) — the initial transition's target has no v2 form here */ + /* not migrated: Transition (_wInitT) — its target 'idle1' lies in 'ir', a region of 'Idle' and no orthogonal region of the pseudostate's; an initial transition enters its own region */ + /* every initial pseudostate of the region enters another region: nothing enters this one */ + state work1; + } } - state Idle { - entry; then idle1; - state idle1; + state Idle parallel { + state ir { + entry; then idle1; + state idle1; + } } } } diff --git a/tests/migrate/testdata/xmi/parking_usecases.golden.report.txt b/tests/migrate/testdata/xmi/parking_usecases.golden.report.txt index d26aac1743..5b1150045f 100644 --- a/tests/migrate/testdata/xmi/parking_usecases.golden.report.txt +++ b/tests/migrate/testdata/xmi/parking_usecases.golden.report.txt @@ -1,12 +1,12 @@ # SysML v1 to v2 migration report: parking_usecases.xmi -# migrated 44 element(s): 25 mapped, 16 approximated, 3 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 44 element(s): 26 mapped, 15 approximated, 3 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) ## unmapped (3) Extend Use Cases::Leave Garage:: _extLost (the extended use case is not in the document; 1 extendedCase reference(s) resolve to nothing in the document (_lostCase)) Include Use Cases::Leave Garage:: _incLost (the included use case is not in the document; 1 addition reference(s) resolve to nothing in the document (_lostCase)) ExtensionPoint Use Cases::Park Car::garage full _epFull (v2 has no extension points; an extending use case is written as a connection to the extended one) -## approximated (16) +## approximated (15) Actor Context::Attendant _attendant -> Context::Attendant (a UML actor is written as a part usage) Actor Context::Bank _bank -> Context::Bank (a UML actor is written as a part usage) Actor Context::Driver _driver -> Context::Driver (a UML actor is written as a part usage) @@ -16,7 +16,6 @@ Property Use Cases:::: _eAttendant -> 'Attendant to Park Property Use Cases:::: _eParkA -> 'Attendant to Park Car' (the end at the use case is the connection's end at the use case's actor parameter attendant) Property Use Cases::::driver _eDriver -> 'Driver to Park Car' (the end at the actor is the connection's end at the actor's part usage; the use case declares the actor parameter driver subsetting that usage) Activity Use Cases::Park Car::Park Flow _parkFlow -> 'Use Cases'::'Park Car'::'Park Flow' (the classifier behavior is run by every object of Use Cases::Park Car as its usage park Flow) -OpaqueAction Use Cases::Park Car::Park Flow::Enter _pfEnter -> Enter (the body is kept as a comment: the body is empty) UseCase Use Cases::Pay Fee _pay -> 'Use Cases'::'Pay Fee' (v2 admits one subject per case, so the subject Context::Ticket is written as the reference usage ticket) Include Use Cases::Pay Fee::authorise _incAuth -> 'Use Cases'::'Pay Fee' (an include referring to a use case usage has no name, so authorise is dropped) Property Use Cases::Settlement:: _ePayB -> settlement (the end at the use case is the connection's end at the use case usage, as the connection def's end is; the use case's actor parameter bank subsets the actor) @@ -24,7 +23,7 @@ Property Use Cases::Settlement::bank _eBank -> settlement (the end at the actor Property Use Cases::Valet Service::offer _svcCase -> 'Use Cases'::'Valet Service'::offer (a property typed by a behavior is written as a reference use case usage) Extend Use Cases::Wait For Space:: _extFull -> 'Use Cases'::'Wait For Space extends Park Car' (v2 has no extend: the extension is written as a connection between usages of the extending use case and the extended use case; it applies at extension point(s) 'garage full' when {English} no bay is free, which only a comment keeps) -## mapped (25) +## mapped (26) Package Context _ctx -> Context Comment Context::Driver:: _driverDoc «Block» Class Context::Garage _garage -> Context::Garage @@ -45,6 +44,7 @@ Include Use Cases::Park Car:: _incPay -> 'Use Cases'::'Park Car' ActivityFinalNode Use Cases::Park Car::Park Flow:: _pfEnd -> final ControlFlow Use Cases::Park Car::Park Flow:: _pfE2 InitialNode Use Cases::Park Car::Park Flow:: _pfStart -> start +OpaqueAction Use Cases::Park Car::Park Flow::Enter _pfEnter -> Enter (its body is empty, so it is written as an action that does nothing) Property Use Cases::Pay Fee::amount _payAmount -> 'Use Cases'::'Pay Fee'::amount Association Use Cases::Settlement _aBankPay -> 'Use Cases'::Settlement (the association's one usage is the connection settlement joining the actor and the use case 'Use Cases'::'Pay Fee') UseCase Use Cases::Valet Park _valet -> 'Use Cases'::'Valet Park' diff --git a/tests/migrate/testdata/xmi/parking_usecases.golden.sysml b/tests/migrate/testdata/xmi/parking_usecases.golden.sysml index 873bf31b8a..fde63095ff 100644 --- a/tests/migrate/testdata/xmi/parking_usecases.golden.sysml +++ b/tests/migrate/testdata/xmi/parking_usecases.golden.sysml @@ -17,9 +17,7 @@ package 'Use Cases' { /* not migrated: ExtensionPoint 'garage full' — v2 has no extension points; an extending use case is written as a connection to the extended one */ action def 'Park Flow' { first start then Enter; - action Enter { - /* body not migrated (the body is empty) */ - } + action Enter; first Enter then final; action final terminate; metadata MigrationMetadata::SynthesizedName about final; diff --git a/tests/migrate/testdata/xmi/plant.golden.report.txt b/tests/migrate/testdata/xmi/plant.golden.report.txt index 8a8c604680..73442d72a8 100644 --- a/tests/migrate/testdata/xmi/plant.golden.report.txt +++ b/tests/migrate/testdata/xmi/plant.golden.report.txt @@ -6,8 +6,8 @@ ControlFlow Plant::Fill:: _f3a (the guard [{JavaScript} on] is kept ActivityPartition Plant::Fill::Elsewhere _gone (a partition names who performs its nodes; it is kept as a comment; the partition represents an element outside the document) ActivityPartition Plant::Fill::Operator _unset (a partition names who performs its nodes; it is kept as a comment; the partition represents nothing) ActivityPartition Plant::Fill::Spare _spare (a partition names who performs its nodes; it is kept as a comment; the partition represents the context's tank, read as this.tank, but nothing in it names the object's features) -OpaqueAction Plant::Fill::mine _mine -> mine (the body is kept as a comment: the name "this.level" resolves to nothing readable: Tank has no feature level) -OpaqueAction Plant::Fill::torn _torn -> torn (the body is kept as a comment: the name "volume" resolves to nothing readable: nothing visible from Plant::Fill::torn is called volume; it is in the partitions 'Tank' (this.tank) and 'Pump' (this.pump), which represent different objects, so names resolve through no partition) +OpaqueAction Plant::Fill::mine _mine -> mine (the body is kept as a textual representation, which is not executed: the name "this.level" resolves to nothing readable: Tank has no feature level) +OpaqueAction Plant::Fill::torn _torn -> torn (the body is kept as a textual representation, which is not executed: the name "volume" resolves to nothing readable: nothing visible from Plant::Fill::torn is called volume; it is in the partitions 'Tank' (this.tank) and 'Pump' (this.pump), which represent different objects, so names resolve through no partition) ## mapped (56) «Block» Class Control _control -> Control diff --git a/tests/migrate/testdata/xmi/plant.golden.sysml b/tests/migrate/testdata/xmi/plant.golden.sysml index 03c445a4b7..5dd7fc7cd2 100644 --- a/tests/migrate/testdata/xmi/plant.golden.sysml +++ b/tests/migrate/testdata/xmi/plant.golden.sysml @@ -33,55 +33,61 @@ part def Plant { action fill { first start then 'open valve'; action 'open valve' { + rep language "JavaScript" /* open = true;*/ assign tank.valve.open := true; } first 'open valve' then 'fill tank'; action 'fill tank' { + rep language "JavaScript" /* volume = volume * 2 + (valve.open ? 1 : 0);*/ assign tank.volume := tank.volume * 2 + (if tank.valve.open ? 1 else 0); } first 'fill tank' then torn; action torn { - /* body not migrated (the name "volume" resolves to nothing readable: nothing visible from Plant::Fill::torn is called volume; it is in the partitions 'Tank' (this.tank) and 'Pump' (this.pump), which represent different objects, so names resolve through no partition) {JavaScript}: - * volume = 0; - */ + rep language "JavaScript" /* volume = 0;*/ } /* guard not migrated: [{JavaScript} on] — the name "on" resolves to nothing readable: nothing visible from Plant::Fill:: is called on; its source 'torn' is in the partitions 'Tank' (this.tank) and 'Pump' (this.pump), which represent different objects, so names resolve through no partition */ first torn then prime; action prime { + rep language "JavaScript" /* on = true;*/ assign pump.on := true; } first prime then twice; action twice { + rep language "JavaScript" /* volume = volume + 1;*/ assign tank.volume := tank.volume + 1; } first twice then mine; action mine { - /* body not migrated (the name "this.level" resolves to nothing readable: Tank has no feature level) {JavaScript}: - * this.level = 1; - */ + rep language "JavaScript" /* this.level = 1;*/ } first mine then 'count run'; action 'count run' { + rep language "JavaScript" /* runs += 1;*/ assign runs := runs + 1; } first 'count run' then 'note level'; action 'note level' { + rep language "JavaScript" /* level = tank.volume;*/ assign level := tank.volume; } first 'note level' then lost; action lost { + rep language "JavaScript" /* level = level + 1;*/ assign level := level + 1; } first lost then free; action free { + rep language "JavaScript" /* level = level + tank.volume;*/ assign level := level + tank.volume; } first free then arm; action arm { + rep language "JavaScript" /* this.status = true;*/ assign site.control.rack.controller.status := true; } first arm then light; action light { + rep language "JavaScript" /* lit = true;*/ assign site.control.rack.controller.led.lit := true; } first light then final; diff --git a/tests/migrate/testdata/xmi/plant_states.golden.report.txt b/tests/migrate/testdata/xmi/plant_states.golden.report.txt index 525768f41d..7e7a6e46f7 100644 --- a/tests/migrate/testdata/xmi/plant_states.golden.report.txt +++ b/tests/migrate/testdata/xmi/plant_states.golden.report.txt @@ -8,7 +8,7 @@ TimeEvent never _never (the time event's time is not written: the instant "next ## approximated (6) StateMachine Plant::Line _line -> Plant::Line (acts on its owner Plant, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Plant as its usage line) -Transition Plant::Line::main:: _tBump (the target 'Prep' lies in another region and is named by its path Work.Prep; a local transition is written external: the composite state Work exits and re-enters where v1 stayed in it, running its exit and entry behaviors) +Transition Plant::Line::main:: _tBump (the target 'Prep' lies in another region and is named by its path Work.steps.Prep; a local transition is written external: the composite state Work exits and re-enters where v1 stayed in it, running its exit and entry behaviors) Trigger Plant::Line::main:::: _trDawn (the absolute time is an instant on the simulation clock, which starts at 0.0 [SI::s]) OpaqueBehavior Plant::Line::main::::log _resumeLog -> Plant::Line::unnamed::log (the JavaScript body is translated to v2; the console print println(…) is left out, as it writes to the tool's console and changes nothing of the model; the JavaScript body is written as v2 assignments) OpaqueBehavior Plant::Line::main::Work::tally _workExit -> Plant::Line::Work::tally (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) @@ -53,10 +53,10 @@ FinalState Plant::Line::main:: _fin -> done Pseudostate Plant::Line::main:: _init0 (written as the entry of the region) Transition Plant::Line::main:: _t0 Transition Plant::Line::main:: _tGo -Transition Plant::Line::main:: _tStop (the source 'Run' lies in another region and is named by its path Work.Run) -Transition Plant::Line::main:: _tDirect (the target 'Run' lies in another region and is named by its path Work.Run) +Transition Plant::Line::main:: _tStop (the source 'Run' lies in another region and is named by its path Work.steps.Run) +Transition Plant::Line::main:: _tDirect (the target 'Run' lies in another region and is named by its path Work.steps.Run) Transition Plant::Line::main:: _tHold -Transition Plant::Line::main:: _tResume (the target 'last' lies in another region and is named by its path Work.last) +Transition Plant::Line::main:: _tResume (the target 'last' lies in another region and is named by its path Work.steps.last) Transition Plant::Line::main:: _tEnter Transition Plant::Line::main:: _tLeave Transition Plant::Line::main:: _tRoute @@ -83,12 +83,12 @@ Trigger Plant::Line::main:::: _trSplit Trigger Plant::Line::main:::: _trPing Trigger Plant::Line::main:::: _trBump State Plant::Line::main::Both _both -> Both -Region Plant::Line::main::Both::a _ra -> a (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Line::main::Both::a _ra -> a (an orthogonal region is written as a sub-state of the parallel state regions) Transition Plant::Line::main::Both::a:: _tA Trigger Plant::Line::main::Both::a:::: _trA State Plant::Line::main::Both::a::A1 _a1 -> A1 State Plant::Line::main::Both::a::A2 _a2 -> A2 -Region Plant::Line::main::Both::b _rb -> b (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Line::main::Both::b _rb -> b (an orthogonal region is written as a sub-state of the parallel state regions) Transition Plant::Line::main::Both::b:: _tB Trigger Plant::Line::main::Both::b:::: _trB State Plant::Line::main::Both::b::B1 _b1 -> B1 @@ -99,7 +99,7 @@ ConnectionPointReference Plant::Line::main::Cell:: _cp State Plant::Line::main::Idle _idle -> Idle State Plant::Line::main::Pause _pause -> Pause State Plant::Line::main::Work _work -> Work -Region Plant::Line::main::Work::steps _rw (the one region is written as the body of its owner) +Region Plant::Line::main::Work::steps _rw -> steps (the region is written as the sub-state steps of the parallel state Work, as a composite state's regions are) Pseudostate Plant::Line::main::Work::steps:: _initW (written as the entry of the region) Transition Plant::Line::main::Work::steps:: _tWi Transition Plant::Line::main::Work::steps:: _tNext diff --git a/tests/migrate/testdata/xmi/plant_states.golden.sysml b/tests/migrate/testdata/xmi/plant_states.golden.sysml index 41c0041b17..0c2f15dd09 100644 --- a/tests/migrate/testdata/xmi/plant_states.golden.sysml +++ b/tests/migrate/testdata/xmi/plant_states.golden.sysml @@ -17,17 +17,20 @@ part def Plant { attribute dawn : Time::TimeInstantValue = 21600.0 [SI::s]; entry; then Idle; state Idle; - state Work { - entry; then Prep; + state Work parallel { exit action tally { + rep language "JavaScript" /* count = count + 1;*/ assign context.count := context.count + 1; } - state Prep; - state Run; - #StateMachines::shallowHistory state last; - #StateMachines::deepHistory state deepest; - transition first Prep accept Next then Run; - transition first last then Prep; + state steps { + entry; then Prep; + state Prep; + state Run; + #StateMachines::shallowHistory state last; + #StateMachines::deepHistory state deepest; + transition first Prep accept Next then Run; + transition first last then Prep; + } } state Pause; state Cell : CellMachine { in ref :>> context = Line::context; } @@ -35,7 +38,7 @@ part def Plant { fork spread; join gather; state Both { - /* no default entry: the regions 'a', 'b' have no initial pseudostate, so only a fork or a transition naming a nested state enters the regions */ + entry; then regions; state regions parallel { state a { /* the region has no initial pseudostate: nothing enters it */ @@ -54,12 +57,14 @@ part def Plant { metadata MigrationMetadata::SynthesizedName about regions; } transition first Idle accept Go then Work; - transition first Work.Run accept Stop then Idle; - transition first Idle accept Resume then Work.Run; + transition first Work.steps.Run accept Stop then Idle; + transition first Idle accept Resume then Work.steps.Run; transition first Work accept Hold then Pause; transition first Pause accept Resume - do action log { } - then Work.last; + do action log { + rep language "JavaScript" /* println("resuming at " + count);*/ + } + then Work.steps.last; transition first Idle accept Enter then Cell.warmStart; transition first Cell.spent then Idle; transition first Idle accept Route then route; @@ -72,7 +77,7 @@ part def Plant { transition first Both.regions.b.B2 then gather; transition first gather then Idle; transition first Idle accept Ping then Idle; - transition first Work accept Bump then Work.Prep; + transition first Work accept Bump then Work.steps.Prep; transition first Pause accept at dawn then Idle; /* transition (_tNever) from Pause to done not migrated — a trigger is dropped: the time event's time is not written: the instant "next Tuesday" is neither a number with a time unit nor an expression: not v2 expression syntax */ } diff --git a/tests/migrate/testdata/xmi/probability_nested_def_context.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_context.golden.report.txt index c98fa2a9fb..b78a8ab5c2 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_context.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_context.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_nested_def_context.xmi -# migrated 19 element(s): 14 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 19 element(s): 16 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 1.0 - context.pr, its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) CallBehaviorAction Mission::Sub::Run::call _call -> call (the behavior acts on a Mission::Sub through its parameter context, which is bound to Run::context) -## mapped (14) +## mapped (16) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -21,5 +19,7 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_nested_def_context.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_context.golden.sysml index 03f373eb95..eb3b9ee8f8 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_context.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_context.golden.sysml @@ -9,13 +9,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = context.pr; } } first 'decide' then b { @Stochastic::Probability { p = 1.0 - context.pr; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_nested_def_default.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_default.golden.report.txt index 3b2d6453c3..676665ab31 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_default.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_default.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_nested_def_default.xmi -# migrated 19 element(s): 14 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 19 element(s): 16 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 0.75, its share of what the marked branches leave of 1) «Probability» ControlFlow Mission::Sub::Run::Inner:: _ea (the probability is written as 0.25, the default of property Mission::pr, since the action acts on a Mission::Sub, which neither holds Mission::pr nor has one part that does; a run no longer reads the property, so an object whose value differs is still weighted by the default) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) -## mapped (14) +## mapped (16) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -20,6 +18,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) CallBehaviorAction Mission::Sub::Run::call _call -> call Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_nested_def_default.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_default.golden.sysml index 1967cf1486..a141634cc1 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_default.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_default.golden.sysml @@ -8,13 +8,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = 0.25; } } first 'decide' then b { @Stochastic::Probability { p = 0.75; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_part.golden.report.txt index 5641704a5c..ba41796cd4 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_part.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_nested_def_part.xmi -# migrated 20 element(s): 15 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 17 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 1.0 - context.mission.pr, its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) CallBehaviorAction Mission::Sub::Run::call _call -> call (the behavior acts on a Mission::Sub through its parameter context, which is bound to Run::context) -## mapped (15) +## mapped (17) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -21,6 +19,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) Property Mission::Sub::mission _mpart -> Mission::Sub::mission Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_part.golden.sysml index 8be72f0056..0d40caaff7 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_part.golden.sysml @@ -10,13 +10,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = context.mission.pr; } } first 'decide' then b { @Stochastic::Probability { p = 1.0 - context.mission.pr; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.report.txt index b8c724428f..bd00ed5ad6 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_nested_def_part_optional.xmi -# migrated 20 element(s): 15 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 17 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 0.75, its share of what the marked branches leave of 1) «Probability» ControlFlow Mission::Sub::Run::Inner:: _ea (the probability is written as 0.25, the default of property Mission::pr, since the action acts on a Mission::Sub, whose one part that holds Mission::pr, Mission::Sub::mission, holds 0..1 objects, so a read through it may find no number; a run no longer reads the property, so an object whose value differs is still weighted by the default) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) -## mapped (15) +## mapped (17) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -20,6 +18,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) CallBehaviorAction Mission::Sub::Run::call _call -> call Property Mission::Sub::mission _mpart -> Mission::Sub::mission Property Mission::pr _pr -> Mission::pr diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.sysml index 7995435b48..b627469a16 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_optional.golden.sysml @@ -9,13 +9,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = 0.25; } } first 'decide' then b { @Stochastic::Probability { p = 0.75; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.report.txt index 894ae3e6ee..ab3bfb3bd7 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_nested_def_part_plural.xmi -# migrated 20 element(s): 15 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 17 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 0.75, its share of what the marked branches leave of 1) «Probability» ControlFlow Mission::Sub::Run::Inner:: _ea (the probability is written as 0.25, the default of property Mission::pr, since the action acts on a Mission::Sub, whose one part that holds Mission::pr, Mission::Sub::mission, holds 2 objects, so a read through it is a collection, not one number; a run no longer reads the property, so an object whose value differs is still weighted by the default) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) -## mapped (15) +## mapped (17) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -20,6 +18,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) CallBehaviorAction Mission::Sub::Run::call _call -> call Property Mission::Sub::mission _mpart -> Mission::Sub::mission Property Mission::pr _pr -> Mission::pr diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.sysml index 3bba8533f0..b9ccb5129e 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_plural.golden.sysml @@ -9,13 +9,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = 0.25; } } first 'decide' then b { @Stochastic::Probability { p = 0.75; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.report.txt b/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.report.txt index 6a2ae9e011..5d1be4fc42 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.report.txt @@ -1,13 +1,11 @@ # SysML v1 to v2 migration report: probability_nested_def_part_uncalled.xmi -# migrated 18 element(s): 14 mapped, 4 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 18 element(s): 16 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (4) +## approximated (2) Activity Mission::Sub::Run _run -> Mission::Sub::Run (the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 1.0 - context.mission.pr, its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) -## mapped (14) +## mapped (16) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub ActivityFinalNode Mission::Sub::Run:: _rf -> final @@ -19,6 +17,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) Property Mission::Sub::mission _mpart -> Mission::Sub::mission Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.sysml b/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.sysml index 4a032de323..5fe8cab636 100644 --- a/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_nested_def_part_uncalled.golden.sysml @@ -9,13 +9,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = context.mission.pr; } } first 'decide' then b { @Stochastic::Probability { p = 1.0 - context.mission.pr; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_part_private.golden.report.txt b/tests/migrate/testdata/xmi/probability_part_private.golden.report.txt index 6345b69845..b63e3fbb9a 100644 --- a/tests/migrate/testdata/xmi/probability_part_private.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_part_private.golden.report.txt @@ -1,14 +1,12 @@ # SysML v1 to v2 migration report: probability_part_private.xmi -# migrated 21 element(s): 16 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 21 element(s): 18 mapped, 3 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (5) +## approximated (3) Activity Mission::Sub::Run _run -> Mission::Sub::Run (acts on its owner Mission::Sub, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Mission::Sub as its usage run) ControlFlow Mission::Sub::Run::Inner:: _eb (the edge carries no «Probability»: it is weighted 0.75, its share of what the marked branches leave of 1) «Probability» ControlFlow Mission::Sub::Run::Inner:: _ea (the probability is written as 0.25, the default of property Mission::pr, since the action acts on a Mission::Sub, whose one part that holds Mission::pr, Mission::Base::mission, is a private feature of Mission::Base, which v2 does not inherit, so it cannot be named on the object; a run no longer reads the property, so an object whose value differs is still weighted by the default) -OpaqueAction Mission::Sub::Run::Inner::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::Inner::b _b -> b (the body is kept as a comment: the body is empty) -## mapped (16) +## mapped (18) «Block» Class Mission _mission -> Mission «Block» Class Mission::Base _base -> Mission::Base Property Mission::Base::mission _mpart -> Mission::Base::mission @@ -22,6 +20,8 @@ ControlFlow Mission::Sub::Run::Inner:: _fa ControlFlow Mission::Sub::Run::Inner:: _fb DecisionNode Mission::Sub::Run::Inner:: _decide -> 'decide' InitialNode Mission::Sub::Run::Inner:: _ii -> start +OpaqueAction Mission::Sub::Run::Inner::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::Inner::b _b -> b (its body is empty, so it is written as an action that does nothing) CallBehaviorAction Mission::Sub::Run::call _call -> call Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_part_private.golden.sysml b/tests/migrate/testdata/xmi/probability_part_private.golden.sysml index 8b6183d16b..2c08917325 100644 --- a/tests/migrate/testdata/xmi/probability_part_private.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_part_private.golden.sysml @@ -11,13 +11,9 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = 0.25; } } first 'decide' then b { @Stochastic::Probability { p = 0.75; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then 'merge'; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/probability_part_shadowed.golden.report.txt b/tests/migrate/testdata/xmi/probability_part_shadowed.golden.report.txt index c120397ad0..0fc321dabd 100644 --- a/tests/migrate/testdata/xmi/probability_part_shadowed.golden.report.txt +++ b/tests/migrate/testdata/xmi/probability_part_shadowed.golden.report.txt @@ -1,13 +1,10 @@ # SysML v1 to v2 migration report: probability_part_shadowed.xmi -# migrated 17 element(s): 13 mapped, 4 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 17 element(s): 16 mapped, 1 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (4) +## approximated (1) ControlFlow Mission::Sub::Run:: _eb (the edge carries no «Probability»: it is weighted 1.0 - Sub::mission.pr, its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Mission::Sub::Run::a _a -> a (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::b _b -> b (the body is kept as a comment: the body is empty) -OpaqueAction Mission::Sub::Run::mission _shadow -> mission (the body is kept as a comment: the body is empty) -## mapped (13) +## mapped (16) «Block» Class Mission _mission -> Mission «Block» Class Mission::Sub _sub -> Mission::Sub Activity Mission::Sub::Run _run -> Mission::Sub::run (written as an action usage every object of Mission::Sub performs from creation, so its body runs on the object and reaches its features) @@ -18,6 +15,9 @@ ControlFlow Mission::Sub::Run:: _fb ControlFlow Mission::Sub::Run:: _fs DecisionNode Mission::Sub::Run:: _decide -> 'decide' InitialNode Mission::Sub::Run:: _ri -> start +OpaqueAction Mission::Sub::Run::a _a -> a (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::b _b -> b (its body is empty, so it is written as an action that does nothing) +OpaqueAction Mission::Sub::Run::mission _shadow -> mission (its body is empty, so it is written as an action that does nothing) Property Mission::Sub::mission _mpart -> Mission::Sub::mission Property Mission::pr _pr -> Mission::pr Model Model _m (the root model's members are written at the top level) diff --git a/tests/migrate/testdata/xmi/probability_part_shadowed.golden.sysml b/tests/migrate/testdata/xmi/probability_part_shadowed.golden.sysml index f1df0ed25c..a2da6fa48d 100644 --- a/tests/migrate/testdata/xmi/probability_part_shadowed.golden.sysml +++ b/tests/migrate/testdata/xmi/probability_part_shadowed.golden.sysml @@ -7,17 +7,11 @@ part def Mission { decide 'decide'; first 'decide' then a { @Stochastic::Probability { p = Sub::mission.pr; } } first 'decide' then b { @Stochastic::Probability { p = 1.0 - Sub::mission.pr; } } - action a { - /* body not migrated (the body is empty) */ - } + action a; first a then mission; - action b { - /* body not migrated (the body is empty) */ - } + action b; first b then 'merge'; - action mission { - /* body not migrated (the body is empty) */ - } + action mission; first mission then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tests/migrate/testdata/xmi/reactor.golden.report.txt b/tests/migrate/testdata/xmi/reactor.golden.report.txt index a5e66e2c3e..35afcf88d0 100644 --- a/tests/migrate/testdata/xmi/reactor.golden.report.txt +++ b/tests/migrate/testdata/xmi/reactor.golden.report.txt @@ -5,25 +5,25 @@ Parameter Ceil::whole _pcr -> Ceil::whole (the return parameter is written as an out parameter) Parameter Energy::result _pr -> Energy::result (the return parameter is written as an out parameter) Parameter Floor::whole _pfr -> Floor::whole (the return parameter is written as an out parameter) -OpaqueAction Reactor::Cycle::alloc _alloc -> alloc (the body is kept as a comment: the construct "new" is outside the translated subset) -OpaqueAction Reactor::Cycle::bare _bare -> bare (the body is kept as a comment: the name "raw.level" resolves to nothing readable: Reactor::Cycle::bare::raw has no type, so no feature level) -OpaqueAction Reactor::Cycle::loop _loop -> 'loop' (the body is kept as a comment: the construct "for" is outside the translated subset) -OpaqueAction Reactor::Cycle::mixed _mixed -> mixed (the body is kept as a comment: the construct "+" is outside the translated subset: string concatenation has no v2 form in the subset) -OpaqueAction Reactor::Cycle::partial _partial -> partial (the body is kept as a comment: the call "label.trim" is not in the translated function table) -OpaqueAction Reactor::Cycle::pinned _pinned -> pinned (the body is kept as a comment: the construct "var x" is outside the translated subset: x is already a feature here, which a declaration would shadow) +OpaqueAction Reactor::Cycle::alloc _alloc -> alloc (the body is kept as a textual representation, which is not executed: the construct "new" is outside the translated subset) +OpaqueAction Reactor::Cycle::bare _bare -> bare (the body is kept as a textual representation, which is not executed: the name "raw.level" resolves to nothing readable: Reactor::Cycle::bare::raw has no type, so no feature level) +OpaqueAction Reactor::Cycle::loop _loop -> 'loop' (the body is kept as a textual representation, which is not executed: the construct "for" is outside the translated subset) +OpaqueAction Reactor::Cycle::mixed _mixed -> mixed (the body is kept as a textual representation, which is not executed: the construct "+" is outside the translated subset: string concatenation has no v2 form in the subset) +OpaqueAction Reactor::Cycle::partial _partial -> partial (the body is kept as a textual representation, which is not executed: the call "label.trim" is not in the translated function table) +OpaqueAction Reactor::Cycle::pinned _pinned -> pinned (the body is kept as a textual representation, which is not executed: the construct "var x" is outside the translated subset: x is already a feature here, which a declaration would shadow) OpaqueAction Reactor::Cycle::python _python -> python (the Python body is written as v2 assignments) -OpaqueAction Reactor::Cycle::relay _relay -> relay (the body is kept as a comment: the construct "x" is outside the translated subset: an input pin is not assigned) -OpaqueAction Reactor::Cycle::self _self -> self2 (the body is kept as a comment: the name "this.owner.power" resolves to nothing readable: Reactor has no feature owner) -OpaqueAction Reactor::Cycle::shadow _shadow -> shadow (the body is kept as a comment: the construct "var power" is outside the translated subset: power is already a feature here, which a declaration would shadow) -OpaqueAction Reactor::Cycle::stray _stray -> stray (the body is kept as a comment: the name "nowhere" resolves to nothing readable: nothing visible from Reactor::Cycle::stray is called nowhere) -OpaqueAction Reactor::Cycle::trim _trim -> trim (the body is kept as a comment: the call "label.trim" is not in the translated function table) -FunctionBehavior Root _root -> Root (the body is kept as a comment: as the result expression, the types at "Math.sqrt(x)" disagree: the expression is a Real, not the Boolean wanted; as statements, the call "Math.sqrt" is not in the translated function table: a call is not a statement of the subset) +OpaqueAction Reactor::Cycle::relay _relay -> relay (the body is kept as a textual representation, which is not executed: the construct "x" is outside the translated subset: an input pin is not assigned) +OpaqueAction Reactor::Cycle::self _self -> self2 (the body is kept as a textual representation, which is not executed: the name "this.owner.power" resolves to nothing readable: Reactor has no feature owner) +OpaqueAction Reactor::Cycle::shadow _shadow -> shadow (the body is kept as a textual representation, which is not executed: the construct "var power" is outside the translated subset: power is already a feature here, which a declaration would shadow) +OpaqueAction Reactor::Cycle::stray _stray -> stray (the body is kept as a textual representation, which is not executed: the name "nowhere" resolves to nothing readable: nothing visible from Reactor::Cycle::stray is called nowhere) +OpaqueAction Reactor::Cycle::trim _trim -> trim (the body is kept as a textual representation, which is not executed: the call "label.trim" is not in the translated function table) +FunctionBehavior Root _root -> Root (the body is kept as a textual representation, which is not executed: as the result expression, the types at "Math.sqrt(x)" disagree: the expression is a Real, not the Boolean wanted; as statements, the call "Math.sqrt" is not in the translated function table: a call is not a statement of the subset) Parameter Root::ok _prok -> Root::ok (the return parameter is written as an out parameter) Parameter Round::whole _prdr -> Round::whole (the return parameter is written as an out parameter) «SimulationConfig» Class Run _config -> Run (the configuration names no execution target, so it runs no behavior) -OpaqueBehavior Scale _scale -> Scale (the body is kept as a comment: the construct "x" is outside the translated subset: an in parameter is not assigned) -OpaqueBehavior Shift _shift -> Shift (the body is kept as a comment: the construct "a" is outside the translated subset: an in parameter is not assigned) -FunctionBehavior Split _split -> Split (the body is kept as a comment: as the result expression, the behavior has 2 output parameters; one result expression can stand for none of them; as statements, the construct "x" is outside the translated subset: an expression that assigns nothing is not a statement of the subset) +OpaqueBehavior Scale _scale -> Scale (the body is kept as a textual representation, which is not executed: the construct "x" is outside the translated subset: an in parameter is not assigned) +OpaqueBehavior Shift _shift -> Shift (the body is kept as a textual representation, which is not executed: the construct "a" is outside the translated subset: an in parameter is not assigned) +FunctionBehavior Split _split -> Split (the body is kept as a textual representation, which is not executed: as the result expression, the behavior has 2 output parameters; one result expression can stand for none of them; as statements, the construct "x" is outside the translated subset: an expression that assigns nothing is not a statement of the subset) Parameter Split::hi _pshi -> Split::hi (the return parameter is written as an out parameter) ## mapped (56) diff --git a/tests/migrate/testdata/xmi/reactor.golden.sysml b/tests/migrate/testdata/xmi/reactor.golden.sysml index 9435c01cc4..1c7447690f 100644 --- a/tests/migrate/testdata/xmi/reactor.golden.sysml +++ b/tests/migrate/testdata/xmi/reactor.golden.sysml @@ -8,24 +8,22 @@ calc def Energy { in P : ScalarValues::Real[1]; in dt : ScalarValues::Real[1]; out result : ScalarValues::Real[1]; + rep language "JavaScript" /* Math.max(E_0 - P * dt, 0)*/ RealFunctions::max(E_0 - P * dt, 0) } action def Scale { in x : ScalarValues::Real[1]; out y : ScalarValues::Real[1]; - /* body not migrated (the construct "x" is outside the translated subset: an in parameter is not assigned) {JavaScript}: - * x = x * 2; y = x; - */ + rep language "JavaScript" /* x = x * 2; y = x;*/ } action def Shift { in a : ScalarValues::Real[1]; out b : ScalarValues::Real[1]; - /* body not migrated (the construct "a" is outside the translated subset: an in parameter is not assigned) {JavaScript}: - * const step = 1; a = a + step; b = a; - */ + rep language "JavaScript" /* const step = 1; a = a + step; b = a;*/ } action def Double { inout n : ScalarValues::Real[1]; + rep language "JavaScript" /* const k = 2; n = n * k;*/ first start then body; action body { attribute k : ScalarValues::Integer; @@ -37,6 +35,7 @@ action def Double { action def Delay { in t_sim : ScalarValues::Real[1]; out d : ScalarValues::Real[1]; + rep language "JavaScript" /* d = t_sim * 2;*/ first start then body; action body { assign d := t_sim * 2; @@ -46,32 +45,31 @@ action def Delay { calc def Floor { in x : ScalarValues::Real[1]; out whole : ScalarValues::Integer[1]; + rep language "JavaScript" /* Math.floor(x)*/ RealFunctions::floor(x) } calc def Ceil { in x : ScalarValues::Real[1]; out whole : ScalarValues::Integer[1]; + rep language "JavaScript" /* Math.ceil(x)*/ OpenSysMLMathFunctions::ceiling(x) } calc def Round { in x : ScalarValues::Real[1]; out whole : ScalarValues::Real[1]; + rep language "JavaScript" /* Math.round(x)*/ RealFunctions::floor(x + 0.5) } action def Root { in x : ScalarValues::Real[1]; out ok : ScalarValues::Boolean[1]; - /* body not migrated (as the result expression, the types at "Math.sqrt(x)" disagree: the expression is a Real, not the Boolean wanted; as statements, the call "Math.sqrt" is not in the translated function table: a call is not a statement of the subset) {JavaScript}: - * Math.sqrt(x) - */ + rep language "JavaScript" /* Math.sqrt(x)*/ } action def Split { in x : ScalarValues::Real[1]; out lo : ScalarValues::Real[1]; out hi : ScalarValues::Real[1]; - /* body not migrated (as the result expression, the behavior has 2 output parameters; one result expression can stand for none of them; as statements, the construct "x" is outside the translated subset: an expression that assigns nothing is not a statement of the subset) {JavaScript}: - * x / 2 - */ + rep language "JavaScript" /* x / 2*/ } part def Gauge { attribute t_sim : ScalarValues::Real default = 4.0; @@ -85,87 +83,68 @@ part def Reactor { action cycle { first start then mark; action mark { + rep language "JavaScript" /* started = t_sim;*/ assign started := localClock.currentTime; } first mark then 'loop'; action 'loop' { - /* body not migrated (the construct "for" is outside the translated subset) {JavaScript}: - * for (var k = 0; k < 3; k++) { power = power + 1; } - */ + rep language "JavaScript" /* for (var k = 0; k < 3; k++) { power = power + 1; }*/ } first 'loop' then alloc; action alloc { - /* body not migrated (the construct "new" is outside the translated subset) {JavaScript}: - * power = new Number(4); - */ + rep language "JavaScript" /* power = new Number(4);*/ } first alloc then trim; action trim { - /* body not migrated (the call "label.trim" is not in the translated function table) {JavaScript}: - * label = label.trim(); - */ + rep language "JavaScript" /* label = label.trim();*/ } first trim then self2; action self2 { - /* body not migrated (the name "this.owner.power" resolves to nothing readable: Reactor has no feature owner) {JavaScript}: - * power = this.owner.power; - */ + rep language "JavaScript" /* power = this.owner.power;*/ } first self2 then stray; action stray { - /* body not migrated (the name "nowhere" resolves to nothing readable: nothing visible from Reactor::Cycle::stray is called nowhere) {JavaScript}: - * power = nowhere + 1; - */ + rep language "JavaScript" /* power = nowhere + 1;*/ } first stray then mixed; action mixed { - /* body not migrated (the construct "+" is outside the translated subset: string concatenation has no v2 form in the subset) {JavaScript}: - * power = label + 1; - */ + rep language "JavaScript" /* power = label + 1;*/ } first mixed then python; action python { + rep language "Python" /* power = power + 1*/ assign power := power + 1; } first python then partial; action partial { - /* body not migrated (the call "label.trim" is not in the translated function table) {JavaScript}: - * power = power + 1; label = label.trim(); - */ + rep language "JavaScript" /* power = power + 1; label = label.trim();*/ } first partial then shadow; action shadow { - /* body not migrated (the construct "var power" is outside the translated subset: power is already a feature here, which a declaration would shadow) {JavaScript}: - * var power = 1; power += 1; - */ + rep language "JavaScript" /* var power = 1; power += 1;*/ } first shadow then pinned; action pinned { in x : ScalarValues::Real[1] = 1.0; - /* body not migrated (the construct "var x" is outside the translated subset: x is already a feature here, which a declaration would shadow) {JavaScript}: - * var x = 1; x += 1; - */ + rep language "JavaScript" /* var x = 1; x += 1;*/ } first pinned then relay; action relay { in x : ScalarValues::Real[1] = 1.0; out y : ScalarValues::Real[1]; - /* body not migrated (the construct "x" is outside the translated subset: an input pin is not assigned) {JavaScript}: - * y = x * 2; x = y; - */ + rep language "JavaScript" /* y = x * 2; x = y;*/ } first relay then gain; action gain { in x : ScalarValues::Real[1] = 1.5; out y : ScalarValues::Real[1]; + rep language "JavaScript" /* y = x * power;*/ assign y := x * power; } first gain then bare; action bare { in raw[1] = 1.0; - /* body not migrated (the name "raw.level" resolves to nothing readable: Reactor::Cycle::bare::raw has no type, so no feature level) {JavaScript}: - * power = raw.level; - */ + rep language "JavaScript" /* power = raw.level;*/ } first bare then final; action final terminate; diff --git a/tests/migrate/testdata/xmi/script_guarded_reads.golden.sysml b/tests/migrate/testdata/xmi/script_guarded_reads.golden.sysml index 472a896a16..357c9e2c7c 100644 --- a/tests/migrate/testdata/xmi/script_guarded_reads.golden.sysml +++ b/tests/migrate/testdata/xmi/script_guarded_reads.golden.sysml @@ -6,17 +6,23 @@ part def Executive { action idle { out z : ScalarValues::Real[1]; out y : ScalarValues::Real[1]; + rep language "JavaScript" /* tdiff = tdiff * 1;*/ assign tdiff := tdiff * 1; } first idle then clamp; action clamp { in w : ScalarValues::Real[0..1]; + rep language "JavaScript" /* tMax = w; + * tdiff = tMax * 2;*/ if w->SequenceFunctions::notEmpty() { assign tMax := w; } assign tdiff := tMax * 2; } first clamp then settle; action settle { in w : ScalarValues::Real[0..1]; + rep language "JavaScript" /* tMax = 1; + * tMax = w; + * tdiff = tMax * 2;*/ assign tMax := 1; if w->SequenceFunctions::notEmpty() { assign tMax := w; } assign tdiff := tMax * 2; @@ -25,6 +31,9 @@ part def Executive { action pick { in w : ScalarValues::Real[0..1]; in v : ScalarValues::Real[0..1]; + rep language "JavaScript" /* tMax = w; + * tMax = v; + * tdiff = tMax * 2;*/ if w->SequenceFunctions::notEmpty() { assign tMax := w; } if v->SequenceFunctions::notEmpty() { assign tMax := v; } assign tdiff := tMax * 2; @@ -33,6 +42,9 @@ part def Executive { action early { in w : ScalarValues::Real[0..1]; in v : ScalarValues::Real[0..1]; + rep language "JavaScript" /* tMax = w; + * tdiff = tMax * 2; + * tMax = v;*/ if w->SequenceFunctions::notEmpty() { assign tMax := w; } assign tdiff := tMax * 2; if v->SequenceFunctions::notEmpty() { assign tMax := v; } diff --git a/tests/migrate/testdata/xmi/script_locals.golden.report.txt b/tests/migrate/testdata/xmi/script_locals.golden.report.txt index 7be4e2d3bd..405349f76d 100644 --- a/tests/migrate/testdata/xmi/script_locals.golden.report.txt +++ b/tests/migrate/testdata/xmi/script_locals.golden.report.txt @@ -3,8 +3,8 @@ ## approximated (5) OpaqueBehavior Executive::Executive Behavior::::::diffTime _diffTime -> Executive::'Executive Behavior'::unnamed2::diffTime (the JavaScript body is translated to v2; the clock variable simtime, named by the configuration Timing, reads the local clock; t1 is declared nowhere, so it is declared a local attribute of the action: a script's assignment to an undeclared name creates it; tdiffOld is declared nowhere, so it is declared a local attribute of the action: a script's assignment to an undeclared name creates it; the console print print(…) is left out, as it writes to the tool's console and changes nothing of the model; the JavaScript body is written as v2 assignments) -OpaqueBehavior Executive::Executive Behavior::::::javaDiff _javaDiff -> Executive::'Executive Behavior'::unnamed3::javaDiff (the body is kept as a comment: the name "tdiffOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::javaDiff is called tdiffOld) -OpaqueBehavior Executive::Executive Behavior::::::readFirst _readFirst -> Executive::'Executive Behavior'::unnamed4::readFirst (the body is kept as a comment: the name "stepsOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::readFirst is called stepsOld) +OpaqueBehavior Executive::Executive Behavior::::::javaDiff _javaDiff -> Executive::'Executive Behavior'::unnamed3::javaDiff (the body is kept as a textual representation, which is not executed: the name "tdiffOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::javaDiff is called tdiffOld) +OpaqueBehavior Executive::Executive Behavior::::::readFirst _readFirst -> Executive::'Executive Behavior'::unnamed4::readFirst (the body is kept as a textual representation, which is not executed: the name "stepsOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::readFirst is called stepsOld) OpaqueBehavior Executive::Executive Behavior::::::stamp _stamp -> Executive::'Executive Behavior'::unnamed::stamp (the JavaScript body is translated to v2; the clock variable simtime, named by the configuration Timing, reads the local clock; the JavaScript body is written as v2 assignments) «SimulationConfig» Class Timing _config -> Timing (neither Executive nor any general of it has a classifier behavior, so the configuration only holds 'Executive') diff --git a/tests/migrate/testdata/xmi/script_locals.golden.sysml b/tests/migrate/testdata/xmi/script_locals.golden.sysml index 88c1a1a579..703f8beb61 100644 --- a/tests/migrate/testdata/xmi/script_locals.golden.sysml +++ b/tests/migrate/testdata/xmi/script_locals.golden.sysml @@ -12,6 +12,7 @@ part def Executive { state def 'Executive Behavior' { in ref context : Executive[1]; entry action stamp { + rep language "JavaScript" /* t0 = simtime;*/ assign context.t0 := localClock.currentTime; } then Aligning; @@ -21,6 +22,11 @@ part def Executive { state Idle; transition first Aligning accept after 3.0 [SI::s] do action diffTime { + rep language "JavaScript" /* t1 = simtime; + * tdiffOld = tdiff; + * tdiff = (t1 - t0); + * tStep = tdiff - tdiffOld; + * print("step " + tStep);*/ attribute t1 : ScalarValues::Real; assign t1 := localClock.currentTime; attribute tdiffOld : ScalarValues::Real; @@ -31,18 +37,14 @@ part def Executive { then Phasing; transition first Phasing accept after 2.0 [SI::s] do action javaDiff { - /* body not migrated (the name "tdiffOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::javaDiff is called tdiffOld) {Java}: - * tdiffOld = tdiff; - * tdiff = tdiff + 1.0; - */ + rep language "Java" /* tdiffOld = tdiff; + * tdiff = tdiff + 1.0;*/ } then Counting; transition first Counting do action readFirst { - /* body not migrated (the name "stepsOld" resolves to nothing readable: nothing visible from Executive::Executive Behavior::::::readFirst is called stepsOld) {JavaScript}: - * steps = steps + stepsOld; - * stepsOld = steps; - */ + rep language "JavaScript" /* steps = steps + stepsOld; + * stepsOld = steps;*/ } then Idle; } diff --git a/tests/migrate/testdata/xmi/script_unset_reads.golden.sysml b/tests/migrate/testdata/xmi/script_unset_reads.golden.sysml index 6f724c5bff..830cd858b0 100644 --- a/tests/migrate/testdata/xmi/script_unset_reads.golden.sysml +++ b/tests/migrate/testdata/xmi/script_unset_reads.golden.sysml @@ -12,6 +12,7 @@ part def Executive { state def 'Executive Behavior' { in ref context : Executive[1]; entry action stamp { + rep language "JavaScript" /* t0 = simtime;*/ assign context.t0 := localClock.currentTime; } then Aligning; @@ -21,6 +22,10 @@ part def Executive { state Idle; transition first Aligning accept after 3.0 [SI::s] do action diffTime { + rep language "JavaScript" /* t1 = simtime; + * tdiffOld = tdiff; + * tdiff = t1 - t0; + * tStep = tdiff - tdiffOld;*/ attribute t1 : ScalarValues::Real; assign t1 := localClock.currentTime; attribute tdiffOld : ScalarValues::Real; @@ -31,12 +36,16 @@ part def Executive { then Phasing; transition first Phasing accept after 2.0 [SI::s] do action reset { + rep language "JavaScript" /* tdiff = 0; + * tStep = tdiff + steps;*/ assign context.tdiff := 0; assign context.tStep := context.tdiff + context.steps; } then Counting; transition first Counting do action count { + rep language "JavaScript" /* steps++; + * tdiff += steps;*/ assign context.steps := context.steps + 1; assign context.tdiff := context.tdiff + context.steps; } diff --git a/tests/migrate/testdata/xmi/station_points.golden.report.txt b/tests/migrate/testdata/xmi/station_points.golden.report.txt index 8e0c437c95..5e97714204 100644 --- a/tests/migrate/testdata/xmi/station_points.golden.report.txt +++ b/tests/migrate/testdata/xmi/station_points.golden.report.txt @@ -24,15 +24,15 @@ OpaqueBehavior Station::Cycle::main::Sync::b::B1:: _b1Entry -> S OpaqueBehavior Station::Cycle::main::Sync::b::B1:: _b1Exit -> Station::Cycle::Sync::regions::b::B1::unnamed2 (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) OpaqueBehavior Station::Cycle::main::Work:: _workEntry -> Station::Cycle::Work::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) OpaqueBehavior Station::Cycle::main::Work:: _workExit -> Station::Cycle::Work::unnamed2 (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps:::: _startEff -> Station::Cycle::Work::unnamed::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps:::: _finishEff -> Station::Cycle::Work::unnamed2::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps:::: _outEff -> Station::Cycle::Work::unnamed3::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Prep:: _prepEntry -> Station::Cycle::Work::Prep::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Run:: _runEntry -> Station::Cycle::Work::Run::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Run:: _runExit -> Station::Cycle::Work::Run::unnamed2 (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace:::: _deepEff -> Station::Cycle::Work::Run::unnamed::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace:::: _backEff -> Station::Cycle::Work::Run::unnamed2::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) -OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace::Fast:: _fastEntry -> Station::Cycle::Work::Run::Fast::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps:::: _startEff -> Station::Cycle::Work::steps::unnamed::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps:::: _finishEff -> Station::Cycle::Work::steps::unnamed2::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps:::: _outEff -> Station::Cycle::Work::steps::unnamed3::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Prep:: _prepEntry -> Station::Cycle::Work::steps::Prep::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Run:: _runEntry -> Station::Cycle::Work::steps::Run::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Run:: _runExit -> Station::Cycle::Work::steps::Run::unnamed2 (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace:::: _deepEff -> Station::Cycle::Work::steps::Run::pace::unnamed::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace:::: _backEff -> Station::Cycle::Work::steps::Run::pace::unnamed2::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) +OpaqueBehavior Station::Cycle::main::Work::steps::Run::pace::Fast:: _fastEntry -> Station::Cycle::Work::steps::Run::pace::Fast::unnamed (the JavaScript body is translated to v2; the JavaScript body is written as v2 assignments) ## mapped (97) SignalEvent _backEv (written where a trigger refers to it, as accept Back) @@ -62,12 +62,12 @@ Class Station _station -> Station Region Station::Cycle::main _main (the one region is written as the body of its owner) Pseudostate Station::Cycle::main:: _init0 (written as the entry of the region) Transition Station::Cycle::main:: _t0 -Transition Station::Cycle::main:: _tGo (the target 'Start' lies in another region and is named by its path Work.Start) +Transition Station::Cycle::main:: _tGo (the target 'Start' lies in another region and is named by its path Work.steps.Start) Transition Station::Cycle::main:: _tNever Transition Station::Cycle::main:: _tEnter (written to Work: no transition leaves the entry point 'Plain', so entering through it enters the state by its default entry) -Transition Station::Cycle::main:: _tDive (the target 'Deep' lies in another region and is named by its path Work.Run.Deep) -Transition Station::Cycle::main:: _tResume (the target 'H' lies in another region and is named by its path Work.H) -Transition Station::Cycle::main:: _tLeave (the source 'Leave' lies in another region and is named by its path Work.Leave) +Transition Station::Cycle::main:: _tDive (the target 'Deep' lies in another region and is named by its path Work.steps.Run.pace.Deep) +Transition Station::Cycle::main:: _tResume (the target 'H' lies in another region and is named by its path Work.steps.H) +Transition Station::Cycle::main:: _tLeave (the source 'Leave' lies in another region and is named by its path Work.steps.Leave) Transition Station::Cycle::main:: _tStop Transition Station::Cycle::main:: _tPair Transition Station::Cycle::main:: _tSplit (the target 'Both' lies in another region and is named by its path Sync.Both) @@ -85,7 +85,7 @@ State Station::Cycle::main::Idle _idle -> Idle State Station::Cycle::main::Sync _sync -> Sync Pseudostate Station::Cycle::main::Sync::Both _both -> Both (written as a fork of its state, whose branches start its regions; a transition entering through it runs the state's entry behavior, then the branches) Pseudostate Station::Cycle::main::Sync::Gather _gather -> Gather (written as a join of its state, which its regions leave through together; the transitions into the join run, then the state's exit behavior, then the transition leaving it) -Region Station::Cycle::main::Sync::a _ra -> a (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Station::Cycle::main::Sync::a _ra -> a (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Station::Cycle::main::Sync::a:: _initA (written as the entry of the region) Transition Station::Cycle::main::Sync::a:: _tAi Transition Station::Cycle::main::Sync::a:: _tA @@ -95,7 +95,7 @@ Constraint Station::Cycle::main::Sync::a:::: _bothAGuard Trigger Station::Cycle::main::Sync::a:::: _trA State Station::Cycle::main::Sync::a::A0 _a0 -> A0 State Station::Cycle::main::Sync::a::A1 _a1 -> A1 -Region Station::Cycle::main::Sync::b _rb -> b (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Station::Cycle::main::Sync::b _rb -> b (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Station::Cycle::main::Sync::b:: _initB (written as the entry of the region) Transition Station::Cycle::main::Sync::b:: _tBi Transition Station::Cycle::main::Sync::b:: _tB @@ -108,13 +108,13 @@ State Station::Cycle::main::Work _work -> Work Pseudostate Station::Cycle::main::Work::Leave _leave -> Leave (written as a `#StateMachines::junction state` of its state; a transition leaving through it runs the transition into the junction, the state's exit behavior, then the transition leaving it) Pseudostate Station::Cycle::main::Work::Plain _plain -> Work (no transition leaves the entry point, so entering through it enters 'Work' by its default entry; a transition to it is written to the state) Pseudostate Station::Cycle::main::Work::Start _start -> Start (written as a `#StateMachines::junction state` of its state; a transition entering through it runs the state's entry behavior, then the transition leaving the junction) -Region Station::Cycle::main::Work::steps _steps (the one region is written as the body of its owner) +Region Station::Cycle::main::Work::steps _steps -> steps (the region is written as the sub-state steps of the parallel state Work, as a composite state's regions are) Pseudostate Station::Cycle::main::Work::steps:: _initW (written as the entry of the region) Transition Station::Cycle::main::Work::steps:: _tWi Transition Station::Cycle::main::Work::steps:: _tNext -Transition Station::Cycle::main::Work::steps:: _tStart (the source 'Start' lies in another region and is named by its path Start) -Transition Station::Cycle::main::Work::steps:: _tFinish (the target 'Leave' lies in another region and is named by its path Leave) -Transition Station::Cycle::main::Work::steps:: _tOut (the source 'Out' lies in another region and is named by its path Run.Out) +Transition Station::Cycle::main::Work::steps:: _tStart +Transition Station::Cycle::main::Work::steps:: _tFinish +Transition Station::Cycle::main::Work::steps:: _tOut (the source 'Out' lies in another region and is named by its path steps.Run.pace.Out) Trigger Station::Cycle::main::Work::steps:::: _trNext Trigger Station::Cycle::main::Work::steps:::: _trFinish Pseudostate Station::Cycle::main::Work::steps::H _hist -> H (written as a `#StateMachines::shallowHistory state` shallow history, which re-enters the substate active when its state was last left) @@ -122,11 +122,11 @@ State Station::Cycle::main::Work::steps::Prep _prep -> Prep State Station::Cycle::main::Work::steps::Run _run -> Run Pseudostate Station::Cycle::main::Work::steps::Run::Deep _deep -> Deep (written as a `#StateMachines::junction state` of its state; a transition entering through it runs the state's entry behavior, then the transition leaving the junction) Pseudostate Station::Cycle::main::Work::steps::Run::Out _out -> Out (written as a `#StateMachines::junction state` of its state; a transition leaving through it runs the transition into the junction, the state's exit behavior, then the transition leaving it) -Region Station::Cycle::main::Work::steps::Run::pace _pace (the one region is written as the body of its owner) +Region Station::Cycle::main::Work::steps::Run::pace _pace -> pace (the region is written as the sub-state pace of the parallel state Run, as a composite state's regions are) Pseudostate Station::Cycle::main::Work::steps::Run::pace:: _initR (written as the entry of the region) Transition Station::Cycle::main::Work::steps::Run::pace:: _tRi -Transition Station::Cycle::main::Work::steps::Run::pace:: _tDeep (the source 'Deep' lies in another region and is named by its path Deep) -Transition Station::Cycle::main::Work::steps::Run::pace:: _tBack (the target 'Out' lies in another region and is named by its path Out) +Transition Station::Cycle::main::Work::steps::Run::pace:: _tDeep +Transition Station::Cycle::main::Work::steps::Run::pace:: _tBack Trigger Station::Cycle::main::Work::steps::Run::pace:::: _trBack State Station::Cycle::main::Work::steps::Run::pace::Fast _fast -> Fast State Station::Cycle::main::Work::steps::Run::pace::Slow _slow -> Slow diff --git a/tests/migrate/testdata/xmi/station_points.golden.sysml b/tests/migrate/testdata/xmi/station_points.golden.sysml index f316c4e8f4..aa5d346cd5 100644 --- a/tests/migrate/testdata/xmi/station_points.golden.sysml +++ b/tests/migrate/testdata/xmi/station_points.golden.sysml @@ -16,74 +16,91 @@ occurrence def Station { in ref context : Station[1]; entry; then Idle; state Idle; - state Work { + state Work parallel { entry action { + rep language "JavaScript" /* trace = trace * 100 + 11;*/ assign context.trace := context.trace * 100 + 11; } - then Prep; exit action { + rep language "JavaScript" /* trace = trace * 100 + 12;*/ assign context.trace := context.trace * 100 + 12; } - #StateMachines::junction state Start; - /* not migrated: Pseudostate 'Through' — (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */ - #StateMachines::junction state Leave; - state Prep { - entry action { - assign context.trace := context.trace * 100 + 13; - } - } - state Run { - entry action { - assign context.trace := context.trace * 100 + 14; - } - then Slow; - exit action { - assign context.trace := context.trace * 100 + 15; + state steps { + #StateMachines::junction state Start; + /* not migrated: Pseudostate 'Through' — (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */ + #StateMachines::junction state Leave; + entry; then Prep; + state Prep { + entry action { + rep language "JavaScript" /* trace = trace * 100 + 13;*/ + assign context.trace := context.trace * 100 + 13; + } } - #StateMachines::junction state Deep; - #StateMachines::junction state Out; - state Slow; - state Fast { + state Run parallel { entry action { - assign context.trace := context.trace * 100 + 17; + rep language "JavaScript" /* trace = trace * 100 + 14;*/ + assign context.trace := context.trace * 100 + 14; + } + exit action { + rep language "JavaScript" /* trace = trace * 100 + 15;*/ + assign context.trace := context.trace * 100 + 15; + } + state pace { + #StateMachines::junction state Deep; + #StateMachines::junction state Out; + entry; then Slow; + state Slow; + state Fast { + entry action { + rep language "JavaScript" /* trace = trace * 100 + 17;*/ + assign context.trace := context.trace * 100 + 17; + } + } + transition first Deep + do action { + rep language "JavaScript" /* trace = trace * 100 + 21;*/ + assign context.trace := context.trace * 100 + 21; + } + then Fast; + transition first Fast accept Back + do action { + rep language "JavaScript" /* trace = trace * 100 + 22;*/ + assign context.trace := context.trace * 100 + 22; + } + then Out; } } - transition first Deep + #StateMachines::shallowHistory state H; + transition first Prep accept Next then Run; + transition first Start do action { - assign context.trace := context.trace * 100 + 21; + rep language "JavaScript" /* trace = trace * 100 + 23;*/ + assign context.trace := context.trace * 100 + 23; } - then Fast; - transition first Fast accept Back + then Run; + /* not migrated: Transition (_tThrough) — the source 'Through' has no v2 form: (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */ + transition first Run accept Finish do action { - assign context.trace := context.trace * 100 + 22; + rep language "JavaScript" /* trace = trace * 100 + 24;*/ + assign context.trace := context.trace * 100 + 24; } - then Out; + then Leave; + transition first steps.Run.pace.Out + do action { + rep language "JavaScript" /* trace = trace * 100 + 25;*/ + assign context.trace := context.trace * 100 + 25; + } + then Prep; } - #StateMachines::shallowHistory state H; - transition first Prep accept Next then Run; - transition first Start - do action { - assign context.trace := context.trace * 100 + 23; - } - then Run; - /* not migrated: Transition (_tThrough) — the source 'Through' has no v2 form: (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */ - transition first Run accept Finish - do action { - assign context.trace := context.trace * 100 + 24; - } - then Leave; - transition first Run.Out - do action { - assign context.trace := context.trace * 100 + 25; - } - then Prep; } state Sync { entry action { + rep language "JavaScript" /* trace = trace * 100 + 31;*/ assign context.trace := context.trace * 100 + 31; } then regions; exit action { + rep language "JavaScript" /* trace = trace * 100 + 32;*/ assign context.trace := context.trace * 100 + 32; } fork Both; @@ -94,9 +111,11 @@ occurrence def Station { state A0; state A1 { entry action { + rep language "JavaScript" /* trace = trace * 100 + 33;*/ assign context.trace := context.trace * 100 + 33; } exit action { + rep language "JavaScript" /* trace = trace * 100 + 34;*/ assign context.trace := context.trace * 100 + 34; } } @@ -107,9 +126,11 @@ occurrence def Station { state B0; state B1 { entry action { + rep language "JavaScript" /* trace = trace * 100 + 35;*/ assign context.trace := context.trace * 100 + 35; } exit action { + rep language "JavaScript" /* trace = trace * 100 + 36;*/ assign context.trace := context.trace * 100 + 36; } } @@ -120,12 +141,14 @@ occurrence def Station { transition first Both then regions.a.A1; transition first regions.a.A1 do action { + rep language "JavaScript" /* trace = trace * 100 + 37;*/ assign context.trace := context.trace * 100 + 37; } then Gather; transition first Both then regions.b.B1; transition first regions.b.B1 do action { + rep language "JavaScript" /* trace = trace * 100 + 38;*/ assign context.trace := context.trace * 100 + 38; } then Gather; @@ -133,25 +156,29 @@ occurrence def Station { } transition first Idle accept Go do action { + rep language "JavaScript" /* trace = trace * 100 + 41;*/ assign context.trace := context.trace * 100 + 41; } - then Work.Start; + then Work.steps.Start; transition first Idle accept Go if false then Work; transition first Idle accept Enter then Work; transition first Idle accept Dive do action { + rep language "JavaScript" /* trace = trace * 100 + 42;*/ assign context.trace := context.trace * 100 + 42; } - then Work.Run.Deep; - transition first Idle accept Resume then Work.H; + then Work.steps.Run.pace.Deep; + transition first Idle accept Resume then Work.steps.H; /* not migrated: Transition (_tSkip) — the target 'Through' has no v2 form: (_tThrough) leads from the entry point straight to the exit point 'Leave' of the same state, crossing it without settling in it; the runtime would then run neither its entry nor its exit behavior */ - transition first Work.Leave + transition first Work.steps.Leave do action { + rep language "JavaScript" /* trace = trace * 100 + 43;*/ assign context.trace := context.trace * 100 + 43; } then Idle; transition first Work accept Stop do action { + rep language "JavaScript" /* trace = trace * 100 + 44;*/ assign context.trace := context.trace * 100 + 44; } then Idle; @@ -159,6 +186,7 @@ occurrence def Station { transition first Idle accept Split then Sync.Both; transition first Sync.Gather do action { + rep language "JavaScript" /* trace = trace * 100 + 46;*/ assign context.trace := context.trace * 100 + 46; } then Idle; diff --git a/tests/migrate/testdata/xmi/submachine_params.golden.sysml b/tests/migrate/testdata/xmi/submachine_params.golden.sysml index ba3230ee75..cc78da86f2 100644 --- a/tests/migrate/testdata/xmi/submachine_params.golden.sysml +++ b/tests/migrate/testdata/xmi/submachine_params.golden.sysml @@ -9,6 +9,7 @@ part def Plant { in x[1]; in ref :>> context = Line::context; entry action tally { + rep language "JavaScript" /* count = count + 1;*/ assign context.count := context.count + 1; } } diff --git a/tests/migrate/testdata/xmi/terminate_orthogonal.golden.report.txt b/tests/migrate/testdata/xmi/terminate_orthogonal.golden.report.txt new file mode 100644 index 0000000000..038983a511 --- /dev/null +++ b/tests/migrate/testdata/xmi/terminate_orthogonal.golden.report.txt @@ -0,0 +1,27 @@ +# SysML v1 to v2 migration report: terminate_orthogonal.xmi +# migrated 21 element(s): 20 mapped, 1 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## approximated (1) +StateMachine Machine::Run _run -> Machine::Run (the classifier behavior is run by every object of Machine as its usage run) + +## mapped (20) +SignalEvent _stopEvent (written where a trigger refers to it, as accept Stop) +SignalEvent _pingEvent (written where a trigger refers to it, as accept Ping) +«Block» Class Machine _machine -> Machine +Region Machine::Run::R1 _r1 -> R1 (an orthogonal region is written as a sub-state of the parallel state regions) +Pseudostate Machine::Run::R1:: _r1Initial (written as the entry of the region) +Pseudostate Machine::Run::R1:: _end -> terminated (written as a terminate action, which ends the state machine's performance in every region) +Transition Machine::Run::R1:: _r1Start +Transition Machine::Run::R1:: _stopTransition +Trigger Machine::Run::R1:::: _stopTrigger +State Machine::Run::R1::A _a -> A +Region Machine::Run::R2 _r2 -> R2 (an orthogonal region is written as a sub-state of the parallel state regions) +Pseudostate Machine::Run::R2:: _r2Initial (written as the entry of the region) +Transition Machine::Run::R2:: _r2Start +Transition Machine::Run::R2:: _pingTransition +Trigger Machine::Run::R2:::: _pingTrigger +State Machine::Run::R2::B _b -> B +State Machine::Run::R2::C _c -> C +Model Model _m (the root model's members are written at the top level) +Signal Ping _ping -> Ping +Signal Stop _stop -> Stop diff --git a/tests/migrate/testdata/xmi/terminate_orthogonal.golden.sysml b/tests/migrate/testdata/xmi/terminate_orthogonal.golden.sysml new file mode 100644 index 0000000000..eb17fcc615 --- /dev/null +++ b/tests/migrate/testdata/xmi/terminate_orthogonal.golden.sysml @@ -0,0 +1,25 @@ +item def Stop; +item def Ping; +part def Machine { + state def Run { + entry; then regions; + state regions parallel { + state R1 { + entry; then A; + state A; + action terminated terminate; + transition first A accept Stop then terminated; + metadata MigrationMetadata::SynthesizedName about terminated; + } + state R2 { + entry; then B; + state B; + state C; + transition first B accept Ping then C; + } + } + transition first regions then done; + metadata MigrationMetadata::SynthesizedName about regions; + } + exhibit state run : Run; +} diff --git a/tests/migrate/testdata/xmi/terminate_orthogonal.xmi b/tests/migrate/testdata/xmi/terminate_orthogonal.xmi new file mode 100644 index 0000000000..6d56047521 --- /dev/null +++ b/tests/migrate/testdata/xmi/terminate_orthogonal.xmi @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/migrate/testdata/xmi/transition_relocation.golden.report.txt b/tests/migrate/testdata/xmi/transition_relocation.golden.report.txt index 0a3f2a4738..6f60233db3 100644 --- a/tests/migrate/testdata/xmi/transition_relocation.golden.report.txt +++ b/tests/migrate/testdata/xmi/transition_relocation.golden.report.txt @@ -13,21 +13,21 @@ Region Plant::Life::main _main (the one region is written as the body of its own Pseudostate Plant::Life::main:: _init (written as the entry of the region) Transition Plant::Life::main:: _enterWork State Plant::Life::main::Parallel _parallel -> Parallel -Region Plant::Life::main::Parallel::region3 _region3 -> region3 (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Parallel::region3 _region3 -> region3 (an orthogonal region is written as a sub-state of the parallel state regions) Pseudostate Plant::Life::main::Parallel::region3:: _region3Init (written as the entry of the region) Transition Plant::Life::main::Parallel::region3:: _enterWait3 State Plant::Life::main::Parallel::region3::wait3 _wait3 -> wait3 -Region Plant::Life::main::Parallel::region4 _region4 -> region4 (the one region is written as the body of its owner; an orthogonal region is written as a sub-state of the parallel state regions) +Region Plant::Life::main::Parallel::region4 _region4 -> region4 (an orthogonal region is written as a sub-state of the parallel state regions) State Plant::Life::main::Parked _parked -> Parked -Region Plant::Life::main::Parked::parked _parkedRegion (the one region is written as the body of its owner) +Region Plant::Life::main::Parked::parked _parkedRegion -> parked (the region is written as the sub-state parked of the parallel state Parked, as a composite state's regions are) Pseudostate Plant::Life::main::Parked::parked:: _parkedInit (written as the entry of the region) Transition Plant::Life::main::Parked::parked:: _enterReady State Plant::Life::main::Parked::parked::Ready _ready -> Ready State Plant::Life::main::Work _work -> Work -Region Plant::Life::main::Work::work _workRegion (the one region is written as the body of its owner) +Region Plant::Life::main::Work::work _workRegion -> work (the region is written as the sub-state work of the parallel state Work, as a composite state's regions are) Pseudostate Plant::Life::main::Work::work:: _workInit (written as the entry of the region) Transition Plant::Life::main::Work::work:: _enterRun -Transition Plant::Life::main::Work::work:: _leaveRun (the source 'Run' lies in another region and is named by its path Work.Run; the target 'Ready' lies in another region and is named by its path Parked.Ready) +Transition Plant::Life::main::Work::work:: _leaveRun (the source 'Run' lies in another region and is named by its path Work.work.Run; the target 'Ready' lies in another region and is named by its path Parked.parked.Ready) Trigger Plant::Life::main::Work::work:::: _stopTrigger State Plant::Life::main::Work::work::Run _run -> Run Signal Stop _stop -> Stop diff --git a/tests/migrate/testdata/xmi/transition_relocation.golden.sysml b/tests/migrate/testdata/xmi/transition_relocation.golden.sysml index a9145e55f8..21fcbb7b30 100644 --- a/tests/migrate/testdata/xmi/transition_relocation.golden.sysml +++ b/tests/migrate/testdata/xmi/transition_relocation.golden.sysml @@ -2,16 +2,20 @@ item def Stop; part def Plant { state def Life { entry; then Work; - state Work { - entry; then Run; - state Run; + state Work parallel { + state work { + entry; then Run; + state Run; + } } - state Parked { - entry; then Ready; - state Ready; + state Parked parallel { + state parked { + entry; then Ready; + state Ready; + } } state Parallel { - /* no default entry: these regions have no written entry: 'region4'; only a fork or a transition naming a nested state enters them */ + entry; then regions; state regions parallel { state region3 { entry; then wait3; @@ -24,6 +28,6 @@ part def Plant { transition first regions then done; metadata MigrationMetadata::SynthesizedName about regions; } - transition first Work.Run accept Stop then Parked.Ready; + transition first Work.work.Run accept Stop then Parked.parked.Ready; } } diff --git a/tests/migrate/testdata/xmi/user_library.golden.report.txt b/tests/migrate/testdata/xmi/user_library.golden.report.txt index 892b0e77cc..33cda81564 100644 --- a/tests/migrate/testdata/xmi/user_library.golden.report.txt +++ b/tests/migrate/testdata/xmi/user_library.golden.report.txt @@ -1,16 +1,15 @@ # SysML v1 to v2 migration report: user_library.xmi # exported by MagicDraw UML -# migrated 20 element(s): 16 mapped, 3 approximated, 1 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 20 element(s): 17 mapped, 2 approximated, 1 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) ## unmapped (1) CallBehaviorAction Recorder::Tally::get _get -> get (the behavior fUML_Library::PrimitiveBehaviors::ListFunctions::ListGet it calls has no v2 declaration) -## approximated (3) +## approximated (2) OutputPin Recorder::Tally::get::result _getOut -> Recorder::Tally::get.result (it is declared admitting no value: it belongs to 'get', which is not migrated and produces no value) -FunctionBehavior fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize _userListSize -> fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize (the body is kept as a comment: the body is empty) Parameter fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize::result _userListSizeResult -> fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize::result (the return parameter is written as an out parameter) -## mapped (16) +## mapped (17) Model Model _m (the root model's members are written at the top level) «Block» Class Recorder _recorder -> Recorder Activity Recorder::Tally _tally -> Recorder::Tally @@ -26,4 +25,5 @@ OutputPin Recorder::Tally::size::result _sizeOut -> Recorder::Tally::size.result Package fUML_Library _userLibrary -> fUML_Library Package fUML_Library::PrimitiveBehaviors _userPrimitives -> fUML_Library::PrimitiveBehaviors Package fUML_Library::PrimitiveBehaviors::ListFunctions _userLists -> fUML_Library::PrimitiveBehaviors::ListFunctions +FunctionBehavior fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize _userListSize -> fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize Parameter fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize::list _userListSizeList -> fUML_Library::PrimitiveBehaviors::ListFunctions::ListSize::list diff --git a/tests/migrate/testdata/xmi/user_library.golden.sysml b/tests/migrate/testdata/xmi/user_library.golden.sysml index 1cad9d655b..d5ed507b32 100644 --- a/tests/migrate/testdata/xmi/user_library.golden.sysml +++ b/tests/migrate/testdata/xmi/user_library.golden.sysml @@ -4,7 +4,6 @@ package fUML_Library { action def ListSize { in list : ScalarValues::String[0..*] ordered nonunique; out result : ScalarValues::Integer[1]; - /* body not migrated (the body is empty) */ } } } diff --git a/tests/migrate/testdata/xmi/weighted_decision.golden.report.txt b/tests/migrate/testdata/xmi/weighted_decision.golden.report.txt index 6e8e8abef7..66da5f1766 100644 --- a/tests/migrate/testdata/xmi/weighted_decision.golden.report.txt +++ b/tests/migrate/testdata/xmi/weighted_decision.golden.report.txt @@ -1,13 +1,10 @@ # SysML v1 to v2 migration report: weighted_decision.xmi -# migrated 16 element(s): 12 mapped, 4 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 16 element(s): 15 mapped, 1 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (4) +## approximated (1) «Probability» ControlFlow Sorter::Route:: _eReject («Probability» from http://example.com/routing/profile is not the SysML profile's; its probability is not read; the edge carries no «Probability»: it is weighted 1.0 - (bias + 0.3), its share of what the marked branches leave of 1, read when the decision is reached) -OpaqueAction Sorter::Route::left _left -> left (the body is kept as a comment: the body is empty) -OpaqueAction Sorter::Route::reject _reject -> reject (the body is kept as a comment: the body is empty) -OpaqueAction Sorter::Route::right _right -> right (the body is kept as a comment: the body is empty) -## mapped (12) +## mapped (15) Model Sorter _m (the root model's members are written at the top level) «Block» Class Sorter _sorter -> Sorter Activity Sorter::Route _act -> Sorter::route (written as an action usage every object of Sorter performs from creation, so its body runs on the object and reaches its features) @@ -19,4 +16,7 @@ ControlFlow Sorter::Route:: _fRight ControlFlow Sorter::Route:: _fReject DecisionNode Sorter::Route:: _decide -> 'decide' InitialNode Sorter::Route:: _init -> start +OpaqueAction Sorter::Route::left _left -> left (its body is empty, so it is written as an action that does nothing) +OpaqueAction Sorter::Route::reject _reject -> reject (its body is empty, so it is written as an action that does nothing) +OpaqueAction Sorter::Route::right _right -> right (its body is empty, so it is written as an action that does nothing) Property Sorter::bias _bias -> Sorter::bias diff --git a/tests/migrate/testdata/xmi/weighted_decision.golden.sysml b/tests/migrate/testdata/xmi/weighted_decision.golden.sysml index 6436a3b36e..b8020be9a5 100644 --- a/tests/migrate/testdata/xmi/weighted_decision.golden.sysml +++ b/tests/migrate/testdata/xmi/weighted_decision.golden.sysml @@ -6,17 +6,11 @@ part def Sorter { first 'decide' then left { @Stochastic::Probability { p = bias; } } first 'decide' then right { @Stochastic::Probability { p = 0.3; } } first 'decide' then reject { @Stochastic::Probability { p = 1.0 - (bias + 0.3); } } - action left { - /* body not migrated (the body is empty) */ - } + action left; first left then 'merge'; - action right { - /* body not migrated (the body is empty) */ - } + action right; first right then 'merge'; - action reject { - /* body not migrated (the body is empty) */ - } + action reject; first reject then 'merge'; merge 'merge'; first 'merge' then final; diff --git a/tools/referee/pssm/classify.go b/tools/referee/pssm/classify.go index aa31360799..6ddb208a73 100644 --- a/tools/referee/pssm/classify.go +++ b/tools/referee/pssm/classify.go @@ -73,9 +73,6 @@ const ( ConstructTesterTrace Construct = "tester trace" ConstructGuardSideEffect Construct = "guard side effect" ConstructGuardBehaviorUnread Construct = "guard behavior not read" - // This project's lowerer refusing a shape UML allows and v2 can spell: - // a candidate gap of ours, recorded apart from v2's missing spellings. - ConstructRegionNoEntry Construct = "lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it" // Recorded but not deciding: a pseudostate filed as a connection point that // is neither an entry nor an exit point and that no transition reaches. ConstructStrayConnectionPoint Construct = "stray connection point" @@ -108,7 +105,6 @@ var constructClass = map[Construct]Expressibility{ ConstructTesterTrace: NotExpressible, ConstructGuardSideEffect: NotExpressible, ConstructGuardBehaviorUnread: NotExpressible, - ConstructRegionNoEntry: NotExpressible, ConstructFork: Extension, ConstructJoin: Extension, ConstructJunction: Extension, @@ -125,10 +121,6 @@ type Use struct { Where string } -// Ours reports whether the construct is unspellable because of this project's -// lowerer rather than because SysML v2 has no notation for it. -func (c Construct) Ours() bool { return c == ConstructRegionNoEntry } - func (u Use) String() string { if u.Where == "" { return string(u.Construct) @@ -170,10 +162,9 @@ func Classify(t *Test) Classification { add(ConstructRedefinedMachine, t.Machine.Name) } w := &walker{ - add: add, - reached: reachedVertices(t.Machine.Regions), - forkEntered: forkEnteredRegions(t.Machine.Regions), - unbound: unboundBehaviors(BindBehaviors(t.Machine)), + add: add, + reached: reachedVertices(t.Machine.Regions), + unbound: unboundBehaviors(BindBehaviors(t.Machine)), } w.connectionPoints(t.Machine.ConnectionPoints) w.regions(t.Machine.Regions) @@ -225,88 +216,9 @@ func reachedVertices(regions []*Region) map[*Vertex]bool { return reached } -// forkEnteredRegions collects every region a fork's branches start, as the -// lowerer plans them: the regions of the fork's owner — the innermost orthogonal -// state below every target — each target lies in, however deep. -func forkEnteredRegions(regions []*Region) map[*Region]bool { - targets := map[*Vertex][]*Vertex{} - var visit func([]*Region) - visit = func(regions []*Region) { - for _, r := range regions { - for _, tr := range r.Transitions { - if tr.Source != nil && tr.Source.Kind == VertexFork && tr.Target != nil { - targets[tr.Source] = append(targets[tr.Source], tr.Target) - } - } - for _, v := range r.Vertices { - visit(v.Regions) - } - } - } - visit(regions) - entered := map[*Region]bool{} - for _, branches := range targets { - owner := forkOwner(branches) - for _, target := range branches { - if region := regionUnder(owner, target); region != nil { - entered[region] = true - } - } - } - return entered -} - -// parentState is the state whose region declares v, nil at the machine's. -func parentState(v *Vertex) *Vertex { - if v.Region == nil { - return nil - } - return v.Region.owner -} - -// forkOwner is the innermost orthogonal state every target lies below, nil when -// only the machine encloses them all. -func forkOwner(targets []*Vertex) *Vertex { - owner := parentState(targets[0]) - for _, target := range targets[1:] { - for owner != nil && !within(owner, target) { - owner = parentState(owner) - } - } - for owner != nil && len(owner.Regions) < 2 { - owner = parentState(owner) - } - return owner -} - -// within reports whether v is owner or lies below it. -func within(owner, v *Vertex) bool { - for s := v; s != nil; s = parentState(s) { - if s == owner { - return true - } - } - return false -} - -// regionUnder is the region of owner that v lies in, nil when owner is the -// machine or v is not below it. -func regionUnder(owner, v *Vertex) *Region { - if owner == nil { - return nil - } - for s := v; s != nil && s != owner; s = parentState(s) { - if s.Region != nil && s.Region.owner == owner { - return s.Region - } - } - return nil -} - type walker struct { - add func(Construct, string) - reached map[*Vertex]bool - forkEntered map[*Region]bool + add func(Construct, string) + reached map[*Vertex]bool // unbound are the behaviors with parameters no event binds. unbound map[*Behavior]bool } @@ -339,7 +251,6 @@ func (w *walker) regions(regions []*Region) { if r.ExtendedRegion != "" { w.add(ConstructExtendedRegion, r.Name) } - w.initial(r) for _, v := range r.Vertices { w.vertex(v) } @@ -359,20 +270,6 @@ func (w *walker) regions(regions []*Region) { } } -// initial records an orthogonal region with neither an initial pseudostate nor -// a fork branch into it: UML allows it, the lowerer refuses it. -func (w *walker) initial(r *Region) { - if r.owner == nil || len(r.owner.Regions) < 2 || w.forkEntered[r] { - return - } - for _, v := range r.Vertices { - if v.Kind == VertexInitial { - return - } - } - w.add(ConstructRegionNoEntry, r.owner.Path()+"/"+r.Name) -} - // guard records a guard whose behavior does more than compute its value, or // whose behavior the reader does not follow: a v2 guard is an expression, and // the evaluator admits no side effect in one. diff --git a/tools/referee/pssm/classify_test.go b/tools/referee/pssm/classify_test.go index 7eaffe0fd0..ace2fcdce3 100644 --- a/tools/referee/pssm/classify_test.go +++ b/tools/referee/pssm/classify_test.go @@ -158,6 +158,13 @@ func TestClassifyTerminateIsStandard(t *testing.T) { } } +func TestClassifyInactiveOrthogonalRegionAsStandard(t *testing.T) { + c := classifyFixture(t, "", ``) + if c.Class != Standard { + t.Fatalf("classified %s (%s), want standard", c.Class, c.Reason()) + } +} + func TestClassifyNoSpellingOutranksAll(t *testing.T) { cases := []struct { name, connectionPoints, body, want string @@ -169,8 +176,6 @@ func TestClassifyNoSpellingOutranksAll(t *testing.T) { {"local transition", "", ``, "local transition TL"}, {"internal transition", "", ``, "internal transition TI"}, {"extended region", "", ``, "extended region R"}, - {"orthogonal region nothing enters", "", ``, "lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it O/R2"}, - {"orthogonal region of a nested orthogonal state a fork passes through", "", ``, "lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it O.N/P; lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it O.N/Q"}, {"redefined state", "", ``, "redefined state R"}, {"guard side effect", "", guardWithSideEffect, "guard side effect T3"}, {"redefined transition", "", ``, "redefined transition TR"},