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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +601 -16
  5. data/lib/statifier/accepts.rb +167 -0
  6. data/lib/statifier/basichttp/decode.rb +142 -0
  7. data/lib/statifier/basichttp/encoding.rb +224 -0
  8. data/lib/statifier/basichttp/net_http_transport.rb +157 -0
  9. data/lib/statifier/basichttp/outbound.rb +87 -0
  10. data/lib/statifier/basichttp/processor.rb +170 -0
  11. data/lib/statifier/basichttp/request.rb +94 -0
  12. data/lib/statifier/basichttp/response.rb +34 -0
  13. data/lib/statifier/basichttp.rb +47 -0
  14. data/lib/statifier/compiler/content.rb +131 -0
  15. data/lib/statifier/compiler/error.rb +30 -0
  16. data/lib/statifier/compiler/expressions.rb +64 -0
  17. data/lib/statifier/compiler/numbering.rb +145 -0
  18. data/lib/statifier/compiler/parts.rb +144 -0
  19. data/lib/statifier/compiler/walk.rb +61 -0
  20. data/lib/statifier/compiler.rb +82 -0
  21. data/lib/statifier/datamodel/data_binding.rb +114 -0
  22. data/lib/statifier/datamodel/delay.rb +54 -0
  23. data/lib/statifier/datamodel/location.rb +145 -0
  24. data/lib/statifier/datamodel/program.rb +82 -0
  25. data/lib/statifier/datamodel/reason_value.rb +42 -0
  26. data/lib/statifier/datamodel/reasons.rb +88 -0
  27. data/lib/statifier/datamodel/stores.rb +169 -0
  28. data/lib/statifier/datamodel/system_variables.rb +69 -0
  29. data/lib/statifier/datamodel.rb +120 -0
  30. data/lib/statifier/document/data.rb +36 -0
  31. data/lib/statifier/document/executable.rb +69 -0
  32. data/lib/statifier/document/invoke.rb +17 -0
  33. data/lib/statifier/document/states.rb +40 -0
  34. data/lib/statifier/document.rb +51 -0
  35. data/lib/statifier/driver/calls.rb +183 -0
  36. data/lib/statifier/driver/checks.rb +110 -0
  37. data/lib/statifier/driver/delivery.rb +45 -0
  38. data/lib/statifier/driver/invocations.rb +129 -0
  39. data/lib/statifier/driver/mailbox.rb +120 -0
  40. data/lib/statifier/driver/routing.rb +193 -0
  41. data/lib/statifier/driver/session_id.rb +40 -0
  42. data/lib/statifier/driver/work.rb +196 -0
  43. data/lib/statifier/driver.rb +205 -0
  44. data/lib/statifier/effect.rb +208 -0
  45. data/lib/statifier/event.rb +67 -0
  46. data/lib/statifier/frozen.rb +22 -0
  47. data/lib/statifier/interpreter/blocks.rb +142 -0
  48. data/lib/statifier/interpreter/content.rb +228 -0
  49. data/lib/statifier/interpreter/entering.rb +141 -0
  50. data/lib/statifier/interpreter/exit_entry.rb +174 -0
  51. data/lib/statifier/interpreter/foreach.rb +83 -0
  52. data/lib/statifier/interpreter/invoke/arguments.rb +52 -0
  53. data/lib/statifier/interpreter/invoke/passes.rb +143 -0
  54. data/lib/statifier/interpreter/invoke.rb +145 -0
  55. data/lib/statifier/interpreter/loop.rb +170 -0
  56. data/lib/statifier/interpreter/machine_state.rb +152 -0
  57. data/lib/statifier/interpreter/macrostep.rb +185 -0
  58. data/lib/statifier/interpreter/name_match.rb +43 -0
  59. data/lib/statifier/interpreter/selection.rb +221 -0
  60. data/lib/statifier/interpreter/send.rb +223 -0
  61. data/lib/statifier/interpreter/step.rb +138 -0
  62. data/lib/statifier/interpreter/stop.rb +66 -0
  63. data/lib/statifier/interpreter/targets.rb +107 -0
  64. data/lib/statifier/interpreter.rb +57 -0
  65. data/lib/statifier/lowering/branches.rb +84 -0
  66. data/lib/statifier/lowering/data.rb +102 -0
  67. data/lib/statifier/lowering/error.rb +102 -0
  68. data/lib/statifier/lowering/executable.rb +106 -0
  69. data/lib/statifier/lowering/placement.rb +65 -0
  70. data/lib/statifier/lowering/reading.rb +53 -0
  71. data/lib/statifier/lowering/states.rb +111 -0
  72. data/lib/statifier/lowering.rb +129 -0
  73. data/lib/statifier/machine/content.rb +81 -0
  74. data/lib/statifier/machine/data.rb +42 -0
  75. data/lib/statifier/machine/states.rb +56 -0
  76. data/lib/statifier/machine/values.rb +37 -0
  77. data/lib/statifier/machine.rb +103 -0
  78. data/lib/statifier/position/rebuild.rb +84 -0
  79. data/lib/statifier/position/shape.rb +73 -0
  80. data/lib/statifier/position/text.rb +43 -0
  81. data/lib/statifier/position.rb +206 -0
  82. data/lib/statifier/validator/attributes.rb +35 -0
  83. data/lib/statifier/validator/context.rb +74 -0
  84. data/lib/statifier/validator/data.rb +136 -0
  85. data/lib/statifier/validator/error.rb +90 -0
  86. data/lib/statifier/validator/executable.rb +139 -0
  87. data/lib/statifier/validator/history.rb +81 -0
  88. data/lib/statifier/validator/messages.rb +142 -0
  89. data/lib/statifier/validator/states.rb +161 -0
  90. data/lib/statifier/validator.rb +94 -0
  91. data/lib/statifier/version.rb +4 -1
  92. data/lib/statifier/xml/character_data.rb +142 -0
  93. data/lib/statifier/xml/cursor.rb +131 -0
  94. data/lib/statifier/xml/markup.rb +153 -0
  95. data/lib/statifier/xml/nodes.rb +50 -0
  96. data/lib/statifier/xml/parser.rb +121 -0
  97. data/lib/statifier/xml/scope.rb +46 -0
  98. data/lib/statifier/xml/tags.rb +112 -0
  99. data/lib/statifier/xml.rb +85 -0
  100. data/lib/statifier.rb +386 -2
  101. metadata +129 -20
  102. data/.gitignore +0 -8
  103. data/CODE_OF_CONDUCT.md +0 -74
  104. data/Gemfile +0 -11
  105. data/Gemfile.lock +0 -33
  106. data/Rakefile +0 -10
  107. data/bin/console +0 -8
  108. data/bin/setup +0 -6
  109. data/statifier.gemspec +0 -34
data/lib/statifier.rb CHANGED
@@ -1,5 +1,389 @@
1
- require "statifier/version"
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
- class Error < StandardError; end
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.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - JohnnyT
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2019-10-30 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
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
- - ".gitignore"
21
- - CODE_OF_CONDUCT.md
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.gemspec
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
- post_install_message:
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: '0'
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: 3.0.6
53
- signing_key:
162
+ rubygems_version: 4.0.20
54
163
  specification_version: 4
55
- summary: Statecharts in Ruby
164
+ summary: The interpreter core of an SCXML statechart engine.
56
165
  test_files: []
data/.gitignore DELETED
@@ -1,8 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
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/
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
- gem "bundler"
6
- gem "minitest"
7
- gem "minitest-focus"
8
- gem "pry-nav"
9
- gem "rake"
10
-
11
- gemspec