inform-runtime 1.0.4

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +185 -0
  4. data/Rakefile +65 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +76 -0
  9. data/game/example.rb +90 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/game_grammar.inf.rb +11 -0
  12. data/game/languages/english.rb +2 -0
  13. data/game/models/example_model.rb +2 -0
  14. data/game/modules/example_module.rb +9 -0
  15. data/game/rules/example_state.rb +2 -0
  16. data/game/scripts/example_script.rb +2 -0
  17. data/game/topics/example_topic.rb +2 -0
  18. data/game/verbs/game_verbs.rb +15 -0
  19. data/game/verbs/metaverbs.rb +2028 -0
  20. data/lib/runtime/articles.rb +138 -0
  21. data/lib/runtime/builtins.rb +359 -0
  22. data/lib/runtime/color.rb +145 -0
  23. data/lib/runtime/command.rb +470 -0
  24. data/lib/runtime/config.rb +48 -0
  25. data/lib/runtime/context.rb +78 -0
  26. data/lib/runtime/daemon.rb +266 -0
  27. data/lib/runtime/database.rb +500 -0
  28. data/lib/runtime/events.rb +771 -0
  29. data/lib/runtime/experimental/handler_dsl.rb +175 -0
  30. data/lib/runtime/game.rb +74 -0
  31. data/lib/runtime/game_loader.rb +132 -0
  32. data/lib/runtime/grammar_parser.rb +553 -0
  33. data/lib/runtime/helpers.rb +177 -0
  34. data/lib/runtime/history.rb +45 -0
  35. data/lib/runtime/inflector.rb +195 -0
  36. data/lib/runtime/io.rb +174 -0
  37. data/lib/runtime/kernel.rb +450 -0
  38. data/lib/runtime/library.rb +59 -0
  39. data/lib/runtime/library_loader.rb +135 -0
  40. data/lib/runtime/link.rb +158 -0
  41. data/lib/runtime/logging.rb +197 -0
  42. data/lib/runtime/mixins.rb +570 -0
  43. data/lib/runtime/module.rb +202 -0
  44. data/lib/runtime/object.rb +761 -0
  45. data/lib/runtime/options.rb +104 -0
  46. data/lib/runtime/persistence.rb +292 -0
  47. data/lib/runtime/plurals.rb +60 -0
  48. data/lib/runtime/prototype.rb +307 -0
  49. data/lib/runtime/publication.rb +92 -0
  50. data/lib/runtime/runtime.rb +321 -0
  51. data/lib/runtime/session.rb +202 -0
  52. data/lib/runtime/stdlib.rb +604 -0
  53. data/lib/runtime/subscription.rb +47 -0
  54. data/lib/runtime/tag.rb +287 -0
  55. data/lib/runtime/tree.rb +204 -0
  56. data/lib/runtime/version.rb +24 -0
  57. data/lib/runtime/world_tree.rb +69 -0
  58. data/lib/runtime.rb +35 -0
  59. metadata +199 -0
@@ -0,0 +1,307 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # Copyright Nels Nelson 2008-2023 but freely usable (see license)
5
+ #
6
+ # This file is part of the Inform Runtime.
7
+ #
8
+ # The Inform Runtime is free software: you can redistribute it and/or
9
+ # modify it under the terms of the GNU General Public License as published
10
+ # by the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # The Inform Runtime is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ # The Inform module
22
+ module Inform
23
+ # The Inform::Prototypical module
24
+ module Prototypical
25
+ def context_event
26
+ Thread.current[:event]
27
+ end
28
+
29
+ def instance_variables_map
30
+ instance_variables.each_with_object({}) do |key, memo|
31
+ memo[key] = instance_variable_get(key)
32
+ end
33
+ end
34
+
35
+ def values
36
+ super || instance_variables_map
37
+ rescue StandardError => e
38
+ log.warn "Unexpected error invoking super(Inform::Prototypical#values): #{e.message}"
39
+ instance_variables_map
40
+ end
41
+
42
+ def string?(key)
43
+ v = self.&key
44
+ return false if v.nil?
45
+ v.respond_to?(:to_s) && !v.to_s.empty?
46
+ end
47
+
48
+ MethodWriterTemplate = '%<method_name>s='.freeze
49
+
50
+ # This method tries to access or write values given by *args
51
+ # to the property using an accessor or writer method, and
52
+ # failing that, will invoke the prototype method, which
53
+ # dynamically defines an accessor or writer method on the
54
+ # receiver for future ease of access.
55
+ # rubocop: disable Metrics/AbcSize
56
+ # rubocop: disable Metrics/BlockNesting
57
+ # rubocop: disable Metrics/CyclomaticComplexity
58
+ # rubocop: disable Metrics/MethodLength
59
+ # rubocop: disable Metrics/PerceivedComplexity
60
+ def &(property, *args)
61
+ return nil if property.nil?
62
+ return super unless property.is_a?(Symbol) || property.is_a?(String)
63
+ return nil if property.respond_to?(:empty?) && property.empty?
64
+ property, sub_property = property.to_s.split('.').map(&:to_sym)
65
+ if !args.empty?
66
+ setter = format(MethodWriterTemplate, method_name: property)
67
+ if !sub_property.nil?
68
+ value = self.send(property)
69
+ if value.object?
70
+ value.&(sub_property, *args)
71
+ elsif value.respond_to?(:key?)
72
+ value[sub_property] = (args.length > 1 ? args : args.first)
73
+ self.send(setter, value)
74
+ else
75
+ self.send(setter, *args)
76
+ end
77
+ elsif self.respond_to?(property) && args.length == self.method(property).arity
78
+ self.send(property, *args)
79
+ elsif self.respond_to?(setter) && args.length == self.method(setter).arity
80
+ self.send(setter, *args)
81
+ elsif !Kernel.methods.include?(property)
82
+ self.send(property, *args)
83
+ else
84
+ self.prototype(property, *args)
85
+ end
86
+ elsif !sub_property.nil?
87
+ value = self.send(property)
88
+ if value.object?
89
+ value.&(sub_property, *args)
90
+ elsif value.respond_to?(property)
91
+ value.send(property)
92
+ elsif value.respond_to?(:key?)
93
+ value[sub_property]
94
+ else
95
+ value
96
+ end
97
+ elsif self.respond_to?(property)
98
+ self.send(property)
99
+ elsif (self.properties.respond_to?(:key?) && self.properties.key?(property)) ||
100
+ (self.respond_to?(:_key?) && self._key?(property))
101
+ self.prototype(property)
102
+ else
103
+ return nil
104
+ end
105
+ end
106
+ # rubocop: enable Metrics/AbcSize
107
+ # rubocop: enable Metrics/BlockNesting
108
+ # rubocop: enable Metrics/CyclomaticComplexity
109
+ # rubocop: enable Metrics/MethodLength
110
+ # rubocop: enable Metrics/PerceivedComplexity
111
+
112
+ # This method attempts to get a value if it exists.
113
+ # If no value is already set on the receiver, any given
114
+ # arguments will be set on the receiver for future access.
115
+ def |(property, *args)
116
+ return nil if property.nil?
117
+ return super unless property.respond_to?(:to_s)
118
+ return nil if property.to_s.empty?
119
+ v = self.&property
120
+ v || send(property, *args)
121
+ end
122
+
123
+ VariableTemplate = '@%<property>s'.freeze
124
+ MethodBooleanOrBangPattern = %r{([\?!]+)$}.freeze
125
+ MethodWriterPattern = %r{=$}.freeze
126
+ LinkPattern = %r{.+_to$}.freeze
127
+
128
+ # rubocop: disable Metrics/AbcSize
129
+ # rubocop: disable Metrics/CyclomaticComplexity
130
+ # rubocop: disable Metrics/MethodLength
131
+ # rubocop: disable Metrics/PerceivedComplexity
132
+ # rubocop: disable Style/MissingRespondToMissing
133
+ def method_missing(method, *args, &block)
134
+ return super if method == :to_ary
135
+ m = method.to_s
136
+ return super if MethodBooleanOrBangPattern.match?(m)
137
+ mname = MethodWriterPattern.match?(m) ? m.chop : m
138
+ property = mname.to_sym
139
+ variable = format(VariableTemplate, property: property).to_sym
140
+ event = context_event
141
+
142
+ if MethodWriterPattern.match?(m) || (LinkPattern.match?(m) && !args.empty?)
143
+ if !event.nil? && Inform::Runtime.invocation_properties.include?(property)
144
+ event.send(method, *args, &block)
145
+ elsif inflib&.library_method?(method)
146
+ inflib.send(method, *args, &block)
147
+ else
148
+ self.prototype(method, *args, &block)
149
+ end
150
+ elsif !event.nil? && Inform::Runtime.invocation_properties.include?(property)
151
+ result = event.send(method, *args, &block)
152
+ log.debug("Value for event #{event}.#{method} is: #{result || 'nil'}") if defined? DEBUG
153
+ result
154
+ elsif inflib&.library_method?(method)
155
+ inflib.send(method, *args, &block)
156
+ elsif inflib.respond_to?(method) && Inform::Runtime.invocation_properties.include?(property)
157
+ inflib.send(method, *args, &block)
158
+ elsif inflib&.instance_variable_get(variable) || inflib&.instance_variables&.include?(variable)
159
+ inflib.instance_variable_get(variable)
160
+ elsif (properties.respond_to?(:key?) && properties.key?(property)) ||
161
+ (respond_to?(:_key?) && _key?(property)) ||
162
+ !args.empty?
163
+ self.prototype(method, *args, &block)
164
+ else
165
+ super
166
+ end
167
+ end
168
+ # rubocop: enable Metrics/AbcSize
169
+ # rubocop: enable Metrics/CyclomaticComplexity
170
+ # rubocop: enable Metrics/MethodLength
171
+ # rubocop: enable Metrics/PerceivedComplexity
172
+ # rubocop: enable Style/MissingRespondToMissing
173
+
174
+ # def respond_to_missing?(method, include_all=false)
175
+ # m = method.to_s
176
+ # return false if m =~ /\?$/
177
+ # mname = m =~ /\=$/ ? m.chop : m
178
+ # property = mname.intern
179
+ # Inform::Runtime.invocation_properties.include? property
180
+ # end
181
+
182
+ def accessor_and_writer(key)
183
+ return [key.to_s.chop, key] if MethodWriterPattern.match?(key)
184
+ [key, format(MethodWriterTemplate, method_name: key)]
185
+ end
186
+
187
+ AccessorMethodTemplate = %(
188
+ def %<accessor>s(*args)
189
+ args.empty? ? self._get(__method__) : self._set(__method__, *args)
190
+ end
191
+ ).freeze
192
+
193
+ WriterMethodTemplate = %(
194
+ def %<writer>s(value)
195
+ self._assign(__method__.to_s.chop.to_sym, value)
196
+ end
197
+ ).freeze
198
+
199
+ # rubocop: disable Metrics/AbcSize
200
+ # rubocop: disable Metrics/CyclomaticComplexity
201
+ # rubocop: disable Metrics/MethodLength
202
+ def prototype(key, *args, &block)
203
+ ensure_properties_attribute!
204
+ accessor, writer = accessor_and_writer(key)
205
+ if key_refers_to_link?(key)
206
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
207
+ if args.empty? && self.properties.key?(key)
208
+ return if (to_obj = get_constant(self.properties[key])).nil? || !to_obj.object?
209
+ self.link(key, to_obj)
210
+ self.properties.delete(key)
211
+ self.save_changes if self.respond_to?(:save_changes)
212
+ return to_obj
213
+ end
214
+ self.properties[key] = args.first
215
+ end
216
+ accessor = format(AccessorMethodTemplate, accessor: accessor)
217
+ self.instance_eval(accessor, __FILE__, __LINE__)
218
+ writer = format(WriterMethodTemplate, writer: writer)
219
+ self.instance_eval(writer, __FILE__, __LINE__)
220
+ self.send(key, *args, &block)
221
+ end
222
+ # rubocop: enable Metrics/AbcSize
223
+ # rubocop: enable Metrics/CyclomaticComplexity
224
+ # rubocop: enable Metrics/MethodLength
225
+
226
+ MISSING_PROPERTIES_MESSAGE = 'Missing properties attribute for: ' \
227
+ '%<identity>s'.freeze
228
+
229
+ def ensure_properties_attribute!
230
+ return unless self.properties.nil?
231
+ raise StandardError, format(MISSING_PROPERTIES_MESSAGE, identity: self.identity)
232
+ end
233
+
234
+ def key_refers_to_link?(key)
235
+ Inform::Library::DeclaredProperties.memo.key?(key) && LinkPattern.match?(key.to_s)
236
+ end
237
+
238
+ def _get(key)
239
+ result = nil
240
+ result = self._get_object(key) if self.respond_to?(:_key?) && self._key?(key)
241
+ result || self.properties.dup[key]
242
+ end
243
+
244
+ # rubocop: disable Metrics/AbcSize
245
+ # rubocop: disable Metrics/CyclomaticComplexity
246
+ # rubocop: disable Metrics/MethodLength
247
+ # rubocop: disable Metrics/PerceivedComplexity
248
+ def _set(key, *values)
249
+ raise "The key parameter must be a symbol" unless key.is_a? Symbol
250
+ raise "The values parameter may not be empty" if values.empty?
251
+ result = nil
252
+ if values.first.object?
253
+ result = self._set_object(key, values.first) if respond_to? :_set_object
254
+ elsif values.first.nil? && self.respond_to?(:_key?) && self._key?(key)
255
+ result = self._unset_object(key) if self.respond_to?(:_unset_object)
256
+ elsif values.first.nil?
257
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
258
+ self.properties.delete(key)
259
+ self.save_changes if self.respond_to?(:save_changes)
260
+ elsif values.length > 1
261
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
262
+ self.properties[key] = []
263
+ self.properties[key].concat(values)
264
+ values.clear
265
+ self.save_changes if self.respond_to?(:save_changes)
266
+ else
267
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
268
+ self.properties[key] = values.first
269
+ self.save_changes if self.respond_to?(:save_changes)
270
+ end
271
+ result || self.properties.dup[key]
272
+ end
273
+ # rubocop: enable Metrics/AbcSize
274
+ # rubocop: enable Metrics/CyclomaticComplexity
275
+ # rubocop: enable Metrics/MethodLength
276
+ # rubocop: enable Metrics/PerceivedComplexity
277
+
278
+ # rubocop: disable Metrics/AbcSize
279
+ # rubocop: disable Metrics/CyclomaticComplexity
280
+ # rubocop: disable Metrics/MethodLength
281
+ # rubocop: disable Metrics/PerceivedComplexity
282
+ def _assign(key, value)
283
+ raise "The key parameter must be a symbol" unless key.is_a? Symbol
284
+ result = nil
285
+ if value.object?
286
+ result = self._set_object(key, value) if self.respond_to?(:_set_object)
287
+ elsif value.nil? && self.respond_to?(:_key?) && self._key?(key)
288
+ result = self._unset_object(key) if self.respond_to?(:_unset_object)
289
+ elsif value.nil? && self.properties.key?(key)
290
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
291
+ self.properties.delete(key)
292
+ self.save_changes if self.respond_to?(:save_changes)
293
+ else
294
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
295
+ self.properties[key] = value
296
+ self.save_changes if self.respond_to?(:save_changes)
297
+ end
298
+ result || self.properties.dup[key]
299
+ end
300
+ # rubocop: enable Metrics/AbcSize
301
+ # rubocop: enable Metrics/CyclomaticComplexity
302
+ # rubocop: enable Metrics/MethodLength
303
+ # rubocop: enable Metrics/PerceivedComplexity
304
+ end
305
+ # module Prototypical
306
+ end
307
+ # module Inform
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # Copyright Nels Nelson 2008-2023 but freely usable (see license)
5
+ #
6
+ # This file is part of the Inform Runtime.
7
+ #
8
+ # The Inform Runtime is free software: you can redistribute it and/or
9
+ # modify it under the terms of the GNU General Public License as published
10
+ # by the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # The Inform Runtime is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ require_relative 'subscription'
22
+
23
+ # The Inform module
24
+ module Inform
25
+ # The Publisher module to implement rudimentary pub-sub functionality
26
+ module Publisher
27
+ include Inform::Subscribers
28
+
29
+ def muted?
30
+ false
31
+ end
32
+
33
+ # rubocop: disable Metrics/AbcSize
34
+ # rubocop: disable Metrics/CyclomaticComplexity
35
+ # rubocop: disable Metrics/PerceivedComplexity
36
+ def subscribers
37
+ safe_refresh if respond_to?(:safe_refresh)
38
+ root_obj = attempt_twice { root } if self.respond_to?(:root) # TODO: FIXME
39
+ return explicit_subscribers.dup if root_obj.nil?
40
+ eavesdroppers = (root_obj&.safe_refresh&.branch || []) - [self]
41
+ # explicit_subscribers + (eavesdroppers & Session.players) # TODO: FIXME
42
+ players = defined?(Session) ? Session.players.dup : []
43
+ explicit_subscribers + eavesdroppers.each_with_object([]) do |o1, memo|
44
+ memo.concat(players.select { |o2| o1 == o2 })
45
+ end
46
+ end
47
+ # rubocop: enable Metrics/AbcSize
48
+ # rubocop: enable Metrics/CyclomaticComplexity
49
+ # rubocop: enable Metrics/PerceivedComplexity
50
+
51
+ def publish_only(message, *args, &condition)
52
+ return if muted?
53
+ (subscribers & args.flatten).each do |subscriber|
54
+ next if block_given? && !condition.call(subscriber)
55
+ subscriber.update(message)
56
+ end
57
+ false
58
+ end
59
+
60
+ def publish_except(message, *args, &condition)
61
+ return if muted?
62
+ (subscribers - args.flatten).each do |subscriber|
63
+ next if block_given? && !condition.call(subscriber)
64
+ log.warn "publishing message (#{message}) to subscriber: #{subscriber}"
65
+ subscriber.update(message)
66
+ end
67
+ false
68
+ end
69
+
70
+ # For system messages (to the InformLibrary, mainly)
71
+ def publish_system_message(message, *args, &condition)
72
+ subscribers.each do |subscriber|
73
+ next if block_given? && !condition.call(subscriber, *args)
74
+ subscriber.send(message, *args) if subscriber.respond_to?(message)
75
+ end
76
+ false
77
+ end
78
+
79
+ def publish(message, *args, &condition)
80
+ return publish_system_message(message, *args, &condition) if message.is_a?(Symbol)
81
+ return if muted?
82
+ subscribers.each do |subscriber|
83
+ next if block_given? && !condition.call(subscriber)
84
+ # log.info "#{subscriber.name}#update message: #{message.inspect}"
85
+ subscriber.update(message, *args)
86
+ end
87
+ false
88
+ end
89
+ end
90
+ # module Publisher
91
+ end
92
+ # module Inform