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.
- checksums.yaml +7 -0
- data/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- 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
|