statifier 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +601 -16
- data/lib/statifier/accepts.rb +167 -0
- data/lib/statifier/basichttp/decode.rb +142 -0
- data/lib/statifier/basichttp/encoding.rb +224 -0
- data/lib/statifier/basichttp/net_http_transport.rb +157 -0
- data/lib/statifier/basichttp/outbound.rb +87 -0
- data/lib/statifier/basichttp/processor.rb +170 -0
- data/lib/statifier/basichttp/request.rb +94 -0
- data/lib/statifier/basichttp/response.rb +34 -0
- data/lib/statifier/basichttp.rb +47 -0
- data/lib/statifier/compiler/content.rb +131 -0
- data/lib/statifier/compiler/error.rb +30 -0
- data/lib/statifier/compiler/expressions.rb +64 -0
- data/lib/statifier/compiler/numbering.rb +145 -0
- data/lib/statifier/compiler/parts.rb +144 -0
- data/lib/statifier/compiler/walk.rb +61 -0
- data/lib/statifier/compiler.rb +82 -0
- data/lib/statifier/datamodel/data_binding.rb +114 -0
- data/lib/statifier/datamodel/delay.rb +54 -0
- data/lib/statifier/datamodel/location.rb +145 -0
- data/lib/statifier/datamodel/program.rb +82 -0
- data/lib/statifier/datamodel/reason_value.rb +42 -0
- data/lib/statifier/datamodel/reasons.rb +88 -0
- data/lib/statifier/datamodel/stores.rb +169 -0
- data/lib/statifier/datamodel/system_variables.rb +69 -0
- data/lib/statifier/datamodel.rb +120 -0
- data/lib/statifier/document/data.rb +36 -0
- data/lib/statifier/document/executable.rb +69 -0
- data/lib/statifier/document/invoke.rb +17 -0
- data/lib/statifier/document/states.rb +40 -0
- data/lib/statifier/document.rb +51 -0
- data/lib/statifier/driver/calls.rb +183 -0
- data/lib/statifier/driver/checks.rb +110 -0
- data/lib/statifier/driver/delivery.rb +45 -0
- data/lib/statifier/driver/invocations.rb +129 -0
- data/lib/statifier/driver/mailbox.rb +120 -0
- data/lib/statifier/driver/routing.rb +193 -0
- data/lib/statifier/driver/session_id.rb +40 -0
- data/lib/statifier/driver/work.rb +196 -0
- data/lib/statifier/driver.rb +205 -0
- data/lib/statifier/effect.rb +208 -0
- data/lib/statifier/event.rb +67 -0
- data/lib/statifier/frozen.rb +22 -0
- data/lib/statifier/interpreter/blocks.rb +142 -0
- data/lib/statifier/interpreter/content.rb +228 -0
- data/lib/statifier/interpreter/entering.rb +141 -0
- data/lib/statifier/interpreter/exit_entry.rb +174 -0
- data/lib/statifier/interpreter/foreach.rb +83 -0
- data/lib/statifier/interpreter/invoke/arguments.rb +52 -0
- data/lib/statifier/interpreter/invoke/passes.rb +143 -0
- data/lib/statifier/interpreter/invoke.rb +145 -0
- data/lib/statifier/interpreter/loop.rb +170 -0
- data/lib/statifier/interpreter/machine_state.rb +152 -0
- data/lib/statifier/interpreter/macrostep.rb +185 -0
- data/lib/statifier/interpreter/name_match.rb +43 -0
- data/lib/statifier/interpreter/selection.rb +221 -0
- data/lib/statifier/interpreter/send.rb +223 -0
- data/lib/statifier/interpreter/step.rb +138 -0
- data/lib/statifier/interpreter/stop.rb +66 -0
- data/lib/statifier/interpreter/targets.rb +107 -0
- data/lib/statifier/interpreter.rb +57 -0
- data/lib/statifier/lowering/branches.rb +84 -0
- data/lib/statifier/lowering/data.rb +102 -0
- data/lib/statifier/lowering/error.rb +102 -0
- data/lib/statifier/lowering/executable.rb +106 -0
- data/lib/statifier/lowering/placement.rb +65 -0
- data/lib/statifier/lowering/reading.rb +53 -0
- data/lib/statifier/lowering/states.rb +111 -0
- data/lib/statifier/lowering.rb +129 -0
- data/lib/statifier/machine/content.rb +81 -0
- data/lib/statifier/machine/data.rb +42 -0
- data/lib/statifier/machine/states.rb +56 -0
- data/lib/statifier/machine/values.rb +37 -0
- data/lib/statifier/machine.rb +103 -0
- data/lib/statifier/position/rebuild.rb +84 -0
- data/lib/statifier/position/shape.rb +73 -0
- data/lib/statifier/position/text.rb +43 -0
- data/lib/statifier/position.rb +206 -0
- data/lib/statifier/validator/attributes.rb +35 -0
- data/lib/statifier/validator/context.rb +74 -0
- data/lib/statifier/validator/data.rb +136 -0
- data/lib/statifier/validator/error.rb +90 -0
- data/lib/statifier/validator/executable.rb +139 -0
- data/lib/statifier/validator/history.rb +81 -0
- data/lib/statifier/validator/messages.rb +142 -0
- data/lib/statifier/validator/states.rb +161 -0
- data/lib/statifier/validator.rb +94 -0
- data/lib/statifier/version.rb +4 -1
- data/lib/statifier/xml/character_data.rb +142 -0
- data/lib/statifier/xml/cursor.rb +131 -0
- data/lib/statifier/xml/markup.rb +153 -0
- data/lib/statifier/xml/nodes.rb +50 -0
- data/lib/statifier/xml/parser.rb +121 -0
- data/lib/statifier/xml/scope.rb +46 -0
- data/lib/statifier/xml/tags.rb +112 -0
- data/lib/statifier/xml.rb +85 -0
- data/lib/statifier.rb +386 -2
- metadata +129 -20
- data/.gitignore +0 -8
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -11
- data/Gemfile.lock +0 -33
- data/Rakefile +0 -10
- data/bin/console +0 -8
- data/bin/setup +0 -6
- data/statifier.gemspec +0 -34
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Statifier
|
|
4
|
+
# The effects executable content answers, ported field for field from the
|
|
5
|
+
# reference's effect vocabulary (`Statifier.Effect.*` in statifier-ex):
|
|
6
|
+
# plain data values a host reads, each a `Data` the block runner answers
|
|
7
|
+
# as a deeply frozen copy, the values it carries included, except the
|
|
8
|
+
# host's own `caller_context`, which it carries untouched. The reference tags
|
|
9
|
+
# every effect with an atom beside its struct; here the class is the tag.
|
|
10
|
+
#
|
|
11
|
+
# Every effect carries the counters as they stood when it was made
|
|
12
|
+
# (`macrostep`, `microstep`, `round`), and every effect content makes
|
|
13
|
+
# carries the content node's `c_index` and its `owner`, the block it ran
|
|
14
|
+
# in: `{kind: :onentry, state_index:, ordinal:}`,
|
|
15
|
+
# `{kind: :onexit, state_index:, ordinal:}`, `{kind: :transition,
|
|
16
|
+
# t_index:}` or `{kind: :finalize, state_index:, invoke_index:}`.
|
|
17
|
+
#
|
|
18
|
+
# A value is a predicator value: `nil` is null and `Predicator::UNDEFINED`
|
|
19
|
+
# is no value at all.
|
|
20
|
+
#
|
|
21
|
+
# These are the effects the driver's calls answer a host
|
|
22
|
+
# ({Driver::Moved}'s `effects`), and what a host's processor is handed.
|
|
23
|
+
# Beside content's effects are the ones the interpreter's loop answers:
|
|
24
|
+
# {DatamodelInit}, {BudgetExhausted}, {Done}, the invocation effects
|
|
25
|
+
# ({Invoke}, {CancelInvoke} and {Autoforward}) and the {Trace} effects.
|
|
26
|
+
# The driver adds one of its own, {Child}, which carries an effect an
|
|
27
|
+
# invoked child's run answered.
|
|
28
|
+
#
|
|
29
|
+
# Every effect a call's runs answered is among the call's effects, in the
|
|
30
|
+
# order made, whatever the driver did with it:
|
|
31
|
+
#
|
|
32
|
+
# - A {Send} or a {SendDelayed}, by its type. A type the host registered
|
|
33
|
+
# is handed to that type's processor (`deliver`), delayed or not. The
|
|
34
|
+
# SCXML type, or none, is routed by its target, and a {SendDelayed} of it
|
|
35
|
+
# waits as a pending timer until {Statifier.advance} reaches it. A send
|
|
36
|
+
# of any other type is refused inside the chart with `error.execution`
|
|
37
|
+
# and answers no effect.
|
|
38
|
+
# - A {Cancel} drops every pending timer under its send id and tells a
|
|
39
|
+
# processor holding a delayed send under it, once.
|
|
40
|
+
# - An {Invoke} of the SCXML type whose content is markup starts a child
|
|
41
|
+
# session in process (no content, or content that is not markup or does
|
|
42
|
+
# not compile, raises `error.communication`); any other type raises
|
|
43
|
+
# `error.execution` inside the chart. A {CancelInvoke} stops the child,
|
|
44
|
+
# and an {Autoforward} hands it the event.
|
|
45
|
+
# - A {Log} is the host's to record; the driver does nothing with it.
|
|
46
|
+
# - A {Done} says the chart stopped; its pending timers and queued external
|
|
47
|
+
# events are discarded.
|
|
48
|
+
# - An error is never an effect: `error.execution` and
|
|
49
|
+
# `error.communication` are events the chart takes from its internal
|
|
50
|
+
# queue, seen by a host only in what the chart does next, or, for a
|
|
51
|
+
# chart started with `trace: true`, as a {Trace::EventDequeued}. The
|
|
52
|
+
# one failure answered as an effect is a {BudgetExhausted}: a macrostep
|
|
53
|
+
# spent its round budget, and the driver then queues external events
|
|
54
|
+
# without taking them.
|
|
55
|
+
# - A {Child} carries an effect an invoked child answered, tagged with the
|
|
56
|
+
# child's session id; a child's own child's effect is tagged with its
|
|
57
|
+
# own.
|
|
58
|
+
module Effect
|
|
59
|
+
# A `<log>`: `label` as written, nil when it was not, and `value`, the
|
|
60
|
+
# evaluated `expr`, nil for a `<log>` without one.
|
|
61
|
+
Log = ::Data.define(:label, :value, :c_index, :owner, :macrostep, :microstep, :round)
|
|
62
|
+
|
|
63
|
+
# A `<send>` with no delay, for the host to deliver now. `event`,
|
|
64
|
+
# `target` and `type` are the resolved values, nil when the element
|
|
65
|
+
# wrote neither attribute of the pair; `data` is the payload;
|
|
66
|
+
# `send_id` is always set, generated when the author named none, and
|
|
67
|
+
# `id_from_author?` is true when the element wrote `id` or
|
|
68
|
+
# `idlocation`. `ordinal` is set only for a type the host registered: the
|
|
69
|
+
# timer counter as this send advanced it.
|
|
70
|
+
Send = ::Data.define(
|
|
71
|
+
:event, :target, :type, :data, :send_id, :c_index, :owner, :macrostep, :microstep, :round, :ordinal,
|
|
72
|
+
:id_from_author?
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# A `<send>` with a delay, for the host to schedule `delay_ms`
|
|
76
|
+
# milliseconds from now: every field of {Send}, the delay, an `ordinal`
|
|
77
|
+
# that is always set, and `caller_context`, the opaque value the host
|
|
78
|
+
# attached to the step, the same object it attached (never copied or
|
|
79
|
+
# frozen), nil when it attached none.
|
|
80
|
+
SendDelayed = ::Data.define(
|
|
81
|
+
:event, :target, :type, :data, :send_id, :delay_ms, :c_index, :owner, :macrostep, :microstep, :round,
|
|
82
|
+
:ordinal, :id_from_author?, :caller_context
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# A `<cancel>`: the host drops the pending delayed send `send_id` names,
|
|
86
|
+
# if any. `ordinal` and `caller_context` as {SendDelayed}'s.
|
|
87
|
+
Cancel = ::Data.define(:send_id, :c_index, :owner, :macrostep, :microstep, :round, :ordinal, :caller_context)
|
|
88
|
+
|
|
89
|
+
# One write to the datamodel: `location_path` the path written (a String
|
|
90
|
+
# key or an Integer index per segment, the root first),
|
|
91
|
+
# `location_source` the location as written, `new_value` and
|
|
92
|
+
# `prior_value` the values at the path after and before. A write by
|
|
93
|
+
# content carries its `c_index` and `owner` and a nil `d_index`; a
|
|
94
|
+
# `<data>` that binds carries its `d_index`, its id as the one-segment
|
|
95
|
+
# path and the source, and a nil `c_index` and `owner`. The two writes
|
|
96
|
+
# no content node makes carry a nil `c_index` and `d_index`: an
|
|
97
|
+
# `<invoke>`'s `idlocation` write, owned by `{kind: :invoke,
|
|
98
|
+
# state_index:, invoke_index:}`, and each write an empty `<finalize>`
|
|
99
|
+
# makes, owned by `{kind: :finalize, state_index:, invoke_index:}`.
|
|
100
|
+
DatamodelChange = ::Data.define(
|
|
101
|
+
:location_path, :location_source, :new_value, :prior_value, :d_index, :c_index, :owner, :macrostep,
|
|
102
|
+
:microstep, :round
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# An `<invoke>` started, for the driver to start its service. `type`
|
|
106
|
+
# and `src` are the resolved values, nil when the element wrote neither
|
|
107
|
+
# attribute of the pair; `params` is the `namelist` and `<param>` values
|
|
108
|
+
# as data (`Predicator::UNDEFINED` for none); `content` the `<content>`
|
|
109
|
+
# child's data, nil when it has none; `autoforward` the attribute;
|
|
110
|
+
# `invoke_id` always set, generated when the author named none;
|
|
111
|
+
# `state_index` the invoking state and `invoke_index` the element's
|
|
112
|
+
# position among that state's `<invoke>`s; and `caller_context` as
|
|
113
|
+
# {SendDelayed}'s.
|
|
114
|
+
Invoke = ::Data.define(
|
|
115
|
+
:invoke_id, :type, :src, :params, :content, :autoforward, :state_index, :invoke_index, :macrostep,
|
|
116
|
+
:microstep, :round, :caller_context
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# A live invocation is cancelled: its invoking state, `state_index`,
|
|
120
|
+
# exits (or the chart stops) before it answered. `caller_context` as
|
|
121
|
+
# {SendDelayed}'s.
|
|
122
|
+
CancelInvoke = ::Data.define(:invoke_id, :state_index, :macrostep, :microstep, :round, :caller_context)
|
|
123
|
+
|
|
124
|
+
# An external event forwarded, unchanged, to the live invocation
|
|
125
|
+
# `invoke_id` names, whose `<invoke>` sets `autoforward`; `state_index`
|
|
126
|
+
# is its invoking state and `event` the {Event} whole.
|
|
127
|
+
Autoforward = ::Data.define(:invoke_id, :state_index, :event, :macrostep, :microstep, :round)
|
|
128
|
+
|
|
129
|
+
# An effect an invoked child's run answered, as the driver reports it
|
|
130
|
+
# among a call's effects: `session_id`, the child's session id (its
|
|
131
|
+
# parent's, a dot and the invoke id), and `effect`, the effect itself. A
|
|
132
|
+
# child's own child's effect is reported the same way, under its own
|
|
133
|
+
# session id.
|
|
134
|
+
Child = ::Data.define(:session_id, :effect)
|
|
135
|
+
|
|
136
|
+
# The datamodel as the chart starts, before any `<data>` binds: the
|
|
137
|
+
# host's values, the system variables over them and every `<data>` id
|
|
138
|
+
# the host did not supply, declared undefined. Answered first when a
|
|
139
|
+
# chart starts.
|
|
140
|
+
DatamodelInit = ::Data.define(:datamodel, :macrostep, :microstep, :round)
|
|
141
|
+
|
|
142
|
+
# A macrostep spent its round budget without reaching a stable
|
|
143
|
+
# configuration: `configuration`, the state indexes active where the
|
|
144
|
+
# last round left them, in document order; `budget`, the budget spent (a
|
|
145
|
+
# count, or `:infinity`); and `pending_internal_events`, the {Event}s
|
|
146
|
+
# still queued, oldest first. The chart keeps running.
|
|
147
|
+
BudgetExhausted = ::Data.define(:configuration, :budget, :pending_internal_events, :macrostep, :microstep,
|
|
148
|
+
:round)
|
|
149
|
+
|
|
150
|
+
# The chart stopped. `donedata` is the top-level final's `<donedata>`
|
|
151
|
+
# (`Predicator::UNDEFINED` when it has none, or it failed), nil when no
|
|
152
|
+
# top-level final was active; `donedata_error`, the data of the first
|
|
153
|
+
# `error.execution` that folding it raised, else nil; and
|
|
154
|
+
# `configuration`, the state indexes active when it stopped, in document
|
|
155
|
+
# order. Answered last.
|
|
156
|
+
Done = ::Data.define(:donedata, :donedata_error, :configuration, :macrostep, :microstep, :round)
|
|
157
|
+
|
|
158
|
+
# The trace effects, ported field for field from the reference's
|
|
159
|
+
# (`Statifier.Effect.Trace.*`): diagnostics the loop answers only for a
|
|
160
|
+
# state started with `trace: true`, each stamped with the counters as
|
|
161
|
+
# they stood at the phase it reports. Configurations and index lists
|
|
162
|
+
# are frozen Arrays in the order the reference gives.
|
|
163
|
+
module Trace
|
|
164
|
+
# One selection round's written conditions, in walk order: each a
|
|
165
|
+
# `{t_index:, outcome:, reason:}` Hash, `outcome` `:enabled`,
|
|
166
|
+
# `:disabled` or `:error` (with its `reason`). Answered only when the
|
|
167
|
+
# round evaluated one.
|
|
168
|
+
CondsEvaluated = ::Data.define(:evaluations, :macrostep, :microstep, :round)
|
|
169
|
+
|
|
170
|
+
# A block of content ran: its `owner` (a content owner, or
|
|
171
|
+
# `{kind: :global_script, index:}` for a `<script>` child of
|
|
172
|
+
# `<scxml>`) and `c_indexes`, the nodes that ran, the failing one
|
|
173
|
+
# included.
|
|
174
|
+
ContentExecuted = ::Data.define(:owner, :c_indexes, :macrostep, :microstep, :round)
|
|
175
|
+
|
|
176
|
+
# The chart stopped: {Effect::Done}'s fields, just before it.
|
|
177
|
+
Done = ::Data.define(:donedata, :donedata_error, :configuration, :macrostep, :microstep, :round)
|
|
178
|
+
|
|
179
|
+
# The states a microstep enters, in entry order (`indexes`), and the
|
|
180
|
+
# `configuration` once they have entered.
|
|
181
|
+
EntrySet = ::Data.define(:indexes, :configuration, :macrostep, :microstep, :round)
|
|
182
|
+
|
|
183
|
+
# An {Event} was taken, `from` `:external` or `:internal`.
|
|
184
|
+
EventDequeued = ::Data.define(:event, :from, :macrostep, :microstep, :round)
|
|
185
|
+
|
|
186
|
+
# The states a microstep exits (or the chart's stop leaves), in exit
|
|
187
|
+
# order (`indexes`), and the `configuration` once they have left.
|
|
188
|
+
ExitSet = ::Data.define(:indexes, :configuration, :macrostep, :microstep, :round)
|
|
189
|
+
|
|
190
|
+
# The pass over live invocations an external event makes before
|
|
191
|
+
# selection: the `event`, the invoke ids `finalized` and those
|
|
192
|
+
# `forwarded` to.
|
|
193
|
+
FinalizeAutoforward = ::Data.define(:event, :finalized, :forwarded, :macrostep, :microstep, :round)
|
|
194
|
+
|
|
195
|
+
# The invoke pass at the end of a stable macrostep: the states it
|
|
196
|
+
# walked (`state_indexes`, entry order) and the `invoke_ids` it
|
|
197
|
+
# started.
|
|
198
|
+
InvokePass = ::Data.define(:state_indexes, :invoke_ids, :macrostep, :microstep, :round)
|
|
199
|
+
|
|
200
|
+
# A macrostep reached a stable configuration with the chart running.
|
|
201
|
+
MacrostepStable = ::Data.define(:configuration, :macrostep, :microstep, :round)
|
|
202
|
+
|
|
203
|
+
# A selection: the `t_indexes` it took (empty included) and the
|
|
204
|
+
# `event` it selected on, nil for an eventless round.
|
|
205
|
+
TransitionsSelected = ::Data.define(:t_indexes, :event, :macrostep, :microstep, :round)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Statifier
|
|
4
|
+
# An event, as the reference carries one (`Statifier.Event` in
|
|
5
|
+
# statifier-ex): the one value every queue holds, whoever raised it.
|
|
6
|
+
#
|
|
7
|
+
# `name`; `type`, `:external`, `:internal` or `:platform` (provenance,
|
|
8
|
+
# not routing: an internal and a platform event ride the same internal
|
|
9
|
+
# queue); `data`, `Predicator::UNDEFINED` for an event with none (nil is
|
|
10
|
+
# predicator's null); `cause`, the {Cause} of an event the chart raised,
|
|
11
|
+
# nil for one the host sent; `invokeid`, `origin`, `origintype` and
|
|
12
|
+
# `sendid`, the specification's `_event` fields, nil when the event
|
|
13
|
+
# carries none; and `caller_context`, the opaque value a host attached to
|
|
14
|
+
# an external event, carried as the same object and never read.
|
|
15
|
+
#
|
|
16
|
+
# A host meets one in the effects the driver answers and as the event a
|
|
17
|
+
# processor is handed beside a send.
|
|
18
|
+
Event = ::Data.define(:name, :type, :data, :cause, :invokeid, :origin, :origintype, :sendid, :caller_context)
|
|
19
|
+
|
|
20
|
+
# The event's {Cause} and its constructors.
|
|
21
|
+
class Event
|
|
22
|
+
# Why the chart raised an event: `origin`, what raised it, and the
|
|
23
|
+
# counters as they stood when it was raised. An origin is one of
|
|
24
|
+
# `{kind: :content, c_index:, owner:}` (a content node, `owner` its
|
|
25
|
+
# block), `{kind: :state, state_index:}` (a completion, or a failed
|
|
26
|
+
# `<donedata>` `<content>`), `{kind: :transition, t_index:}` (a failed
|
|
27
|
+
# condition), `{kind: :data, d_index:}` (a failed `<data>`),
|
|
28
|
+
# `{kind: :donedata_param, state_index:, param_index:}`,
|
|
29
|
+
# `{kind: :global_script, index:}`, `{kind: :invoke, state_index:,
|
|
30
|
+
# invoke_index:}` (an `<invoke>` that failed to start) and
|
|
31
|
+
# `{kind: :finalize, state_index:, invoke_index:}` (a write of an empty
|
|
32
|
+
# `<finalize>` that failed).
|
|
33
|
+
Cause = ::Data.define(:origin, :macrostep, :microstep, :round)
|
|
34
|
+
|
|
35
|
+
# The fields an event the chart raised leaves blank.
|
|
36
|
+
BLANK = { invokeid: nil, origin: nil, origintype: nil, caller_context: nil }.freeze
|
|
37
|
+
|
|
38
|
+
# The optional fields of an event the host sends, as they stand when it
|
|
39
|
+
# names none of them.
|
|
40
|
+
EXTERNAL = { data: Predicator::UNDEFINED, sendid: nil, **BLANK }.freeze
|
|
41
|
+
|
|
42
|
+
# An event the host sent: no cause, and each of {EXTERNAL}'s fields as
|
|
43
|
+
# `fields` gives it.
|
|
44
|
+
#
|
|
45
|
+
# @return [Event]
|
|
46
|
+
# @raise [ArgumentError] for a field an event does not have
|
|
47
|
+
def self.external(name, **fields) = new(name:, type: :external, cause: nil, **EXTERNAL, **fields)
|
|
48
|
+
|
|
49
|
+
# An event raised by a `<raise>`, or by a send to the internal queue:
|
|
50
|
+
# `origin` and `origintype` are blank, as the specification requires of
|
|
51
|
+
# an internal event.
|
|
52
|
+
#
|
|
53
|
+
# @return [Event]
|
|
54
|
+
def self.internal(name, cause, data: Predicator::UNDEFINED, sendid: nil)
|
|
55
|
+
new(name:, type: :internal, data:, cause:, sendid:, **BLANK)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# An event the platform raised: an `error.*` or a `done.state.*`.
|
|
59
|
+
#
|
|
60
|
+
# @return [Event]
|
|
61
|
+
def self.platform(name, cause, data: Predicator::UNDEFINED, sendid: nil)
|
|
62
|
+
new(name:, type: :platform, data:, cause:, sendid:, **BLANK)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private_constant :BLANK, :EXTERNAL
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Statifier
|
|
4
|
+
# How the package freezes the values it builds and answers.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module Frozen
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# `value`, an effect or an event, as a deeply frozen copy: whatever it
|
|
11
|
+
# carries that is not already shareable is copied before it is frozen,
|
|
12
|
+
# so no object the caller passed in (a datamodel value, the stamp's
|
|
13
|
+
# owner) is frozen in place. The one field that is not copied or frozen
|
|
14
|
+
# is `caller_context`, the host's own opaque value, which the effect
|
|
15
|
+
# carries as the same object, whatever it is.
|
|
16
|
+
def copy(value)
|
|
17
|
+
return Ractor.make_shareable(value, copy: true) unless value.respond_to?(:caller_context)
|
|
18
|
+
|
|
19
|
+
Ractor.make_shareable(value.with(caller_context: nil), copy: true).with(caller_context: value.caller_context)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Statifier
|
|
4
|
+
module Interpreter
|
|
5
|
+
# What a loop function answered: the {MachineState} it leaves and its
|
|
6
|
+
# effects, in the order they were made.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
Stepped = ::Data.define(:state, :effects) do
|
|
10
|
+
# This step followed by `later`: `later`'s state, and both effect
|
|
11
|
+
# lists in order.
|
|
12
|
+
def then_step(later) = Stepped.new(state: later.state, effects: effects + later.effects)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# What a step runs against a {MachineState}, as the reference runs it
|
|
16
|
+
# (`Statifier.Interpreter.Content.execute_block/3`, the binding of
|
|
17
|
+
# `Statifier.Interpreter.Datamodel` and `ExitEntry.donedata/2` in
|
|
18
|
+
# statifier-ex): a block of content, a `<data>` binding pass and a
|
|
19
|
+
# final state's `<donedata>`, each read against the state as it stands
|
|
20
|
+
# and each answering the state it leaves.
|
|
21
|
+
#
|
|
22
|
+
# A block runs through {Content.execute_block}: the datamodel it leaves,
|
|
23
|
+
# its send state and the events it raised are kept, and with tracing on
|
|
24
|
+
# a {Effect::Trace::ContentExecuted} follows its effects.
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
module Blocks
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# One block run, owned by `owner`.
|
|
31
|
+
#
|
|
32
|
+
# @return [Stepped]
|
|
33
|
+
def run_block(state, owner, content)
|
|
34
|
+
stamp = Content::Stamp.new(owner:, **state.counters, caller_context: state.caller_context)
|
|
35
|
+
outcome = Content.execute_block(state.context, content, stamp, state.sends)
|
|
36
|
+
after = kept(state, outcome)
|
|
37
|
+
trace = traced(after, Effect::Trace::ContentExecuted, owner:, c_indexes: outcome.executed)
|
|
38
|
+
Stepped.new(state: after, effects: outcome.effects + trace)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The state with what a block left kept: its datamodel, deeply frozen,
|
|
42
|
+
# its send state and the events it raised, queued.
|
|
43
|
+
def kept(state, outcome)
|
|
44
|
+
state.with(datamodel: Ractor.make_shareable(outcome.context.data), sends: outcome.sends)
|
|
45
|
+
.enqueue(*outcome.raised)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Each of a state's `<onentry>` or `<onexit>` blocks (`kind`), in
|
|
49
|
+
# document order, owned by its ordinal.
|
|
50
|
+
#
|
|
51
|
+
# @return [Stepped]
|
|
52
|
+
def run_blocks(state, kind, index)
|
|
53
|
+
state.machine.state(index).public_send(kind).each_with_index
|
|
54
|
+
.reduce(Stepped.new(state:, effects: [])) do |acc, (block, ordinal)|
|
|
55
|
+
acc.then_step(run_block(acc.state, { kind:, state_index: index, ordinal: }, block.content))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The content of the transition at `t_index`; nothing when it is nil.
|
|
60
|
+
#
|
|
61
|
+
# @return [Stepped]
|
|
62
|
+
def run_transition_content(state, t_index)
|
|
63
|
+
return Stepped.new(state:, effects: []) if t_index.nil?
|
|
64
|
+
|
|
65
|
+
run_block(state, { kind: :transition, t_index: }, state.machine.transitions.fetch(t_index).content)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# What a `<data>` binding pass answered ({Datamodel::DataBinding}),
|
|
69
|
+
# kept: each binding a {Effect::DatamodelChange} (its id the one-segment
|
|
70
|
+
# path), each failure an `error.execution` (origin `{kind: :data,
|
|
71
|
+
# d_index:}`).
|
|
72
|
+
#
|
|
73
|
+
# @return [Stepped]
|
|
74
|
+
def bound(state, bound)
|
|
75
|
+
after = bound.failures.reduce(state.with(datamodel: Ractor.make_shareable(bound.data))) do |acc, failure|
|
|
76
|
+
acc.raise_failure({ kind: :data, d_index: failure.d_index }, failure.reason)
|
|
77
|
+
end
|
|
78
|
+
Stepped.new(state: after, effects: bound.changes.map { binding_change(state, _1) })
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The effect one binding answers.
|
|
82
|
+
def binding_change(state, change)
|
|
83
|
+
Frozen.copy(Effect::DatamodelChange.new(
|
|
84
|
+
location_path: [change.id], location_source: change.id, new_value: change.new_value,
|
|
85
|
+
prior_value: change.prior_value, d_index: change.d_index, c_index: nil, owner: nil,
|
|
86
|
+
**state.counters
|
|
87
|
+
))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# `returnDoneEvent`'s data: the final state's `<donedata>` folded to
|
|
91
|
+
# the value its `done.*` event carries, as `[state, data]`. None, or
|
|
92
|
+
# an empty one, is no data; a `<content>` body is read as a `<send>`'s
|
|
93
|
+
# is ({Send.text_data}); a `<content>` `expr` is evaluated, and a
|
|
94
|
+
# failure raises `error.execution` (origin `{kind: :state,
|
|
95
|
+
# state_index:}`) and is no data; `<param>`s are evaluated in order
|
|
96
|
+
# against one context, each failure raising its own `error.execution`
|
|
97
|
+
# (origin `{kind: :donedata_param, state_index:, param_index:}`) and
|
|
98
|
+
# dropping that param.
|
|
99
|
+
def donedata(state, index)
|
|
100
|
+
donedata = state.machine.state(index).donedata
|
|
101
|
+
return [state, Predicator::UNDEFINED] if donedata.nil? || (donedata.expr.nil? && donedata.params.empty?)
|
|
102
|
+
return donedata_params(state, index, donedata.params) if donedata.expr.nil?
|
|
103
|
+
|
|
104
|
+
donedata_content(state, index, donedata.expr)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# A `<donedata>` `<content>`, as {donedata}.
|
|
108
|
+
def donedata_content(state, index, expr)
|
|
109
|
+
return [state, Send.text_data(expr.value)] if expr.is_a?(Machine::Static)
|
|
110
|
+
|
|
111
|
+
evaluated = Datamodel.evaluate(state.context, expr)
|
|
112
|
+
return [state, evaluated.value] if evaluated.ok?
|
|
113
|
+
|
|
114
|
+
[state.raise_failure({ kind: :state, state_index: index }, evaluated.error), Predicator::UNDEFINED]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The `<donedata>` `<param>`s, as {donedata}.
|
|
118
|
+
def donedata_params(state, index, params)
|
|
119
|
+
context = state.context
|
|
120
|
+
pairs = []
|
|
121
|
+
params.each_with_index do |param, param_index|
|
|
122
|
+
evaluated = Datamodel.evaluate(context, param.expr)
|
|
123
|
+
next pairs << [param.name, evaluated.value] if evaluated.ok?
|
|
124
|
+
|
|
125
|
+
state = state.raise_failure({ kind: :donedata_param, state_index: index, param_index: }, evaluated.error)
|
|
126
|
+
end
|
|
127
|
+
[state, Send.params_data(pairs)]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The trace effect `klass` with `fields`, stamped with `state`'s
|
|
131
|
+
# counters, as a one-element list; empty, and nothing built, when the
|
|
132
|
+
# state does not trace. Every field is deeply frozen except an {Event},
|
|
133
|
+
# already a frozen copy, whose host `caller_context` stays the host's
|
|
134
|
+
# object.
|
|
135
|
+
def traced(state, klass, **fields)
|
|
136
|
+
return [] unless state.trace
|
|
137
|
+
|
|
138
|
+
[klass.new(**fields.transform_values { _1.is_a?(Event) ? _1 : Ractor.make_shareable(_1) }, **state.counters)]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Statifier
|
|
4
|
+
module Interpreter
|
|
5
|
+
# The block runner and the content nodes it runs, as the reference runs
|
|
6
|
+
# them (`Statifier.Interpreter.Content` and `Statifier.Machine.Content.*`
|
|
7
|
+
# in statifier-ex): the specification's `executeContent`.
|
|
8
|
+
#
|
|
9
|
+
# A block runs its nodes in document order against one
|
|
10
|
+
# {Datamodel::Context}: every write a node makes (an `<assign>`, a
|
|
11
|
+
# `<foreach>`'s item and index, a `<script>`'s program, a `<send>`'s
|
|
12
|
+
# `idlocation`) is seen by every node after it. The first node that
|
|
13
|
+
# fails stops the block. The nodes that ran before it keep what they did;
|
|
14
|
+
# the failing node answers no effect, and its failure becomes one
|
|
15
|
+
# `error.*` platform event whose origin is that node, the top-level node
|
|
16
|
+
# of this block when the failure was inside an `<if>` or a `<foreach>`
|
|
17
|
+
# ({NestedContent} then names the inner node). Other blocks are not
|
|
18
|
+
# affected: each call is independent.
|
|
19
|
+
#
|
|
20
|
+
# What a failing node keeps is the reference's: a leaf that fails (a
|
|
21
|
+
# `<log>`, an `<assign>`, a `<cancel>`, a `<send>` whose argument
|
|
22
|
+
# failed, a `<script>` that never compiled) keeps nothing; an `<if>` or
|
|
23
|
+
# a `<foreach>` keeps what its nodes wrote and raised, though not their
|
|
24
|
+
# effects; a `<script>` keeps the writes its program made before it
|
|
25
|
+
# stopped; a refused `<send>` keeps its minted id and its `idlocation`
|
|
26
|
+
# write.
|
|
27
|
+
#
|
|
28
|
+
# A condition of an `<if>` that fails, or answers something other than
|
|
29
|
+
# true or false, does not stop the block: its branch counts as not
|
|
30
|
+
# taken, and its `error.execution` is raised once the top-level node that
|
|
31
|
+
# holds it finishes, after any event the taken branch raised, the order
|
|
32
|
+
# the reference takes.
|
|
33
|
+
#
|
|
34
|
+
# Nothing here moves a queue. The runner answers the events the block
|
|
35
|
+
# raised, in the order they join the internal queue, beside its effects,
|
|
36
|
+
# and the step that called it queues them and stamps their counters.
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
module Content
|
|
40
|
+
# Where a block runs: `owner`, the block (`{kind: :onentry,
|
|
41
|
+
# state_index:, ordinal:}`, `{kind: :onexit, state_index:, ordinal:}`,
|
|
42
|
+
# `{kind: :transition, t_index:}` or `{kind: :finalize, state_index:,
|
|
43
|
+
# invoke_index:}`); the counters as they stand while it runs, which no
|
|
44
|
+
# node moves; and `caller_context`, the opaque value the host attached
|
|
45
|
+
# to the step (any object, never copied or frozen here), nil for none.
|
|
46
|
+
Stamp = ::Data.define(:owner, :macrostep, :microstep, :round, :caller_context) do
|
|
47
|
+
# The fields every effect content makes carries: the owner and the
|
|
48
|
+
# counters.
|
|
49
|
+
def fields = { owner:, macrostep:, microstep:, round: }
|
|
50
|
+
|
|
51
|
+
# The origin of an event the content node at `c_index` raises.
|
|
52
|
+
def origin(c_index) = { kind: :content, c_index:, owner: }
|
|
53
|
+
|
|
54
|
+
# The {Event::Cause} of an event the content node at `c_index`
|
|
55
|
+
# raises: its origin and the counters.
|
|
56
|
+
def cause(c_index) = Event::Cause.new(origin: origin(c_index), macrostep:, microstep:, round:)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# What a block answered: the `context` it leaves; `sends`, the
|
|
60
|
+
# {Send::SendState} it leaves; `effects` in the order the nodes made
|
|
61
|
+
# them; `raised`, the {Event}s it raised in queue order (an internal
|
|
62
|
+
# event per `<raise>`, a platform `error.execution` per failure and an
|
|
63
|
+
# `error.execution` or `error.communication` per refused `<send>`,
|
|
64
|
+
# each with its {Event::Cause}); and `executed`, the `c_index` of every
|
|
65
|
+
# top-level node that ran, the failing one included.
|
|
66
|
+
Outcome = ::Data.define(:context, :sends, :effects, :raised, :executed)
|
|
67
|
+
|
|
68
|
+
# A node inside an `<if>` or a `<foreach>` failed: `c_index` names it
|
|
69
|
+
# and `reason` is its failure.
|
|
70
|
+
NestedContent = ::Data.define(:c_index, :reason)
|
|
71
|
+
|
|
72
|
+
# What a block threads from node to node.
|
|
73
|
+
Run = ::Data.define(:context, :sends, :effects, :raised, :pending) do
|
|
74
|
+
# The run with `effect` appended, as a deeply frozen copy
|
|
75
|
+
# ({Frozen.copy}).
|
|
76
|
+
def effect(effect) = with(effects: (effects + [Frozen.copy(effect)]).freeze)
|
|
77
|
+
|
|
78
|
+
# The run with `events` raised, each a deeply frozen copy.
|
|
79
|
+
def raise_events(*events) = with(raised: (raised + events.map { Frozen.copy(_1) }).freeze)
|
|
80
|
+
|
|
81
|
+
# The run with a condition's failure recorded.
|
|
82
|
+
def defer(reason) = with(pending: (pending + [reason]).freeze)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
module_function
|
|
86
|
+
|
|
87
|
+
# The function each kind of node runs through.
|
|
88
|
+
RUNNERS = {
|
|
89
|
+
Machine::Raise => :raise_event, Machine::Log => :log, Machine::Assign => :assign, Machine::If => :if_node,
|
|
90
|
+
Machine::Foreach => :foreach, Machine::Script => :script, Machine::Send => :send_node,
|
|
91
|
+
Machine::Cancel => :cancel
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
# Runs `content`, a block's nodes, in document order against
|
|
95
|
+
# `context`, stopping at the first that fails. `sends` is the send
|
|
96
|
+
# state the block starts with and `stamp` the {Stamp} its effects and
|
|
97
|
+
# events carry.
|
|
98
|
+
#
|
|
99
|
+
# @return [Outcome]
|
|
100
|
+
def execute_block(context, content, stamp, sends = Send::SendState::INITIAL)
|
|
101
|
+
run = Run.new(context:, sends:, effects: [].freeze, raised: [].freeze, pending: [].freeze)
|
|
102
|
+
executed = content.each_with_object([]) do |node, ran|
|
|
103
|
+
ran << node.c_index
|
|
104
|
+
run, failed = top_level(run, node, stamp)
|
|
105
|
+
break ran if failed
|
|
106
|
+
end
|
|
107
|
+
Outcome.new(**run.to_h.except(:pending), executed: executed.freeze)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# One top-level node run, its recorded condition failures raised
|
|
111
|
+
# against it, then its own failure: `[run, true]` when it failed.
|
|
112
|
+
def top_level(run, node, stamp)
|
|
113
|
+
cause = stamp.cause(node.c_index)
|
|
114
|
+
run, reason = execute_node(run, node, stamp)
|
|
115
|
+
run = drain(run, cause)
|
|
116
|
+
reason.nil? ? [run, false] : [run.raise_events(error_event(reason, cause)), true]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# One node run: `[run, nil]` on success, else `[run, reason]` with the
|
|
120
|
+
# run the block keeps, which never holds the failing node's effects.
|
|
121
|
+
def execute_node(run, node, stamp)
|
|
122
|
+
after, reason = __send__(RUNNERS.fetch(node.class), run, node, stamp)
|
|
123
|
+
reason.nil? ? [after, nil] : [after.with(effects: run.effects), reason]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The pending condition failures raised with `cause`, the top-level
|
|
127
|
+
# node's, in the order they were recorded.
|
|
128
|
+
def drain(run, cause)
|
|
129
|
+
return run if run.pending.empty?
|
|
130
|
+
|
|
131
|
+
run.raise_events(*run.pending.map { error_event(_1, cause) }).with(pending: [].freeze)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The event a failure raises: a refused `<send>` raises the event its
|
|
135
|
+
# kind names, carrying its id; anything else `error.execution`. The
|
|
136
|
+
# failure, as a predicator value ({Datamodel::ReasonValue.of}), is the
|
|
137
|
+
# event's data.
|
|
138
|
+
def error_event(reason, cause)
|
|
139
|
+
unless reason.is_a?(Send::SendRejected)
|
|
140
|
+
return Event.platform("error.execution", cause, data: Datamodel::ReasonValue.of(reason))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
Event.platform("error.#{reason.kind}", cause, data: Datamodel::ReasonValue.of(reason.reason),
|
|
144
|
+
sendid: reason.send_id)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# `<raise>`: one internal event named as written.
|
|
148
|
+
def raise_event(run, node, stamp)
|
|
149
|
+
[run.raise_events(Event.internal(node.event, stamp.cause(node.c_index))), nil]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# `<log>`: the label and the evaluated `expr`, nil without one, as an
|
|
153
|
+
# effect.
|
|
154
|
+
def log(run, node, stamp)
|
|
155
|
+
evaluated = node.expr.nil? ? Send::NONE : Datamodel.evaluate(run.context, node.expr)
|
|
156
|
+
return [run, evaluated.error] unless evaluated.ok?
|
|
157
|
+
|
|
158
|
+
[run.effect(Effect::Log.new(label: node.label, value: evaluated.value, c_index: node.c_index, **stamp.fields)),
|
|
159
|
+
nil]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# `<assign>`: the value evaluated, then written at the location
|
|
163
|
+
# ({Datamodel::Location.write}), which never declares a root. A write
|
|
164
|
+
# that lands answers its change.
|
|
165
|
+
def assign(run, node, stamp)
|
|
166
|
+
context = run.context
|
|
167
|
+
value = Datamodel.evaluate(context, node.value)
|
|
168
|
+
written = value.ok? ? Datamodel::Location.write(context, node.location, value.value) : value
|
|
169
|
+
return [run, written.error] unless written.ok?
|
|
170
|
+
|
|
171
|
+
[run.with(context: written.context).effect(change(written, node.location, value.value, node.c_index, stamp)),
|
|
172
|
+
nil]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The change a content node's write made: `written` the
|
|
176
|
+
# {Datamodel::Written}, `source` the location as written.
|
|
177
|
+
def change(written, source, new_value, c_index, stamp)
|
|
178
|
+
Effect::DatamodelChange.new(location_path: written.path, location_source: source, new_value:,
|
|
179
|
+
prior_value: written.prior_value, d_index: nil, c_index:, **stamp.fields)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# `<if>`: the first branch whose condition is true runs, an `<else>`
|
|
183
|
+
# always; a condition that fails or is not a boolean is recorded and
|
|
184
|
+
# its branch passed over.
|
|
185
|
+
def if_node(run, node, stamp)
|
|
186
|
+
node.branches.each do |branch|
|
|
187
|
+
evaluated = Datamodel.evaluate_cond(run.context, branch.cond)
|
|
188
|
+
return nested(run, branch.content, stamp) if evaluated.ok? && evaluated.value
|
|
189
|
+
|
|
190
|
+
run = run.defer(evaluated.error) unless evaluated.ok?
|
|
191
|
+
end
|
|
192
|
+
[run, nil]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# A partition or a loop body: each node in order, the first failure
|
|
196
|
+
# wrapped as a {NestedContent} naming the inner node.
|
|
197
|
+
def nested(run, content, stamp)
|
|
198
|
+
content.each do |node|
|
|
199
|
+
run, reason = execute_node(run, node, stamp)
|
|
200
|
+
return [run, NestedContent.new(c_index: node.c_index, reason:)] unless reason.nil?
|
|
201
|
+
end
|
|
202
|
+
[run, nil]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# `<foreach>`, through {Foreach.execute}.
|
|
206
|
+
def foreach(run, node, stamp) = Foreach.execute(run, node, stamp)
|
|
207
|
+
|
|
208
|
+
# `<script>`: the program run through {Datamodel.execute}; its writes
|
|
209
|
+
# before a failure are kept.
|
|
210
|
+
def script(run, node, _stamp)
|
|
211
|
+
executed = Datamodel.execute(run.context, node.program)
|
|
212
|
+
[run.with(context: run.context.with(data: executed.data)), executed.error]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# `<send>`, through {Send.execute_send}.
|
|
216
|
+
def send_node(run, node, stamp) = sent(run, Send.execute_send(run.context, node, run.sends, stamp))
|
|
217
|
+
|
|
218
|
+
# `<cancel>`, through {Send.execute_cancel}.
|
|
219
|
+
def cancel(run, node, stamp) = sent(run, Send.execute_cancel(run.context, node, run.sends, stamp))
|
|
220
|
+
|
|
221
|
+
# What a `<send>` or `<cancel>` answered, kept.
|
|
222
|
+
def sent(run, sent)
|
|
223
|
+
kept = run.with(context: sent.context, sends: sent.sends)
|
|
224
|
+
[sent.effects.reduce(kept) { |acc, effect| acc.effect(effect) }, sent.error]
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|