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,570 @@
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
+
22
+ JoinedTemplate = '%<str>s%<other>s'.freeze
23
+ ObjectIdentityTemplate = '#<%<klass>s:%<name>s:0x%<hex>x:%<id>d>'.freeze
24
+ ObjectIdentityAttributes = %i[name].freeze
25
+ AttributeTemplate = '@%<key>s=%<value>s'.freeze
26
+
27
+ # These are mixins to support Inform standard library built-ins and some
28
+ # so-called least-surprise idiomatic methods.
29
+ class Object
30
+ def +(other)
31
+ format(JoinedTemplate, str: self.to_s, other: other.to_s)
32
+ end
33
+
34
+ def identity
35
+ identification = { klass: self.class }
36
+ identifier = (self.respond_to?(:object?) && self.object? ? self.id : self.object_id) || 0
37
+ identification[:name] = (self.respond_to?(:name) ? self.name : nil) || 'unnamed'
38
+ identification[:hex] = identifier << 1
39
+ identification[:id] = identifier
40
+ format(ObjectIdentityTemplate, identification)
41
+ end
42
+
43
+ def number?
44
+ self.to_s.match?(/\A[+-]?\d+?(\.\d+)?\Z/)
45
+ end
46
+
47
+ def positive?
48
+ # TODO: This might be as simple as:
49
+ # self.to_i.positive?
50
+ self.to_s.match?(/\A+?\d+?(\.\d+)?\Z/)
51
+ end
52
+
53
+ def negative?
54
+ # TODO: This might be as simple as:
55
+ # self.to_i.negative?
56
+ self.to_s.match?(/\A-?\d+?(\.\d+)?\Z/)
57
+ end
58
+
59
+ def blank?
60
+ respond_to?(:empty?) ? empty? : self.nil?
61
+ end
62
+ end
63
+
64
+ # The NilClass class
65
+ class NilClass
66
+ def +(other)
67
+ case other
68
+ when String then format(JoinedTemplate, str: self.to_s, other: other.to_s)
69
+ when Integer then 0 + other
70
+ when Enumerable then [] + other
71
+ else format(JoinedTemplate, str: self.to_s, other: other.to_s)
72
+ end
73
+ end
74
+
75
+ def -(other)
76
+ case other
77
+ when String then self.to_s.delete other
78
+ when Integer then 0 - other
79
+ when Enumerable then [] - other
80
+ else self.to_s.delete other
81
+ end
82
+ end
83
+
84
+ def >(other, default = false)
85
+ other < 0
86
+ # return default unless other.respond_to? __method__
87
+ # 0.send(__method__, other)
88
+ rescue StandardError => _e
89
+ default
90
+ end
91
+
92
+ def >=(other, default = false)
93
+ other <= 0
94
+ # return default unless other.respond_to? __method__
95
+ # 0.send(__method__, other)
96
+ rescue StandardError => _e
97
+ default
98
+ end
99
+
100
+ def <(other, default = false)
101
+ other > 0
102
+ # return default unless other.respond_to? __method__
103
+ # 0.send(__method__, other)
104
+ rescue StandardError => _e
105
+ default
106
+ end
107
+
108
+ def <=(other, default = false)
109
+ other >= 0
110
+ # return default unless other.respond_to? __method__
111
+ # 0.send(__method__, other)
112
+ rescue StandardError => _e
113
+ default
114
+ end
115
+
116
+ # def & (other)
117
+ # 0 & other
118
+ # # return self unless other.respond_to? __method__
119
+ # # 0.send(__method__, other) rescue self
120
+ # end
121
+
122
+ # def | (other)
123
+ # 0 | other
124
+ # # return self unless other.respond_to? __method__
125
+ # # 0.send(__method__, other) rescue self
126
+ # end
127
+
128
+ # Incompatible with RubyUnits::Unit#to_base
129
+ # def *(other)
130
+ # 0 * other
131
+ # return self unless other.respond_to? __method__
132
+ # 0.send(__method__, other) rescue self
133
+ # end
134
+
135
+ def [](_key)
136
+ self
137
+ end
138
+
139
+ def []=(_key, _value)
140
+ self
141
+ end
142
+
143
+ def to_ary
144
+ []
145
+ end
146
+
147
+ def to_i
148
+ 0
149
+ end
150
+
151
+ def to_f
152
+ 0.0
153
+ end
154
+
155
+ def to_str
156
+ self.to_s
157
+ end
158
+
159
+ def to_sym
160
+ self
161
+ end
162
+
163
+ alias intern to_sym
164
+ def empty?
165
+ true
166
+ end
167
+
168
+ include Enumerable
169
+
170
+ def each(*_args, &_block)
171
+ return # Don't do anything, I'm nil
172
+ end
173
+
174
+ def join(s)
175
+ [].join(s)
176
+ end
177
+
178
+ def refresh
179
+ self
180
+ end
181
+ end
182
+ # class NilClass
183
+
184
+ # The TrueClass class
185
+ class TrueClass
186
+ def +(other)
187
+ format(JoinedTemplate, str: self.to_s, other: other.to_s)
188
+ end
189
+
190
+ def >(other)
191
+ other < 1
192
+ rescue StandardError => _e
193
+ false
194
+ end
195
+
196
+ def <(other)
197
+ other > 1
198
+ rescue StandardError => _e
199
+ false
200
+ end
201
+
202
+ def to_i
203
+ 1
204
+ end
205
+
206
+ def to_f
207
+ 1.0
208
+ end
209
+ end
210
+
211
+ # The FalseClass class
212
+ class FalseClass
213
+ def +(other)
214
+ format(JoinedTemplate, str: self.to_s, other: other.to_s)
215
+ end
216
+
217
+ def >(other)
218
+ other < 0
219
+ rescue StandardError => _e
220
+ false
221
+ end
222
+
223
+ def <(other)
224
+ other > 0
225
+ rescue StandardError => _e
226
+ false
227
+ end
228
+
229
+ # Don't do this, it will almost definitely break a lot of things.
230
+ # def ==(other)
231
+ # other.number? && other.to_i == 0
232
+ # rescue StandardError => _e
233
+ # false
234
+ # end
235
+
236
+ def to_i
237
+ 0
238
+ end
239
+
240
+ def to_f
241
+ 0.0
242
+ end
243
+ end
244
+
245
+ # The Numeric class
246
+ class Numeric
247
+ def commas(delimiter = ',')
248
+ parts = self.to_s.split('.')
249
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, format(JoinedTemplate, str: '\1', other: delimiter))
250
+ parts.join('.')
251
+ end
252
+ end
253
+
254
+ # The Integer class
255
+ class Integer
256
+ alias equals_operator ==
257
+ def ==(other)
258
+ case other
259
+ when NilClass, FalseClass then self == 0
260
+ when TrueClass then self == 1
261
+ else equals_operator(other)
262
+ end
263
+ end
264
+
265
+ alias lessthan_operator <
266
+ def <(other)
267
+ case other
268
+ when NilClass, FalseClass then self < 0
269
+ when TrueClass then self < 1
270
+ else lessthan_operator(other)
271
+ end
272
+ end
273
+
274
+ alias lessthanorequal_operator <=
275
+ def <=(other)
276
+ case other
277
+ when NilClass, FalseClass then self <= 0
278
+ when TrueClass then self <= 1
279
+ else lessthanorequal_operator(other)
280
+ end
281
+ end
282
+
283
+ alias greaterthan_operator >
284
+ def >(other)
285
+ case other
286
+ when NilClass, FalseClass then self > 0
287
+ when TrueClass then self > 1
288
+ else greaterthan_operator(other)
289
+ end
290
+ end
291
+
292
+ alias greaterthanorequal_operator >=
293
+ def >=(other)
294
+ case other
295
+ when NilClass, FalseClass then self >= 0
296
+ when TrueClass then self >= 1
297
+ else greaterthanorequal_operator(other)
298
+ end
299
+ end
300
+
301
+ alias plus_operator +
302
+ def +(other)
303
+ case other
304
+ when String then format(JoinedTemplate, str: self.to_s, other: other.to_s)
305
+ when NilClass, FalseClass then self + 0
306
+ when TrueClass then self + 1
307
+ else plus_operator(other)
308
+ end
309
+ end
310
+
311
+ def number?
312
+ true
313
+ end
314
+
315
+ # Some math constants
316
+ N_BYTES = [42].pack('i').size unless defined? N_BYTES
317
+ N_BITS = N_BYTES * 8 unless defined? N_BITS
318
+ MAX = (2**(N_BITS - 2)) - 1 unless defined? MAX
319
+ MIN = -MAX - 1 unless defined? MIN
320
+ end
321
+
322
+ # The String class
323
+ class String
324
+ def +(other)
325
+ format(JoinedTemplate, str: self, other: other)
326
+ end
327
+
328
+ # Replace the second of three capture groups with the given block.
329
+ def midsub(regexp, &_block)
330
+ self.gsub(regexp) { '\1' + yield('\2') + '\3' }
331
+ end
332
+
333
+ def sentence_case
334
+ self.capitalize
335
+ end
336
+
337
+ def poststrip
338
+ self.gsub(/\s+$/, '')
339
+ end
340
+
341
+ def poststrip!
342
+ self.gsub!(/\s+$/, '')
343
+ end
344
+
345
+ def prestrip
346
+ self.gsub(/^\s+/, '')
347
+ end
348
+
349
+ def prestrip!
350
+ self.gsub!(/^\s+/, '')
351
+ end
352
+
353
+ def to_n
354
+ /\./.match?(self) ? self.to_f : self.to_i
355
+ end
356
+
357
+ DefaultUnderscoreOptions = {
358
+ downcase: true
359
+ }.freeze
360
+
361
+ def underscore(options = {})
362
+ options = DefaultUnderscoreOptions.merge(options)
363
+ return snake_case if options[:downcase]
364
+ self
365
+ .gsub(/::/, '/')
366
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
367
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2')
368
+ .tr(' -', '_')
369
+ end
370
+
371
+ def snake_case
372
+ self
373
+ .gsub(/::/, '/')
374
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
375
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2')
376
+ .tr(' -', '_')
377
+ .downcase
378
+ end
379
+
380
+ def titleize
381
+ self.split(/[\W_]/).map(&:capitalize).join(' ')
382
+ end
383
+
384
+ def camelize(_s = nil)
385
+ self.downcase.split(/[^a-z0-9]/).inject('') do |str1, str2|
386
+ str2.length > 1 ? [str1, str2[0].chr.upcase, str2[1..]].join : str1.to_s
387
+ end
388
+ end
389
+
390
+ def dequote
391
+ self.gsub(/(\A['"]|['"]\Z)/, '')
392
+ end
393
+
394
+ def save(file)
395
+ File.write(file, self)
396
+ end
397
+
398
+ HumanUnits = { b: 'bytes', kb: 'kilobytes', mb: 'megabytes', gb: 'gigabytes', tb: 'terabytes' }.freeze
399
+ HumanUnitsRegex = /(#{HumanUnits.keys.join('|')})/i.freeze
400
+
401
+ def to_human
402
+ scalar, unit = self.split
403
+ unit.sub!(HumanUnitsRegex) { |s| HumanUnits[s.downcase.to_sym] }
404
+ [scalar.to_f.commas, unit].join(' ')
405
+ end
406
+ end
407
+ # class String
408
+
409
+ # The Symbol class
410
+ class Symbol
411
+ def +(other)
412
+ format(JoinedTemplate, str: self.to_s, other: other.to_s)
413
+ end
414
+
415
+ def to_n
416
+ to_s.to_n
417
+ end
418
+
419
+ def underscore(options = {})
420
+ to_s.underscore(options)
421
+ end
422
+
423
+ def snake_case
424
+ to_s.snake_case
425
+ end
426
+
427
+ def titleize
428
+ to_s.titleize
429
+ end
430
+
431
+ def camelize(_s = nil)
432
+ to_s.camelize
433
+ end
434
+
435
+ def dequote
436
+ to_s.dequote
437
+ end
438
+
439
+ def save(file)
440
+ to_s.save file
441
+ end
442
+ end
443
+
444
+ CommaSpaceString = ', '.freeze
445
+ ListCommaTemplate = '%<str>s, %<other>s %<last>s'.freeze
446
+ ListSansCommaTemplate = '%<str>s, %<other>s %<last>s'.freeze
447
+ SentenceTemplate = '%<str>s %<other>s %<last>s'.freeze
448
+
449
+ # The Array class
450
+ class Array
451
+ alias plus_operator +
452
+ def +(other)
453
+ case other
454
+ when String then self.to_s + other
455
+ else plus_operator(other)
456
+ end
457
+ end
458
+
459
+ def sum(&block)
460
+ return inject(0, &block) if block_given?
461
+ inject(:+)
462
+ end
463
+
464
+ def average
465
+ return self.first if self.length == 1
466
+ (self.sum / self.length.to_f)
467
+ end
468
+
469
+ def having(method)
470
+ select { |a| a.respond_to? method.to_sym }
471
+ end
472
+
473
+ def identities
474
+ map { |o| "#{o} [#{o.identity}]" }
475
+ end
476
+
477
+ def longest_length
478
+ inject(0) do |max, x|
479
+ [x.to_s.length, max].max
480
+ end
481
+ end
482
+
483
+ def pad(min = 1, max = longest_length + min)
484
+ each do |x|
485
+ yield(format(JoinedTemplate, str: x.to_s, other: ' ' * [max - x.to_s.length, min].max), x)
486
+ end
487
+ end
488
+
489
+ def to_sentence(conjunction = 'and')
490
+ case length
491
+ when 0 then ''
492
+ when 1 then self.first.dup.to_s
493
+ when 2
494
+ format(SentenceTemplate, str: self[0], other: conjunction, last: self[1])
495
+ else
496
+ format(SentenceTemplate, str: self[0...].join(CommaSpaceString), other: conjunction, last: self[-1])
497
+ end
498
+ end
499
+
500
+ alias ruby_to_s to_s
501
+ def to_s(separator = CommaSpaceString)
502
+ # This will make it possible to print Lists nicely, but not screw up
503
+ # the behavior of the List.
504
+ map(&:to_s).join(separator)
505
+ end
506
+
507
+ alias random sample
508
+ end
509
+ # class Array
510
+
511
+ # The EmptyImmutableEnumerable class
512
+ class EmptyImmutableEnumerable
513
+ def initialize(klass)
514
+ @subject = klass.new.freeze
515
+ end
516
+
517
+ def respond_to_missing?(method)
518
+ @subject.respond_to?(method)
519
+ end
520
+
521
+ def method_missing(method, *args, &block)
522
+ return @subject.send(method, *args, &block) if @subject.respond_to?(method)
523
+ super
524
+ rescue StandardError => _e
525
+ args.first
526
+ end
527
+ end
528
+
529
+ # The Array class
530
+ class Array
531
+ Empty = EmptyImmutableEnumerable.new(Array)
532
+ end
533
+
534
+ # The Hash class
535
+ class Hash
536
+ Empty = EmptyImmutableEnumerable.new(Hash)
537
+
538
+ def sort
539
+ keys.sort.each { |k| self[k] = self.delete(k) }
540
+ self
541
+ end
542
+
543
+ MethodBooleanPattern = %r{\?$}.freeze
544
+ MethodWriterPattern = %r{=$}.freeze
545
+
546
+ # TODO: Test
547
+ # def method_missing(method, *args, &block)
548
+ # m = method.to_s
549
+ # return false if MethodBooleanPattern.match?(m)
550
+ # mname = MethodWriterPattern.match?(m) ? m.chop : m
551
+ # property = mname.to_sym
552
+ # if MethodWriterPattern.match?(m)
553
+ # self[mname] = *args
554
+ # elsif self.key?(mname)
555
+ # self.fetch(mname, Hash::Empty)
556
+ # elsif self.key?(property)
557
+ # self.fetch(property, Hash::Empty)
558
+ # else
559
+ # super
560
+ # end
561
+ # end
562
+ end
563
+
564
+ # The Set class
565
+ class Set
566
+ alias ruby_to_s to_s
567
+ def to_s(separator = CommaSpaceString)
568
+ to_a.to_s(separator)
569
+ end
570
+ end