Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unreleased/migrate-sender-and-wait-notes.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **The migrator's ledger counts a send action as a sender only once it is written, and a button of the tool's UI prototype as posting the signals it specializes.** An accept of a signal is noted as waiting for a message only the tool's UI posts when no written send sends it or a signal specializing it — a send left as a placeholder, which performs nothing, no longer hides the note — and a button posting a specialization of the accepted signal now raises it. A send whose port is not the sender's keeps the note on its arguments beside the one on its port, and an entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes, not for a time event or duration constraint it leaves as a placeholder, nor for one in a behavior named by a call written as a placeholder or never firing, which invokes nothing.
4 changes: 2 additions & 2 deletions docs/reference/sysml-v1-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ returned over the service yet.
| `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity with no nodes | an empty action: `entry action x;` in a state, `do action x { }` on a transition, whose target follows on the next line | mapped (the note says the action is empty) |
| `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity whose every action node is refused | the action, holding the flow and a comment for each refused node; the behavior runs nothing | approximated (each node: **unmapped**) |
| `entry`, `doActivity`, `exit` behavior or transition `effect` that is an OpaqueBehavior in a language the mapping cannot write | the action, holding the body as a comment | approximated |
| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes, an accept of a time event, or a call of an activity, or of an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted) | approximated |
| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes that is written as a wait, an accept of a time event whose time is written, or a call it writes invoking an activity, or an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted; a constraint or time event left as a placeholder is no wait, and is not noted either, nor is one in a behavior named by a call written as a placeholder or never firing, which invokes nothing) | approximated |
| Transition `effect` referring to a behavior owned elsewhere | `do action : Def` on the transition, the target following on the next line; the behavior's own `action def` is written once where it is owned | mapped |
| `entry`, `doActivity`, `exit` behavior or transition `effect` referring to a behavior that is not written, or is written as something no state runs (a StateMachine, for one) | comment in the state's body or before the transition (a `/* */` comment is admitted only where a member may appear, not between the transition's clauses); the state or transition is written without it | approximated (the state or transition: "its … is not run"; a behavior not written: **unmapped**) |
| Transition `effect` with `in` parameters | the accepted signal is named, `accept sig : Sig`, and each parameter typed by the signal (or a general of it), or the sole untyped one, is bound to it: `in p : Sig = sig;`; a parameter of another type takes no value | mapped (an unbound parameter: approximated) |
Expand All @@ -272,7 +272,7 @@ returned over the service yet.
| State `stateInvariant` | comment in the state's body quoting the constraint; the state is written with a body so the comment has a place | **unmapped** — no v2 form |
| Initial transition with a trigger or guard | the region's `entry; then s;`; each trigger and the guard are dropped and reported apart from the transition | approximated (the trigger, the guard: unmapped) |
| SignalEvent, ChangeEvent, relative TimeEvent | written where a trigger refers to them, as `accept Sig`, `accept when <cond>`, `accept after <d> [SI::s]` | mapped / approximated |
| SignalEvent whose signal no send or broadcast action of the document sends (neither it nor a signal specializing it, which the accept takes too), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated |
| SignalEvent whose signal no written send action of the document sends (neither it nor a signal specializing it, which the accept takes too; a send left as a placeholder performs nothing and is no sender), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal, or a signal specializing it, as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated |
| Absolute TimeEvent a trigger refers to | `accept at <instant>` on the transition or accept action, the instant an attribute of the `state def`/`action def` typed `Time::TimeInstantValue` when `when` is a number with a time unit or an expression that resolves, read on the simulation clock, which starts at 0 | approximated (the clock's origin is the run's, not the calendar's) |
| Event (of any kind) no trigger refers to | — | skipped, counted as a model element nothing refers to |
| SignalEvent whose signal is not written, TimeEvent whose `when` is not a number with a time unit | comment; the transition that refers to it drops the trigger | **unmapped** — the reason names the signal or the time |
Expand Down
66 changes: 47 additions & 19 deletions internal/translate/migrate/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,42 +840,69 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) {
if len(dcs) == 0 {
return "", false
}
dc := dcs[0]
for _, other := range dcs[1:] {
a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dc)+" is written as its wait")
a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dcs[0])+" is written as its wait")
}
w := a.m.waitOf(e)
switch {
case w.ok:
a.m.add(w.dc, Approximated, a.m.v2Name(a.def), w.note)
return w.expr, true
case w.noInterval:
a.m.unmapped(w.dc, w.why)
default:
a.unmappedWait(w.dc, e, w.why)
}
return "", false
}

// wait is what the duration constraint dc on an element is written as: the
// delay before the element in seconds with the note on it when ok, else why
// none is written — the constraint has no interval, or its bounds admit no one wait.
type wait struct {
dc *sysmlv1.Element
expr, note string
why string
ok bool
noInterval bool
}

// waitOf decides the wait the first duration constraint on e is written as; a
// zero wait when e has none. The decision is shared by the writer and by the
// search for the clock waits of a behavior performed at an instant.
func (m *migration) waitOf(e *sysmlv1.Element) wait {
dcs := m.bounded[e]
if len(dcs) == 0 {
return wait{}
}
dc := dcs[0]
spec := firstOwned(dc, "specification")
if spec == nil || spec.Type != "DurationInterval" && spec.Type != "Interval" {
a.m.unmapped(dc, "the duration constraint has no interval")
return "", false
return wait{dc: dc, why: "the duration constraint has no interval", noInterval: true}
}
lo, lok, lnote := a.m.durationExpr(a.m.model.Ref(spec, "min"), e)
hi, hok, hnote := a.m.durationExpr(a.m.model.Ref(spec, "max"), e)
if bound, bnote, ok := a.m.singleValue(spec, lo, lok, hok); ok {
a.m.add(dc, Approximated, a.m.v2Name(a.def), joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e)))
return inSeconds(bound), true
lo, lok, lnote := m.durationExpr(m.model.Ref(spec, "min"), e)
hi, hok, hnote := m.durationExpr(m.model.Ref(spec, "max"), e)
if bound, bnote, ok := m.singleValue(spec, lo, lok, hok); ok {
return wait{dc: dc, expr: inSeconds(bound), note: joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e)), ok: true}
}
if !lok || !hok {
a.unmappedWait(dc, e, a.m.openInterval(spec, lo, lok, lnote, hi, hok, hnote))
return "", false
return wait{dc: dc, why: m.openInterval(spec, lo, lok, lnote, hi, hok, hnote)}
}
note := joinNotes(lnote, hnote)
var expr string
lf, lerr := strconv.ParseFloat(lo, 64)
hf, herr := strconv.ParseFloat(hi, 64)
switch {
case lerr == nil && herr == nil && lf > hf:
a.unmappedWait(dc, e, "the interval's min "+lo+" exceeds its max "+hi)
return "", false
return wait{dc: dc, why: "the interval's min " + lo + " exceeds its max " + hi}
case lo == hi:
expr = lo
note = joinNotes(note, "written as a fixed wait of "+lo+" s before "+describe(e))
default:
expr = "RandomFunctions::uniform(" + lo + ", " + hi + ")"
note = joinNotes(note, "written as a wait drawn uniformly over ["+lo+", "+hi+"] s before "+describe(e)+"; a tool's fixed min or max mode is a run setting, not the model's")
}
a.m.add(dc, Approximated, a.m.v2Name(a.def), note)
return inSeconds(expr), true
return wait{dc: dc, expr: inSeconds(expr), note: note, ok: true}
}

func (a *activity) unmappedWait(dc, e *sysmlv1.Element, note string) {
Expand Down Expand Up @@ -2338,7 +2365,7 @@ func (a *activity) readFeature(n *sysmlv1.Element, name string) {
}
a.names[r] = pname
mult, mnote := a.m.multiplicity(r)
a.m.w.line("out " + writeName(pname) + shaped(mult, r, true) + " = " + expr + ";")
a.m.w.line("out " + writeName(pname) + shaped(mult, r, true, false) + " = " + expr + ";")
a.m.add(r, verdictFor(mnote), a.m.v2Name(n)+"."+pname, mnote)
a.m.add(n, Mapped, name, "")
})
Expand Down Expand Up @@ -2405,11 +2432,11 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) {
path, hasPath := a.portPath(port)
switch {
case !a.m.written(port):
note = "the port " + qualifiedName(port) + " has no v2 declaration; the signal is sent to the sender"
note = joinNotes(note, "the port "+qualifiedName(port)+" has no v2 declaration; the signal is sent to the sender")
case hasPath:
line += " via " + a.m.respellThis(a.self()+"."+path, a.act)
default:
note = "the port " + qualifiedName(port) + " is no port of the object the sender acts on; the signal is sent to the sender"
note = joinNotes(note, "the port "+qualifiedName(port)+" is no port of the object the sender acts on; the signal is sent to the sender")
}
} else if t := firstOwned(n, "target"); t != nil {
obj, _, ok := a.objectOf(t)
Expand All @@ -2424,6 +2451,7 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) {
}
}
a.m.w.line(line + ";")
a.m.senders.sent[sig] = true
})
a.m.add(n, verdictFor(note), name, note)
}
Expand Down Expand Up @@ -2486,7 +2514,7 @@ func (a *activity) trigger(t, n *sysmlv1.Element) (clause, note string, ok bool)
tnote := ""
if sig := a.m.model.Ref(ev, "signal"); ev.Type == "SignalEvent" && sig != nil {
clause, tnote = a.m.actionRoute(clause, t, a.selfType(), a.act, a.viaPrefix(), sig)
tnote = joinNotes(tnote, a.m.unsentNote(sig))
a.m.uiAccept(sig, t, n)
if len(n.Owned("result")) > 0 {
a.payload[n.Owned("result")[0]] = sig
}
Expand Down
3 changes: 2 additions & 1 deletion internal/translate/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result {
opUsage: map[*sysmlv1.Element]string{},
deciding: map[*sysmlv1.Element]bool{},
bounded: map[*sysmlv1.Element][]*sysmlv1.Element{},
senders: senders{actions: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}},
senders: senders{sent: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}},
allocated: map[*sysmlv1.Element][]*sysmlv1.Element{},
triggered: map[*sysmlv1.Element]bool{},
snapshots: map[*sysmlv1.Element]snapshotTyping{},
Expand Down Expand Up @@ -259,6 +259,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result {
m.views(nil)
m.flushFlows()
m.placeholderEnds()
m.uiOnlyAccepts()
m.unwrittenEvents()
m.w.fill()
m.diagrams()
Expand Down
Loading
Loading