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,470 @@
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
+ # Re-open the Proc class to define #to_lambda
22
+ class Proc
23
+ def to_lambda
24
+ return self if lambda?
25
+
26
+ # Save local reference to self so we can use it in module_exec/lambda scopes
27
+ source_proc = self
28
+
29
+ # Convert proc to unbound method
30
+ unbound_method = Module.new.module_exec do
31
+ instance_method(define_method(:_proc_call, &source_proc))
32
+ end
33
+
34
+ # Return lambda which binds our unbound method to correct receiver and calls it with given args/block
35
+ lambda do |*args, &block|
36
+ # If binding doesn't changed (eg. lambda_obj.call) then bind method to original proc binding,
37
+ # otherwise bind to current binding (eg. instance_exec(&lambda_obj)).
38
+ unbound_method.bind(self == source_proc ? source_proc.receiver : self).call(*args, &block)
39
+ end
40
+ end
41
+
42
+ def receiver
43
+ binding.eval('self')
44
+ end
45
+ end
46
+
47
+ # The Inform module
48
+ module Inform
49
+ # The CommandCauseRecordNotFoundError class
50
+ class CommandCauseRecordNotFoundError < StandardError; end
51
+
52
+ # The Commands module
53
+ module Commands
54
+ end
55
+ end
56
+
57
+ # The PublicCommandMethods module
58
+ module PublicCommandMethods
59
+ def <<(event)
60
+ @successors.add event
61
+ event.antecedent = self
62
+ end
63
+
64
+ def origin(x = self)
65
+ x = origin x.antecedent if x.antecedent
66
+ x.object? ? x : x.cause
67
+ end
68
+
69
+ def termination(x = self)
70
+ event = x.successors.first
71
+ event || x.terminus
72
+ end
73
+
74
+ def elemental?
75
+ @type == :elemental
76
+ end
77
+
78
+ def integral?
79
+ @type == :integral
80
+ end
81
+
82
+ def immediately?
83
+ @when == :immediately
84
+ end
85
+
86
+ def cancelled?
87
+ return true if future.nil?
88
+ future.cancelled?
89
+ end
90
+
91
+ def antecedent_cancelled?
92
+ return false if self.antecedent.nil?
93
+ return false unless self.antecedent.cancelled?
94
+ return false if self.terminus.nil?
95
+ log.warn "Command antecedent #{self.antecedent} was cancelled"
96
+ true
97
+ end
98
+
99
+ def concluded?
100
+ semaphore.synchronize { concluded == true }
101
+ end
102
+
103
+ def now(params = {}, &block)
104
+ # TODO: Determine if :cause should be overwritten
105
+ # by any :cause params given by the params Hash
106
+ Inform::Command.new({ cause: cause, antecedent: self }.merge(params), &block)
107
+ end
108
+ alias add_event now
109
+ alias event now
110
+ alias and_then now
111
+ alias chain now
112
+ alias eventually now
113
+ alias later now
114
+
115
+ # Shortcut for using delays
116
+ def delay(delay, &block)
117
+ Inform::Command.new({ cause: cause, antecedent: self, delay: delay }, &block)
118
+ end
119
+ alias after_delay delay
120
+
121
+ def finally(&block)
122
+ Inform::Command.new({ cause: cause, antecedent: self, terminus: true }, &block)
123
+ end
124
+ alias on_cancel finally
125
+ alias when_completed finally
126
+ alias when_done finally
127
+ alias when_finished finally
128
+ alias when_over finally
129
+ alias ultimately finally
130
+
131
+ def parse_activity
132
+ self.cause.parse(self.activity)
133
+ end
134
+
135
+ def call_activity
136
+ return self.activity.call(self) if self.activity.arity == 1
137
+ self.activity.call(*self.args)
138
+ end
139
+
140
+ def call
141
+ process self
142
+ end
143
+
144
+ def run
145
+ call
146
+ end
147
+
148
+ def on_failure(_cause)
149
+ cancelled
150
+ end
151
+
152
+ def on_success(_result)
153
+ completed
154
+ end
155
+
156
+ def on_cancelled_antecedent
157
+ schedule self unless terminus.nil?
158
+ successors.each(&:on_cancelled_antecedent)
159
+ end
160
+
161
+ def to_s
162
+ self.identity
163
+ end
164
+
165
+ RecordNotFoundErrorPattern = %r{Record not found}.freeze
166
+
167
+ def mandate_cause_record_exists!
168
+ self.cause.refresh
169
+ rescue StandardError => e
170
+ if RecordNotFoundErrorPattern.match?(e.message)
171
+ message = e.message + ' for cause of event ' + self.to_s
172
+ raise Inform::CommandCauseRecordNotFoundError, message
173
+ end
174
+ log.warn "Unexpected error refreshing event cause object: #{e.message}"
175
+ end
176
+
177
+ AttributeFieldReferenceTemplate = '@%<attribute>s'.freeze
178
+
179
+ def get_value(from, attribute)
180
+ return from.send(attribute) if from.respond_to?(attribute)
181
+ from.instance_variable_get(format(AttributeFieldReferenceTemplate, attribute: attribute).to_sym)
182
+ end
183
+
184
+ def warn_when_nilling_noun(from, attribute, value)
185
+ return unless attribute == :noun
186
+ return unless value.nil?
187
+ return if (current_value = self.send(attribute)).nil?
188
+ return if attribute == :parameters && current_value <= 1
189
+ log.warn "Overwriting #{self}.#{attribute} (#{current_value}) with #{from}.#{attribute} (nil)!"
190
+ log.warn " #{self}.context: #{self.context.inspect}"
191
+ maybe_log_nilling_warning
192
+ end
193
+
194
+ def maybe_log_nilling_warning
195
+ return if callstack.grep('channel_read')
196
+ ['===========', (caller[-4...] + callstack), '==========='].each { |t| log.warn t unless t.nil? }
197
+ end
198
+ end
199
+ # module PublicCommandMethods
200
+
201
+ # The PrivateCommandMethods module
202
+ module PrivateCommandMethods
203
+ private
204
+
205
+ def to_java_future(future)
206
+ future.to_java(com.google.common.util.concurrent.ListenableFuture)
207
+ end
208
+
209
+ # TODO: Implement a mechanism by which new event scheduling may
210
+ # be deferred or discarded entirely. The reason for this is that
211
+ # during shutdown, there will certainly be ongoing events which
212
+ # require some form of state resolution. The spinning top, for
213
+ # instance -- once spun, is given the spinning attribute. Now,
214
+ # perhaps this is unnecessary, but the case remains that the state
215
+ # of the top must be returned to its resting state even though the
216
+ # game is being shutdown. Perhaps a special on_cancel hook could
217
+ # be provided for such events which require such state resolution.
218
+ # Otherwise, at this point, some way of deciding if incoming
219
+ # events are newly spawned, or if they are part of a chain of
220
+ # events which must be allowed to transpire for the sake of an
221
+ # object's state consistency.
222
+ # It occurs to me that this is what a message queue is for, so I
223
+ # need to figure out how to integrate with a message broker.
224
+ # rubocop: disable Metrics/AbcSize
225
+ def schedule(event, delay = 0)
226
+ raise "The event parameter may not be nil" if event.nil?
227
+ delay = event.time_delay.to_f * 1000 if event.time_delay > 0
228
+ event.future = Inform::Commands::Scheduler.schedule(event, delay, TimeUnit::MILLISECONDS)
229
+ Futures.addCallback(to_java_future(event.future), event, Inform::Commands::Scheduler)
230
+ event.cause.events << event
231
+ Inform::Commands::ActiveObjects << event.cause unless Inform::Commands::ActiveObjects.include?(event.cause)
232
+ event.future
233
+ end
234
+ # rubocop: enable Metrics/AbcSize
235
+
236
+ # rubocop: disable Metrics/AbcSize
237
+ # rubocop: disable Metrics/CyclomaticComplexity
238
+ # rubocop: disable Metrics/MethodLength
239
+ def process(event)
240
+ raise "The event parameter may not be nil" if event.nil?
241
+ Thread.current[:event] = event
242
+ event.mandate_cause_record_exists!
243
+ # TODO: This is probably racey so consider wrapping this in a semaphore.
244
+ return if event.antecedent_cancelled?
245
+ case event.activity
246
+ when String
247
+ result = event.parse_activity
248
+ # log.info "Parsed activity: #{self.activity.inspect}; result: #{result.inspect}"
249
+ # event.cause.inflib&.println(result)
250
+ event.cause.println(result)
251
+ when Proc
252
+ # event.cause.inflib&.println(event.call_activity, isolate: event.elemental?)
253
+ event.cause.println(event.call_activity, isolate: event.elemental?)
254
+ else
255
+ log.error "Command activity type is unknown: #{event.activity} (#{event.activity.class})"
256
+ end
257
+ rescue Inform::CommandCauseRecordNotFoundError => e
258
+ log.error "Error refreshing cause object: #{e.message}"
259
+ rescue LocalJumpError => e
260
+ # It is a shame that this happens. It used to not happen, so long
261
+ # as event.activity was wrapped in lambda(event.activity).
262
+ # Then one day, this exception began to be thrown again.
263
+ # TODO: Either use this kludgey hack to fix it:
264
+ # https://stackoverflow.com/questions/2946603/ruby-convert-proc-to-lambda
265
+ # https://stackoverflow.com/a/24965981/328469
266
+ # Or, try to get jruby fixed:
267
+ # https://github.com/jruby/jruby/issues/4369 (2016)
268
+ # https://github.com/jruby/jruby/pull/4423 (2017)
269
+ # https://github.com/jruby/jruby/issues/4577 (2017)
270
+ log.warn "Command block used return keyword: #{e.message}"
271
+ log.debug "Local jump error: #{e.message}"
272
+ e.backtrace.each { |t| log.warn t }
273
+ rescue SystemExit => e
274
+ raise e
275
+ rescue StandardError => e
276
+ log.error "Unexpected error processing #{event}: #{e.message}", e
277
+ event.callstack.each { |t| log.error t }
278
+ ensure
279
+ Thread.current[:event] = nil
280
+ end
281
+ # rubocop: enable Metrics/AbcSize
282
+ # rubocop: enable Metrics/CyclomaticComplexity
283
+ # rubocop: enable Metrics/MethodLength
284
+
285
+ # rubocop: disable Metrics/AbcSize
286
+ # rubocop: disable Metrics/MethodLength
287
+ def invoke(event)
288
+ raise "The event parameter may not be nil" if event.nil?
289
+ event.antecedent << event if !event.antecedent.nil? && !event.antecedent.concluded?
290
+ se = Thread.current[:event]
291
+ Thread.current[:event] = event
292
+ log.debug "Queueing #{event}"
293
+ event.future = Inform::Commands.pool.java_send(:submit, [java.lang.Runnable], event)
294
+ Futures.addCallback(to_java_future(event.future), event, Inform::Commands.pool)
295
+ event.cause.events << event
296
+ Inform::Commands.active_objects << event.cause unless Inform::Commands.active_objects.include?(event.cause)
297
+ event.future.get
298
+ event.future
299
+ rescue SystemExit => e
300
+ raise e
301
+ rescue Java::JavaUtilConcurrent::CancellationException => e
302
+ log.warn "Cancelled #{event}"
303
+ log.debug "Reason for event cancellation: #{e}"
304
+ rescue Java::JavaUtilConcurrent::ExecutionException => e
305
+ log.error "Command execution exception: #{e.message}"
306
+ rescue StandardError => e
307
+ log.error "Unexpected error invoking #{event}: #{e.message}", e
308
+ event.callstack.each { |t| log.error t }
309
+ ensure
310
+ Thread.current[:event] = se
311
+ end
312
+ # rubocop: enable Metrics/AbcSize
313
+ # rubocop: enable Metrics/MethodLength
314
+
315
+ def deactivate(obj)
316
+ raise "The obj parameter may not be nil" if obj.nil?
317
+ obj.events.remove self
318
+ Inform::Commands::ActiveObjects.remove obj if obj.events.empty?
319
+ end
320
+
321
+ def cancelled
322
+ successors.each(&:on_cancelled_antecedent)
323
+ rescue StandardError => e
324
+ log.error "Error cancelling #{self}: #{e.message}", e
325
+ ensure
326
+ deactivate cause
327
+ end
328
+
329
+ # rubocop: disable Metrics/AbcSize
330
+ # rubocop: disable Metrics/CyclomaticComplexity
331
+ def completed
332
+ semaphore.synchronize { self.concluded = true }
333
+ return if !antecedent.nil? && antecedent.cancelled? && terminus.nil?
334
+ return if cancelled? && terminus.nil?
335
+ successors.each { |event| schedule event }
336
+ rescue StandardError => e
337
+ log.error "Error completing #{self}: #{e.message}", e
338
+ ensure
339
+ deactivate cause
340
+ end
341
+ # rubocop: enable Metrics/AbcSize
342
+ # rubocop: enable Metrics/CyclomaticComplexity
343
+ end
344
+ # module PrivateCommandMethods
345
+
346
+ # The CommandInitializationMethods module
347
+ module CommandInitializationMethods
348
+ CommandNameTemplate = 'event:%<time>s'.freeze
349
+
350
+ # rubocop: disable Metrics/AbcSize
351
+ # rubocop: disable Metrics/MethodLength
352
+ # rubocop: disable Lint/LambdaWithoutLiteralBlock
353
+ def init_fields(params, &block)
354
+ @successors = defined?(Java) ? java.util.concurrent.CopyOnWriteArrayList.new : []
355
+ @time = Time.ms
356
+ @cause = params[:cause]
357
+ @name = params.fetch(:name, format(CommandNameTemplate, time: @time))
358
+ @args = params[:args]
359
+ # TODO: FIXME This causes an error:
360
+ # Command block used return keyword: unexpected return
361
+ @activity = params.fetch(:activity, block_given? ? lambda(&block) : @name)
362
+ # Converting a given block to a lambda supposedly enables the use of return
363
+ # statements inside the event activity blocks.
364
+ # TODO: Cite documentation source.
365
+ # TODO: Implement tests.
366
+ # @activity = params.fetch(:activity, block_given? ? block.to_lambda : @name)
367
+ @antecedent = params[:antecedent]
368
+ @time_delay = params.fetch(:delay, 0)
369
+ @terminus = params[:terminus] ? self : nil
370
+ @type = params.fetch(:type, :integral)
371
+ @when = params.fetch(:when, :eventually)
372
+ @callstack = caller.slice((%i[immediately elementally].include?(@when) ? 4 : 3)..-1)
373
+ @concluded = false
374
+ @cause = @cause.player if @cause.is_a? InformLibrary
375
+ @identity = generate_identity
376
+ @context = params[:context]
377
+ end
378
+ # rubocop: enable Metrics/AbcSize
379
+ # rubocop: enable Metrics/MethodLength
380
+ # rubocop: enable Lint/LambdaWithoutLiteralBlock
381
+
382
+ CommandCauseIdentityTemplate = '%<identity>s:%<name>s'.freeze
383
+ CommandSourceTemplate = ':%<source_location>s:%<source_line_number>s'.freeze
384
+ CommandSourceFullTemplate = ':%<method_name>s:%<source_location>s:%<source_line_number>s'.freeze
385
+ CommandActivityTemplate = ':%<activity>s'.freeze
386
+ CommandAntecedantTemplate = ' < %<antecedent>s'.freeze
387
+ CallerPattern = %r{([^:]+):([^:]+):in `([^']+)'}.freeze
388
+
389
+ # rubocop: disable Metrics/AbcSize
390
+ # rubocop: disable Metrics/MethodLength
391
+ def generate_identity
392
+ s = format(CommandCauseIdentityTemplate, identity: @cause.identity, name: @cause.name)
393
+ if !callstack.empty?
394
+ trace = @callstack.first.gsub(Inform::Runtime.project_dir_path, '')
395
+ location, line_number, method_name = CallerPattern.match(trace)&.captures
396
+ s << format(CommandSourceFullTemplate,
397
+ method_name: method_name, source_location: location, source_line_number: line_number)
398
+ elsif @activity.respond_to?(:source_location) || @activity.is_a?(Proc)
399
+ activity_source_location_enumerator = @activity.source_location.each
400
+ source_location = activity_source_location_enumerator.next.gsub(Inform::Runtime.project_dir_path, '')
401
+ source_line_number = activity_source_location_enumerator.next
402
+ s << format(CommandSourceTemplate, source_location: source_location, source_line_number: source_line_number)
403
+ elsif name != @activity.to_s
404
+ s << format(CommandActivityTemplate, activity: @activity)
405
+ end
406
+ s << format(CommandAntecedantTemplate, antecedent: @antecedent) unless @antecedent.nil?
407
+ s
408
+ end
409
+ # rubocop: enable Metrics/AbcSize
410
+ # rubocop: enable Metrics/MethodLength
411
+
412
+ def init_context(context, antecedent, cause)
413
+ return contextualize(context) unless context.nil?
414
+ if antecedent&.cause.respond_to?(:inflib)
415
+ return contextualize(antecedent.cause.inflib) unless antecedent&.cause&.inflib.nil?
416
+ end
417
+ contextualize(cause) unless cause.nil? # TODO: Maybe remove
418
+ end
419
+
420
+ def defer(event)
421
+ event.antecedent << event
422
+ end
423
+
424
+ def schedule_or_defer(event)
425
+ return defer(event) if !event.antecedent.nil? && !event.antecedent.concluded?
426
+ schedule(event) if event.antecedent.nil? || event.antecedent.cancelled?
427
+ end
428
+ end
429
+ # module CommandInitializationMethods
430
+
431
+ # The Inform module
432
+ module Inform
433
+ # The Inform::Command class
434
+ class Command
435
+ attr_accessor :future
436
+ attr_reader :time, :name, :args, :type, :context, :semaphore, :identity
437
+
438
+ # Note that block if given is converted to lambda. This enables
439
+ # the use of return statements inside the event activity blocks.
440
+ def initialize(params = {}, &block)
441
+ @semaphore = Mutex.new
442
+ init_fields(params, &block)
443
+ init_context(@context)
444
+ puts "Invoking #{self}"
445
+ invoke(self)
446
+ # @when == :immediately ? invoke(self) : schedule_or_defer(self)
447
+ end
448
+
449
+ def init_context(context)
450
+ return if context.nil?
451
+ contextualize(context)
452
+ end
453
+
454
+ def contextualize(from)
455
+ # log.info "Initializing context for command #{self} from #{from} (#{from.class})"
456
+ Inform::Context.get.members.each do |attribute|
457
+ value = get_value(from, attribute)
458
+ warn_when_nilling_noun(from, attribute, value)
459
+ setter_method = format('%<attribute>s=', attribute: attribute).to_sym
460
+ send(setter_method, value)
461
+ end
462
+ end
463
+
464
+ private
465
+
466
+ include CommandInitializationMethods
467
+ end
468
+ # class Command
469
+ end
470
+ # module Inform
@@ -0,0 +1,48 @@
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
+ RUNTIME_DIR_PATH = File.expand_path(__dir__) unless defined?(RUNTIME_DIR_PATH)
24
+ LIB_DIR_PATH = File.expand_path(File.dirname(RUNTIME_DIR_PATH)) unless defined?(LIB_DIR_PATH)
25
+ INFORM_DIR_PATH = 'inform'.freeze unless defined?(INFORM_DIR_PATH)
26
+ PROJECT_DIR_PATH = File.expand_path(File.dirname(LIB_DIR_PATH)) unless defined?(PROJECT_DIR_PATH)
27
+
28
+ # The Config class
29
+ class Config
30
+ DEFAULTS = {
31
+ environment: 'default',
32
+ admin: false,
33
+ language: 'English',
34
+ game_path: File.expand_path(File.join(PROJECT_DIR_PATH, 'game')),
35
+ game_dir_name: 'game',
36
+ game_grammar_module_name: 'grammar',
37
+ game_config_file_name: 'config.yml',
38
+ game_components: 'modules world models rules verbs languages', # This order is required
39
+ properties: 'player actor action action_to_be ' \
40
+ 'results parameters pattern ' \
41
+ 'noun second inp1 inp2 ' \
42
+ 'special_word special_number special_number1 special_number2 ' \
43
+ 'consult_words match_from wn verb_wordnum words ' \
44
+ 'parser_action parser_one parser_two scope_reason',
45
+ log_level: Logger::INFO
46
+ }.freeze
47
+ end
48
+ end
@@ -0,0 +1,78 @@
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
+ def self.context_class
24
+ Inform::Context.definition || Inform::Context.set(Inform::Runtime.invocation_context)
25
+ end
26
+
27
+ def self.context
28
+ context_class.new
29
+ end
30
+
31
+ # Define Context module
32
+ module Context
33
+ Registry = Struct.new(:memo).new(nil)
34
+ def definition
35
+ Inform::Context::Registry.memo
36
+ end
37
+ module_function :definition
38
+
39
+ def get
40
+ Inform::Context.definition
41
+ end
42
+ module_function :get
43
+
44
+ AttributeFieldReferenceTemplate = '@%<attribute>s'.freeze
45
+
46
+ def get_value(from, attribute)
47
+ return from.send(attribute) if from.respond_to?(attribute)
48
+ from.instance_variable_get(format(AttributeFieldReferenceTemplate, attribute: attribute).to_sym)
49
+ end
50
+ module_function :get_value
51
+
52
+ AttributeSetterMethodTemplate = '%<attribute>s='.freeze
53
+
54
+ def setter_method(attribute)
55
+ format(AttributeSetterMethodTemplate, attribute: attribute).to_sym
56
+ end
57
+ module_function :setter_method
58
+
59
+ def each_attribute(obj)
60
+ get&.members&.each do |attribute|
61
+ yield(attribute, get_value(obj, attribute))
62
+ end
63
+ end
64
+ module_function :each_attribute
65
+
66
+ def set(definition)
67
+ Inform::Context::Registry.memo = definition
68
+ get&.members&.each do |attribute|
69
+ Inform::Command.send(:attr_accessor, attribute) unless Inform::Command.respond_to? attribute
70
+ Inform::Event.send(:attr_accessor, attribute) unless Inform::Event.respond_to? attribute
71
+ end
72
+ definition
73
+ end
74
+ module_function :set
75
+ end
76
+ # module Context
77
+ end
78
+ # module Inform