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
data/lib/statifier.rb
CHANGED
|
@@ -1,5 +1,389 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "statifier/version"
|
|
4
|
+
require_relative "statifier/xml"
|
|
5
|
+
require_relative "statifier/lowering"
|
|
6
|
+
require_relative "statifier/validator"
|
|
7
|
+
require_relative "statifier/compiler"
|
|
8
|
+
require_relative "statifier/datamodel"
|
|
9
|
+
require_relative "statifier/interpreter"
|
|
10
|
+
require_relative "statifier/driver"
|
|
11
|
+
require_relative "statifier/position"
|
|
12
|
+
require_relative "statifier/accepts"
|
|
13
|
+
|
|
14
|
+
# The interpreter core of an SCXML statechart engine.
|
|
15
|
+
#
|
|
16
|
+
# This is the 0.1.0 rewrite of the package. {Statifier.compile} reads SCXML
|
|
17
|
+
# source into a machine, through the reader, the lowering, the validator
|
|
18
|
+
# and the compiler, all internal. The driver
|
|
19
|
+
# ({Statifier::Driver}) steps a machine: {Statifier.start} starts a chart,
|
|
20
|
+
# {Statifier.step} sends it an event, {Statifier.advance} moves its virtual
|
|
21
|
+
# clock, {Statifier.report_send_failed} tells it a send failed, and
|
|
22
|
+
# {Statifier.configuration} and {Statifier.done?} read it.
|
|
23
|
+
# {Statifier.export_position} writes a chart's execution out as plain data a
|
|
24
|
+
# host can keep, and {Statifier.import_position} reads it back in. The
|
|
25
|
+
# interpreter under the driver is internal. {Statifier.check_accepts}
|
|
26
|
+
# compares the event names a host declares a chart accepts with the events
|
|
27
|
+
# the chart listens for.
|
|
28
|
+
#
|
|
29
|
+
# Every one of those calls but {Statifier.check_accepts}, which refuses
|
|
30
|
+
# nothing and answers its {AcceptsCheck} directly, answers a {Result}.
|
|
31
|
+
# Nothing is kept between calls but the state a call answers: there is no
|
|
32
|
+
# global configuration, and a host passes its processors and its clock with
|
|
33
|
+
# every call.
|
|
34
|
+
#
|
|
35
|
+
# The public surface is these calls and the values they take and answer:
|
|
36
|
+
# {Result}, {AcceptsCheck}, {Event}, the effects under {Effect}, the
|
|
37
|
+
# driver's values under {Driver} and the position's under {Position}, and
|
|
38
|
+
# {VERSION}; `require "statifier/basichttp"` adds {BasicHTTP}. Every other
|
|
39
|
+
# constant is private (`private_constant`), so a host cannot name it, and
|
|
40
|
+
# a value a call answers whose class is private (the machine, a driver
|
|
41
|
+
# state, a compile refusal) is read by its documented members.
|
|
3
42
|
module Statifier
|
|
4
|
-
|
|
43
|
+
# The driver's calls and checks, its state, and the position's bodies:
|
|
44
|
+
# private constants of {Driver} and {Position}, named here once so the
|
|
45
|
+
# calls below reach them.
|
|
46
|
+
DRIVER_CALLS = Driver.const_get(:Calls)
|
|
47
|
+
DRIVER_CHECKS = Driver.const_get(:Checks)
|
|
48
|
+
DRIVER_STATE = Driver.const_get(:State)
|
|
49
|
+
POSITION_CODEC = Position.const_get(:Codec)
|
|
50
|
+
|
|
51
|
+
# Everything the package builds a machine and steps it with is internal:
|
|
52
|
+
# a host reaches it only through the calls below and the values they
|
|
53
|
+
# answer.
|
|
54
|
+
private_constant :Accepts, :Compiler, :Datamodel, :Document, :Frozen, :Interpreter, :Lowering, :Machine,
|
|
55
|
+
:Validator, :XML, :DRIVER_CALLS, :DRIVER_CHECKS, :DRIVER_STATE, :POSITION_CODEC
|
|
56
|
+
|
|
57
|
+
# What every public call answers: its `value`, or, as `error`, why it
|
|
58
|
+
# was refused.
|
|
59
|
+
#
|
|
60
|
+
# @!attribute [r] value
|
|
61
|
+
# @return [Object, nil] {Statifier.compile}'s machine; the
|
|
62
|
+
# {Driver::Moved} a driver call that moves a chart answers;
|
|
63
|
+
# {Statifier.configuration}'s list of names; {Statifier.done?}'s
|
|
64
|
+
# {Driver::DoneStatus}; {Statifier.export_position}'s position;
|
|
65
|
+
# {Statifier.import_position}'s {Driver::State}. Nil when the call was
|
|
66
|
+
# refused
|
|
67
|
+
# @!attribute [r] error
|
|
68
|
+
# @return [Array, Symbol, Position::Refusal, nil] nil when the call
|
|
69
|
+
# succeeded. For {Statifier.compile}, the refusals, a frozen list that
|
|
70
|
+
# is never empty; for a driver call, the reason as a Symbol; for
|
|
71
|
+
# {Statifier.export_position} and {Statifier.import_position}, a
|
|
72
|
+
# {Position::Refusal}
|
|
73
|
+
Result = ::Data.define(:value, :error) do
|
|
74
|
+
# @return [Boolean] whether the call succeeded
|
|
75
|
+
def ok? = error.nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Compiles SCXML source into a machine: the document is read, lowered,
|
|
79
|
+
# validated and compiled, in that order, and the first stage that refuses
|
|
80
|
+
# it stops the rest.
|
|
81
|
+
#
|
|
82
|
+
# A refused source answers a failed {Result} whose `error` is the list of
|
|
83
|
+
# that stage's refusals, in document order: the reader's one parse error,
|
|
84
|
+
# or every error of the lowering, of the validator or of the compiler (an
|
|
85
|
+
# expression predicator refused). Their classes are internal. Each is a
|
|
86
|
+
# frozen value that carries a `reason` symbol, a `message` for a person
|
|
87
|
+
# and a `location`, the span in the source it names (`start_line`,
|
|
88
|
+
# `start_column`, `start_offset`, `end_line`, `end_column` and
|
|
89
|
+
# `end_offset`); all but the reader's carry a `detail` Hash too. A
|
|
90
|
+
# validator warning never refuses: a chart that compiles carries its
|
|
91
|
+
# warnings on the machine, as the reference's machine does.
|
|
92
|
+
#
|
|
93
|
+
# The machine is a frozen value, deeply, and its shape is internal: a host
|
|
94
|
+
# hands it on and reads one of its members only, `warnings`, the
|
|
95
|
+
# validator's warnings in document order, a frozen list of warning
|
|
96
|
+
# values, each with the `reason`, `detail`, `message` and `location` an
|
|
97
|
+
# error carries (empty for a chart with none).
|
|
98
|
+
#
|
|
99
|
+
# @example
|
|
100
|
+
# result = Statifier.compile(<<~SCXML)
|
|
101
|
+
# <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
|
|
102
|
+
# <state id="a"><transition event="e.go" target="b"/></state>
|
|
103
|
+
# <final id="b"/>
|
|
104
|
+
# </scxml>
|
|
105
|
+
# SCXML
|
|
106
|
+
# result.ok? # => true
|
|
107
|
+
#
|
|
108
|
+
# @param source [String] the SCXML document
|
|
109
|
+
# @return [Result] the machine, or the refusals
|
|
110
|
+
# @raise [TypeError] when `source` is not a String
|
|
111
|
+
def self.compile(source)
|
|
112
|
+
parsed = XML.parse(source)
|
|
113
|
+
return refused([parsed.error]) unless parsed.ok?
|
|
114
|
+
|
|
115
|
+
compiled = chart(parsed.value, source)
|
|
116
|
+
compiled.ok? ? Result.new(value: compiled.value, error: nil) : refused(compiled.errors)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The read tree lowered, validated and compiled: the result of the last
|
|
120
|
+
# stage that ran, each stage's result answering `ok?`, `value` and
|
|
121
|
+
# `errors`.
|
|
122
|
+
def self.chart(root, source)
|
|
123
|
+
lowered = Lowering.lower(root, source)
|
|
124
|
+
return lowered unless lowered.ok?
|
|
125
|
+
|
|
126
|
+
validated = Validator.validate(lowered.value, source)
|
|
127
|
+
return validated unless validated.ok?
|
|
128
|
+
|
|
129
|
+
Compiler.compile(validated.value, warnings: validated.warnings)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# A failed {Result} carrying `errors`.
|
|
133
|
+
def self.refused(errors) = Result.new(value: nil, error: Ractor.make_shareable(errors.dup))
|
|
134
|
+
|
|
135
|
+
private_class_method :chart, :refused
|
|
136
|
+
|
|
137
|
+
# Starts a chart: its datamodel bound, its initial states entered, and the
|
|
138
|
+
# chart run, with every event it queues for itself, to a stable
|
|
139
|
+
# configuration.
|
|
140
|
+
#
|
|
141
|
+
# @example
|
|
142
|
+
# machine = Statifier.compile(<<~SCXML).value
|
|
143
|
+
# <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
|
|
144
|
+
# <state id="a"><transition event="e.go" target="b"/></state>
|
|
145
|
+
# <final id="b"/>
|
|
146
|
+
# </scxml>
|
|
147
|
+
# SCXML
|
|
148
|
+
# started = Statifier.start(machine, session_id: "s1")
|
|
149
|
+
# Statifier.configuration(started.value.state).value # => ["a"]
|
|
150
|
+
#
|
|
151
|
+
# @param machine [Machine] a compiled chart
|
|
152
|
+
# @param options [Hash] the keywords below, each optional
|
|
153
|
+
# @option options [String, nil] :session_id the session's id, which
|
|
154
|
+
# `_sessionid` reads; the driver mints one (`sess_` and 26 base32
|
|
155
|
+
# symbols) when nil
|
|
156
|
+
# @option options [Hash{String => Object}] :datamodel the datamodel's
|
|
157
|
+
# starting values, under the system variables; copied, never frozen in
|
|
158
|
+
# place
|
|
159
|
+
# @option options [Hash{String => #deliver}] :processors the host's
|
|
160
|
+
# processor for each send type it registers ({Statifier::Driver} says
|
|
161
|
+
# what each is handed); every later call names the same types
|
|
162
|
+
# @option options [Integer, Symbol] :max_macrostep_rounds the rounds one
|
|
163
|
+
# macrostep may spend, a positive Integer or `:infinity`; 10000 when
|
|
164
|
+
# absent
|
|
165
|
+
# @option options [Boolean] :trace whether the chart answers the trace
|
|
166
|
+
# effects ({Statifier::Effect::Trace}); false when absent
|
|
167
|
+
# @option options [#call, nil] :clock the clock predicator reads for the
|
|
168
|
+
# call; its own when nil
|
|
169
|
+
# @return [Result] a {Driver::Moved}: the state and the effects
|
|
170
|
+
# @raise [TypeError] for a machine, session id or processors of the wrong
|
|
171
|
+
# kind
|
|
172
|
+
# @raise [ArgumentError] for an option it does not name, or a round
|
|
173
|
+
# budget that is neither a positive Integer nor `:infinity`
|
|
174
|
+
def self.start(machine, **options)
|
|
175
|
+
raise TypeError, "expected a compiled machine, got #{machine.class}" unless machine.is_a?(Machine)
|
|
176
|
+
|
|
177
|
+
DRIVER_CALLS.start(machine, options)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Sends the chart one external event, and runs it, and every event it
|
|
181
|
+
# queues for itself, to a stable configuration. While a macrostep's round
|
|
182
|
+
# budget has halted the chart, the event is queued and not taken.
|
|
183
|
+
#
|
|
184
|
+
# @param state [Driver::State] what the last call answered
|
|
185
|
+
# @param name [String] the event's name
|
|
186
|
+
# @param data [Object] the event's data, `Predicator::UNDEFINED` for none
|
|
187
|
+
# @param processors [Hash{String => #deliver}] as {start} took them
|
|
188
|
+
# @param clock [#call, nil] as {start} took it
|
|
189
|
+
# @return [Result] a {Driver::Moved}, or the error `:not_running` once
|
|
190
|
+
# the chart has stopped
|
|
191
|
+
# @raise [TypeError] for a state or a name of the wrong kind
|
|
192
|
+
# @raise [ArgumentError] for processors that name other types than the
|
|
193
|
+
# chart was started with
|
|
194
|
+
def self.step(state, name, data: Predicator::UNDEFINED, processors: {}, clock: nil)
|
|
195
|
+
DRIVER_CALLS.step(state, name, data:, processors:, clock:)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Moves the virtual clock `milliseconds` forward, firing every pending
|
|
199
|
+
# timer due by then, earliest first and in the order they were scheduled
|
|
200
|
+
# at one due time, each run to completion before the next.
|
|
201
|
+
#
|
|
202
|
+
# @param state [Driver::State] what the last call answered
|
|
203
|
+
# @param milliseconds [Numeric] how far the clock moves, finite and not
|
|
204
|
+
# negative
|
|
205
|
+
# @param processors [Hash{String => #deliver}] as {start} took them
|
|
206
|
+
# @param clock [#call, nil] as {start} took it
|
|
207
|
+
# @return [Result] a {Driver::Moved}, or the error `:invalid_duration`
|
|
208
|
+
# @raise [TypeError] for a state of the wrong kind
|
|
209
|
+
# @raise [ArgumentError] for processors that name other types than the
|
|
210
|
+
# chart was started with
|
|
211
|
+
def self.advance(state, milliseconds, processors: {}, clock: nil)
|
|
212
|
+
DRIVER_CALLS.advance(state, milliseconds, processors:, clock:)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Reports a send the host was handed and could not deliver:
|
|
216
|
+
# `error.communication` joins the chart's internal queue, its origin the
|
|
217
|
+
# send's content and its `sendid` the send's id whether or not its author
|
|
218
|
+
# named it, and the chart runs to a stable configuration, then takes
|
|
219
|
+
# whatever its external queue holds, as {step} does. The driver keeps no
|
|
220
|
+
# table of the sends it handed, so the report is not checked against one:
|
|
221
|
+
# it reaches the session `state` is for. A failure a processor knows of
|
|
222
|
+
# while it is handed the send is answered from its `deliver` as a
|
|
223
|
+
# {Driver::DeliveryFailure} instead, and fails the send within that call.
|
|
224
|
+
#
|
|
225
|
+
# Once the chart has stopped, or while a macrostep's round budget has
|
|
226
|
+
# halted it, the report is refused and nothing is written, as the
|
|
227
|
+
# reference's session drops it; the miss is then the host's to record.
|
|
228
|
+
#
|
|
229
|
+
# @example
|
|
230
|
+
# failed = Statifier.report_send_failed(machine, moved.state, send, processors:)
|
|
231
|
+
# failed.value.state # the chart after it took error.communication
|
|
232
|
+
#
|
|
233
|
+
# @param machine [Machine] the compiled chart `state` is an execution of
|
|
234
|
+
# @param state [Driver::State] what the last call answered
|
|
235
|
+
# @param send [Effect::Send, Effect::SendDelayed] the send the host's
|
|
236
|
+
# processor was handed
|
|
237
|
+
# @param options [Hash] the keywords below, each optional
|
|
238
|
+
# @option options [Object, nil] :reason the host's own account of the
|
|
239
|
+
# failure; not read, and the chart is not handed it
|
|
240
|
+
# @option options [Hash{String => #deliver}] :processors as {start} took
|
|
241
|
+
# them
|
|
242
|
+
# @option options [#call, nil] :clock as {start} took it
|
|
243
|
+
# @return [Result] a {Driver::Moved}, or the error `:not_running` once the
|
|
244
|
+
# chart has stopped, or `:halted` while a round budget has halted it
|
|
245
|
+
# @raise [TypeError] for a machine, a state or a send of the wrong kind
|
|
246
|
+
# @raise [ArgumentError] for an option it does not name, a state that is
|
|
247
|
+
# not an execution of `machine`, or processors that name other types
|
|
248
|
+
# than the chart was started with
|
|
249
|
+
def self.report_send_failed(machine, state, send, **options)
|
|
250
|
+
DRIVER_CALLS.report_send_failed(machine, state, send, options)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# The active states, by id, the `<scxml>` root left out, sorted; a state
|
|
254
|
+
# the document gave no id is named `#` and its index. Empty once the
|
|
255
|
+
# chart has stopped ({done?} names the states it stopped in).
|
|
256
|
+
#
|
|
257
|
+
# @param state [Driver::State] what a call answered
|
|
258
|
+
# @return [Result] the names, a frozen list
|
|
259
|
+
# @raise [TypeError] for a state of the wrong kind
|
|
260
|
+
def self.configuration(state) = DRIVER_CALLS.configuration(state)
|
|
261
|
+
|
|
262
|
+
# Whether the chart has stopped and, once it has, the top-level final's
|
|
263
|
+
# donedata and the states it stopped in, as the {Effect::Done} carried
|
|
264
|
+
# them.
|
|
265
|
+
#
|
|
266
|
+
# @param state [Driver::State] what a call answered
|
|
267
|
+
# @return [Result] a {Driver::DoneStatus}
|
|
268
|
+
# @raise [TypeError] for a state of the wrong kind
|
|
269
|
+
def self.done?(state) = DRIVER_CALLS.done?(state)
|
|
270
|
+
|
|
271
|
+
# Writes the chart's execution out as a position: plain data in the
|
|
272
|
+
# reference's string-id vocabulary, a frozen Hash with String keys whose
|
|
273
|
+
# values are Strings, Integers, booleans, and Arrays and Hashes of them,
|
|
274
|
+
# ready for JSON ({Statifier::Position} says what each key holds). A
|
|
275
|
+
# datamodel value is carried as text in predicator's tagged-value
|
|
276
|
+
# encoding.
|
|
277
|
+
#
|
|
278
|
+
# A position holds what the interpreter holds between two calls and
|
|
279
|
+
# nothing of the driver's own: no pending timer, no queued external
|
|
280
|
+
# event, no send a processor holds, no virtual time and no donedata.
|
|
281
|
+
#
|
|
282
|
+
# @example
|
|
283
|
+
# started = Statifier.start(machine, session_id: "s1").value
|
|
284
|
+
# position = Statifier.export_position(started.state).value
|
|
285
|
+
# position["configuration"] # => ["a"]
|
|
286
|
+
#
|
|
287
|
+
# @param state [Driver::State] what a call answered
|
|
288
|
+
# @return [Result] the position, or a {Position::Refusal}:
|
|
289
|
+
# `:internal_queue_not_empty` while an internal event waits;
|
|
290
|
+
# `:unnameable_states`, every index (`detail[:indexes]`, sorted) of a
|
|
291
|
+
# state the position holds that the document gave no id; or
|
|
292
|
+
# `:unencodable_value`, the first variable (`detail[:field]`) whose
|
|
293
|
+
# value the tagged-value encoding refuses, with the codec's reason
|
|
294
|
+
# @raise [TypeError] for a state of the wrong kind
|
|
295
|
+
def self.export_position(state) = POSITION_CODEC.export(DRIVER_CHECKS.state(state, nil))
|
|
296
|
+
|
|
297
|
+
# Reads a position back in as a driver state over `machine`, which may be
|
|
298
|
+
# a fresh compile of the same source or any machine holding every state
|
|
299
|
+
# the position names: the position's chart is not checked, as the
|
|
300
|
+
# reference's import checks none. The ids are resolved against `machine`,
|
|
301
|
+
# the root re-added, and the driver's own members start as a fresh start
|
|
302
|
+
# leaves them: the virtual clock at zero, no pending timer, nothing
|
|
303
|
+
# queued or held. The session id is the one `_sessionid` holds. A stopped
|
|
304
|
+
# position imports stopped, its donedata undefined and its configuration
|
|
305
|
+
# empty. The position is read, never frozen or changed.
|
|
306
|
+
#
|
|
307
|
+
# @example
|
|
308
|
+
# imported = Statifier.import_position(machine, JSON.parse(JSON.generate(position)))
|
|
309
|
+
# Statifier.configuration(imported.value).value # => ["a"]
|
|
310
|
+
#
|
|
311
|
+
# @param machine [Machine] a compiled chart
|
|
312
|
+
# @param position [Hash] what {export_position} answered, or the same
|
|
313
|
+
# data read back from JSON
|
|
314
|
+
# @param processors [Hash{String => #deliver}] the host's processors, as
|
|
315
|
+
# {start} takes them; every later call names the same types
|
|
316
|
+
# @return [Result] the {Driver::State}, or a {Position::Refusal}:
|
|
317
|
+
# `:malformed_export` when `position` is not a Hash
|
|
318
|
+
# (`detail[:kind]` `:not_a_hash`), lacks a key (`:missing_keys`, every
|
|
319
|
+
# one in `detail[:keys]`, sorted), holds a field of the wrong shape
|
|
320
|
+
# (`:bad_shape`, the first in `detail[:field]`), a datamodel text that
|
|
321
|
+
# does not decode (`:undecodable_value`) or no String `_sessionid`
|
|
322
|
+
# (`:no_session_id`); or `:unknown_state_ids`, every id `machine` holds
|
|
323
|
+
# no state for (`detail[:ids]`, sorted)
|
|
324
|
+
# @raise [TypeError] for a machine or processors of the wrong kind
|
|
325
|
+
def self.import_position(machine, position, processors: {})
|
|
326
|
+
raise TypeError, "expected a compiled machine, got #{machine.class}" unless machine.is_a?(Machine)
|
|
327
|
+
|
|
328
|
+
POSITION_CODEC.import(machine, position, processors)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Compares `declared_events`, the event names a chart is declared to
|
|
332
|
+
# accept, with the chart's event vocabulary: every event descriptor on a
|
|
333
|
+
# transition whose source state can be active, as authored, a pattern
|
|
334
|
+
# never expanded. "Can be active" is a static rule over the chart's
|
|
335
|
+
# structure that reads no `cond` (a state some path of transitions and
|
|
336
|
+
# initial, parallel and history defaults enters, with its ancestors), so
|
|
337
|
+
# it over-counts and never under-counts. The reference's
|
|
338
|
+
# `Statifier.Chart.check_accepts/2` answers the same two lists.
|
|
339
|
+
#
|
|
340
|
+
# A descriptor matches a declared name as transition selection matches an
|
|
341
|
+
# event, on token boundaries: a declared `e.go` is matched by the
|
|
342
|
+
# descriptors `e.go`, `e.*`, `e.`, `e` and `*`, and not by `e.gone` or
|
|
343
|
+
# `e.go.now`. A declared name is a name, not a descriptor: a `*` in it is
|
|
344
|
+
# an ordinary token, so a declared `e.*` is matched by the descriptor `e`
|
|
345
|
+
# and not by `e.go`.
|
|
346
|
+
#
|
|
347
|
+
# - `unreachable`: each declared name no descriptor matches, in the
|
|
348
|
+
# declaration's order and without duplicates.
|
|
349
|
+
# - `undeclared`: each descriptor that matches no declared name, in
|
|
350
|
+
# document order of the transitions (a state's own before its
|
|
351
|
+
# children's), each descriptor once.
|
|
352
|
+
#
|
|
353
|
+
# An empty declaration declares that the chart accepts nothing:
|
|
354
|
+
# `unreachable` is empty and `undeclared` is the whole vocabulary. With
|
|
355
|
+
# nil, no declaration, the vocabulary is the contract, so both lists are
|
|
356
|
+
# empty; a host asking whether one name `n` is in the vocabulary passes
|
|
357
|
+
# `[n]` and reads `unreachable`. It reports and refuses nothing: which
|
|
358
|
+
# list a host refuses a chart on, if either, is the host's decision. The
|
|
359
|
+
# declared names are read, never frozen or changed.
|
|
360
|
+
#
|
|
361
|
+
# @example
|
|
362
|
+
# machine = Statifier.compile(<<~SCXML).value
|
|
363
|
+
# <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
|
|
364
|
+
# <state id="a"><transition event="e.go" target="b"/></state>
|
|
365
|
+
# <state id="b"><transition event="e.back" target="a"/></state>
|
|
366
|
+
# </scxml>
|
|
367
|
+
# SCXML
|
|
368
|
+
# check = Statifier.check_accepts(machine, ["e.go", "e.stop"])
|
|
369
|
+
# check.unreachable # => ["e.stop"]
|
|
370
|
+
# check.undeclared # => ["e.back"]
|
|
371
|
+
#
|
|
372
|
+
# @param machine [Machine] a compiled chart
|
|
373
|
+
# @param declared_events [Array<String>, nil] the declared event names,
|
|
374
|
+
# or nil for no declaration
|
|
375
|
+
# @return [AcceptsCheck] the two lists, frozen
|
|
376
|
+
# @raise [TypeError] for a machine of the wrong kind, a declaration that
|
|
377
|
+
# is neither an Array nor nil, or a declared name that is not a String
|
|
378
|
+
def self.check_accepts(machine, declared_events)
|
|
379
|
+
raise TypeError, "expected a compiled machine, got #{machine.class}" unless machine.is_a?(Machine)
|
|
380
|
+
unless declared_events.nil? || declared_events.is_a?(Array)
|
|
381
|
+
raise TypeError, "expected the declared events as an Array or nil, got #{declared_events.class}"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
stray = declared_events&.find { !_1.is_a?(String) }
|
|
385
|
+
raise TypeError, "expected every declared event to be a String, got #{stray.class}" unless stray.nil?
|
|
386
|
+
|
|
387
|
+
Accepts.check(machine, declared_events)
|
|
388
|
+
end
|
|
5
389
|
end
|
metadata
CHANGED
|
@@ -1,40 +1,150 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: statifier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- JohnnyT
|
|
8
|
-
|
|
9
|
-
bindir: exe
|
|
8
|
+
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: predicator
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
32
|
+
description: 'Interprets SCXML statecharts as a value step: an immutable state and
|
|
33
|
+
an event in, a new state and plain-data effects out. Conditions and expressions
|
|
34
|
+
are evaluated by the predicator gem.'
|
|
14
35
|
email:
|
|
15
36
|
- ubergeek3141@gmail.com
|
|
16
37
|
executables: []
|
|
17
38
|
extensions: []
|
|
18
39
|
extra_rdoc_files: []
|
|
19
40
|
files:
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
- Gemfile
|
|
23
|
-
- Gemfile.lock
|
|
24
|
-
- LICENSE.txt
|
|
41
|
+
- CHANGELOG.md
|
|
42
|
+
- LICENSE
|
|
25
43
|
- README.md
|
|
26
|
-
- Rakefile
|
|
27
|
-
- bin/console
|
|
28
|
-
- bin/setup
|
|
29
44
|
- lib/statifier.rb
|
|
45
|
+
- lib/statifier/accepts.rb
|
|
46
|
+
- lib/statifier/basichttp.rb
|
|
47
|
+
- lib/statifier/basichttp/decode.rb
|
|
48
|
+
- lib/statifier/basichttp/encoding.rb
|
|
49
|
+
- lib/statifier/basichttp/net_http_transport.rb
|
|
50
|
+
- lib/statifier/basichttp/outbound.rb
|
|
51
|
+
- lib/statifier/basichttp/processor.rb
|
|
52
|
+
- lib/statifier/basichttp/request.rb
|
|
53
|
+
- lib/statifier/basichttp/response.rb
|
|
54
|
+
- lib/statifier/compiler.rb
|
|
55
|
+
- lib/statifier/compiler/content.rb
|
|
56
|
+
- lib/statifier/compiler/error.rb
|
|
57
|
+
- lib/statifier/compiler/expressions.rb
|
|
58
|
+
- lib/statifier/compiler/numbering.rb
|
|
59
|
+
- lib/statifier/compiler/parts.rb
|
|
60
|
+
- lib/statifier/compiler/walk.rb
|
|
61
|
+
- lib/statifier/datamodel.rb
|
|
62
|
+
- lib/statifier/datamodel/data_binding.rb
|
|
63
|
+
- lib/statifier/datamodel/delay.rb
|
|
64
|
+
- lib/statifier/datamodel/location.rb
|
|
65
|
+
- lib/statifier/datamodel/program.rb
|
|
66
|
+
- lib/statifier/datamodel/reason_value.rb
|
|
67
|
+
- lib/statifier/datamodel/reasons.rb
|
|
68
|
+
- lib/statifier/datamodel/stores.rb
|
|
69
|
+
- lib/statifier/datamodel/system_variables.rb
|
|
70
|
+
- lib/statifier/document.rb
|
|
71
|
+
- lib/statifier/document/data.rb
|
|
72
|
+
- lib/statifier/document/executable.rb
|
|
73
|
+
- lib/statifier/document/invoke.rb
|
|
74
|
+
- lib/statifier/document/states.rb
|
|
75
|
+
- lib/statifier/driver.rb
|
|
76
|
+
- lib/statifier/driver/calls.rb
|
|
77
|
+
- lib/statifier/driver/checks.rb
|
|
78
|
+
- lib/statifier/driver/delivery.rb
|
|
79
|
+
- lib/statifier/driver/invocations.rb
|
|
80
|
+
- lib/statifier/driver/mailbox.rb
|
|
81
|
+
- lib/statifier/driver/routing.rb
|
|
82
|
+
- lib/statifier/driver/session_id.rb
|
|
83
|
+
- lib/statifier/driver/work.rb
|
|
84
|
+
- lib/statifier/effect.rb
|
|
85
|
+
- lib/statifier/event.rb
|
|
86
|
+
- lib/statifier/frozen.rb
|
|
87
|
+
- lib/statifier/interpreter.rb
|
|
88
|
+
- lib/statifier/interpreter/blocks.rb
|
|
89
|
+
- lib/statifier/interpreter/content.rb
|
|
90
|
+
- lib/statifier/interpreter/entering.rb
|
|
91
|
+
- lib/statifier/interpreter/exit_entry.rb
|
|
92
|
+
- lib/statifier/interpreter/foreach.rb
|
|
93
|
+
- lib/statifier/interpreter/invoke.rb
|
|
94
|
+
- lib/statifier/interpreter/invoke/arguments.rb
|
|
95
|
+
- lib/statifier/interpreter/invoke/passes.rb
|
|
96
|
+
- lib/statifier/interpreter/loop.rb
|
|
97
|
+
- lib/statifier/interpreter/machine_state.rb
|
|
98
|
+
- lib/statifier/interpreter/macrostep.rb
|
|
99
|
+
- lib/statifier/interpreter/name_match.rb
|
|
100
|
+
- lib/statifier/interpreter/selection.rb
|
|
101
|
+
- lib/statifier/interpreter/send.rb
|
|
102
|
+
- lib/statifier/interpreter/step.rb
|
|
103
|
+
- lib/statifier/interpreter/stop.rb
|
|
104
|
+
- lib/statifier/interpreter/targets.rb
|
|
105
|
+
- lib/statifier/lowering.rb
|
|
106
|
+
- lib/statifier/lowering/branches.rb
|
|
107
|
+
- lib/statifier/lowering/data.rb
|
|
108
|
+
- lib/statifier/lowering/error.rb
|
|
109
|
+
- lib/statifier/lowering/executable.rb
|
|
110
|
+
- lib/statifier/lowering/placement.rb
|
|
111
|
+
- lib/statifier/lowering/reading.rb
|
|
112
|
+
- lib/statifier/lowering/states.rb
|
|
113
|
+
- lib/statifier/machine.rb
|
|
114
|
+
- lib/statifier/machine/content.rb
|
|
115
|
+
- lib/statifier/machine/data.rb
|
|
116
|
+
- lib/statifier/machine/states.rb
|
|
117
|
+
- lib/statifier/machine/values.rb
|
|
118
|
+
- lib/statifier/position.rb
|
|
119
|
+
- lib/statifier/position/rebuild.rb
|
|
120
|
+
- lib/statifier/position/shape.rb
|
|
121
|
+
- lib/statifier/position/text.rb
|
|
122
|
+
- lib/statifier/validator.rb
|
|
123
|
+
- lib/statifier/validator/attributes.rb
|
|
124
|
+
- lib/statifier/validator/context.rb
|
|
125
|
+
- lib/statifier/validator/data.rb
|
|
126
|
+
- lib/statifier/validator/error.rb
|
|
127
|
+
- lib/statifier/validator/executable.rb
|
|
128
|
+
- lib/statifier/validator/history.rb
|
|
129
|
+
- lib/statifier/validator/messages.rb
|
|
130
|
+
- lib/statifier/validator/states.rb
|
|
30
131
|
- lib/statifier/version.rb
|
|
31
|
-
- statifier.
|
|
132
|
+
- lib/statifier/xml.rb
|
|
133
|
+
- lib/statifier/xml/character_data.rb
|
|
134
|
+
- lib/statifier/xml/cursor.rb
|
|
135
|
+
- lib/statifier/xml/markup.rb
|
|
136
|
+
- lib/statifier/xml/nodes.rb
|
|
137
|
+
- lib/statifier/xml/parser.rb
|
|
138
|
+
- lib/statifier/xml/scope.rb
|
|
139
|
+
- lib/statifier/xml/tags.rb
|
|
32
140
|
homepage: https://github.com/riddler/statifier-rb
|
|
33
141
|
licenses:
|
|
34
142
|
- MIT
|
|
35
143
|
metadata:
|
|
36
144
|
homepage_uri: https://github.com/riddler/statifier-rb
|
|
37
|
-
|
|
145
|
+
source_code_uri: https://github.com/riddler/statifier-rb
|
|
146
|
+
changelog_uri: https://github.com/riddler/statifier-rb/blob/main/CHANGELOG.md
|
|
147
|
+
rubygems_mfa_required: 'true'
|
|
38
148
|
rdoc_options: []
|
|
39
149
|
require_paths:
|
|
40
150
|
- lib
|
|
@@ -42,15 +152,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
42
152
|
requirements:
|
|
43
153
|
- - ">="
|
|
44
154
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '
|
|
155
|
+
version: '3.3'
|
|
46
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
157
|
requirements:
|
|
48
158
|
- - ">="
|
|
49
159
|
- !ruby/object:Gem::Version
|
|
50
160
|
version: '0'
|
|
51
161
|
requirements: []
|
|
52
|
-
rubygems_version:
|
|
53
|
-
signing_key:
|
|
162
|
+
rubygems_version: 4.0.20
|
|
54
163
|
specification_version: 4
|
|
55
|
-
summary:
|
|
164
|
+
summary: The interpreter core of an SCXML statechart engine.
|
|
56
165
|
test_files: []
|
data/.gitignore
DELETED
data/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Contributor Covenant Code of Conduct
|
|
2
|
-
|
|
3
|
-
## Our Pledge
|
|
4
|
-
|
|
5
|
-
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
-
contributors and maintainers pledge to making participation in our project and
|
|
7
|
-
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
-
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
9
|
-
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
-
orientation.
|
|
11
|
-
|
|
12
|
-
## Our Standards
|
|
13
|
-
|
|
14
|
-
Examples of behavior that contributes to creating a positive environment
|
|
15
|
-
include:
|
|
16
|
-
|
|
17
|
-
* Using welcoming and inclusive language
|
|
18
|
-
* Being respectful of differing viewpoints and experiences
|
|
19
|
-
* Gracefully accepting constructive criticism
|
|
20
|
-
* Focusing on what is best for the community
|
|
21
|
-
* Showing empathy towards other community members
|
|
22
|
-
|
|
23
|
-
Examples of unacceptable behavior by participants include:
|
|
24
|
-
|
|
25
|
-
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
-
advances
|
|
27
|
-
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
-
* Public or private harassment
|
|
29
|
-
* Publishing others' private information, such as a physical or electronic
|
|
30
|
-
address, without explicit permission
|
|
31
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
-
professional setting
|
|
33
|
-
|
|
34
|
-
## Our Responsibilities
|
|
35
|
-
|
|
36
|
-
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
-
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
-
response to any instances of unacceptable behavior.
|
|
39
|
-
|
|
40
|
-
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
-
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
-
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
-
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
-
threatening, offensive, or harmful.
|
|
45
|
-
|
|
46
|
-
## Scope
|
|
47
|
-
|
|
48
|
-
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
-
when an individual is representing the project or its community. Examples of
|
|
50
|
-
representing a project or community include using an official project e-mail
|
|
51
|
-
address, posting via an official social media account, or acting as an appointed
|
|
52
|
-
representative at an online or offline event. Representation of a project may be
|
|
53
|
-
further defined and clarified by project maintainers.
|
|
54
|
-
|
|
55
|
-
## Enforcement
|
|
56
|
-
|
|
57
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
-
reported by contacting the project team at ubergeek3141@gmail.com. All
|
|
59
|
-
complaints will be reviewed and investigated and will result in a response that
|
|
60
|
-
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
|
-
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
62
|
-
Further details of specific enforcement policies may be posted separately.
|
|
63
|
-
|
|
64
|
-
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
-
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
-
members of the project's leadership.
|
|
67
|
-
|
|
68
|
-
## Attribution
|
|
69
|
-
|
|
70
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
71
|
-
available at [http://contributor-covenant.org/version/1/4][version]
|
|
72
|
-
|
|
73
|
-
[homepage]: http://contributor-covenant.org
|
|
74
|
-
[version]: http://contributor-covenant.org/version/1/4/
|