inform-runtime 1.4.0 → 1.6.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.
@@ -243,6 +243,34 @@ class FalseClass
243
243
  end
244
244
  end
245
245
 
246
+ # module Inform
247
+ module Inform
248
+ # module English
249
+ module English
250
+ TensNumerals = {
251
+ 20 => 'twenty', 30 => 'thirty', 40 => 'forty', 50 => 'fifty',
252
+ 60 => 'sixty', 70 => 'seventy', 80 => 'eighty', 90 => 'ninety'
253
+ }.freeze
254
+ EnglishNumbersLanguage = {
255
+ 1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five',
256
+ 6 => 'six', 7 => 'seven', 8 => 'eight', 9 => 'nine', 10 => 'ten',
257
+ 11 => 'eleven', 12 => 'twelve', 13 => 'thirteen', 14 => 'fourteen', 15 => 'fifteen',
258
+ 16 => 'sixteen', 17 => 'seventeen', 18 => 'eighteen', 19 => 'nineteen', 20 => 'twenty'
259
+ }.freeze
260
+ OrdinalNumerals = {
261
+ 'zero' => 'zeroth', 'one' => 'first', 'two' => 'second', 'three' => 'third',
262
+ 'four' => 'fourth', 'five' => 'fifth', 'six' => 'sixth', 'seven' => 'seventh',
263
+ 'eight' => 'eighth', 'nine' => 'ninth', 'ten' => 'tenth', 'eleven' => 'eleventh',
264
+ 'twelve' => 'twelfth', 'thirteen' => 'thirteenth', 'fourteen' => 'fourteenth',
265
+ 'fifteen' => 'fifteenth', 'sixteen' => 'sixteenth', 'seventeen' => 'seventeenth',
266
+ 'eighteen' => 'eighteenth', 'nineteen' => 'nineteenth', 'twenty' => 'twentieth',
267
+ 'thirty' => 'thirtieth', 'forty' => 'fortieth', 'fifty' => 'fiftieth',
268
+ 'sixty' => 'sixtieth', 'seventy' => 'seventieth', 'eighty' => 'eightieth',
269
+ 'ninety' => 'ninetieth'
270
+ }.freeze
271
+ end
272
+ end
273
+
246
274
  # The Numeric class
247
275
  class Numeric
248
276
  def commas(delimiter = ',')
@@ -250,6 +278,69 @@ class Numeric
250
278
  parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, format(JoinedTemplate, str: '\1', other: delimiter))
251
279
  parts.join('.')
252
280
  end
281
+
282
+ def to_cardinal_numerals(language: Inform::English)
283
+ return self.to_s unless is_a?(Integer) && self >= 0
284
+ cardinal_integer(self, language)
285
+ end
286
+
287
+ def to_ordinal_numerals(language: Inform::English)
288
+ parts = decimal_numeral_parts
289
+ return self.to_s if parts.nil?
290
+
291
+ integer, fraction = parts
292
+ ordinal = ordinal_integer(integer, language)
293
+ return ordinal if fraction.nil?
294
+ "#{ordinal} point #{decimal_digits(fraction, language)}"
295
+ end
296
+
297
+ private
298
+
299
+ def decimal_numeral_parts
300
+ integer, fraction = self.to_s.split('.', 2)
301
+ return unless /\A[+-]?\d+\z/.match?(integer)
302
+ return unless fraction.nil? || /\A\d+\z/.match?(fraction)
303
+ [integer, fraction]
304
+ end
305
+
306
+ def cardinal_integer(value, language)
307
+ return 'zero' if value == 0
308
+ return language::EnglishNumbersLanguage.fetch(value) if value <= 20
309
+ return cardinal_under_hundred(value, language) if value < 100
310
+ return cardinal_under_thousand(value, language) if value < 1000
311
+ cardinal_thousands(value, language)
312
+ end
313
+
314
+ def ordinal_integer(integer, language)
315
+ ordinal = cardinal_integer(integer.delete_prefix('-').delete_prefix('+').to_i, language)
316
+ ordinal = ordinal.sub(/([a-z]+)\z/) { |word| language::OrdinalNumerals.fetch(word, "#{word}th") }
317
+ integer.start_with?('-') ? "minus #{ordinal}" : ordinal
318
+ end
319
+
320
+ def decimal_digits(fraction, language)
321
+ fraction.chars.map { |digit| cardinal_integer(digit.to_i, language) }.join(' ')
322
+ end
323
+
324
+ def cardinal_under_hundred(value, language)
325
+ tens, ones = value.divmod(10)
326
+ return language::TensNumerals.fetch(tens * 10) if ones == 0
327
+ "#{language::TensNumerals.fetch(tens * 10)}-#{cardinal_integer(ones, language)}"
328
+ end
329
+
330
+ def cardinal_under_thousand(value, language)
331
+ hundreds, remainder = value.divmod(100)
332
+ result = "#{cardinal_integer(hundreds, language)} hundred"
333
+ return result if remainder == 0
334
+ "#{result} and #{cardinal_integer(remainder, language)}"
335
+ end
336
+
337
+ def cardinal_thousands(value, language)
338
+ thousands, remainder = value.divmod(1000)
339
+ result = "#{cardinal_integer(thousands, language)} thousand"
340
+ return result if remainder == 0
341
+ separator = remainder < 100 ? ' and ' : ', '
342
+ "#{result}#{separator}#{cardinal_integer(remainder, language)}"
343
+ end
253
344
  end
254
345
 
255
346
  # The Integer class
@@ -493,6 +584,10 @@ class Array
493
584
  end
494
585
  end
495
586
 
587
+ def second
588
+ self[1]
589
+ end
590
+
496
591
  def to_sentence(conjunction = 'and')
497
592
  case length
498
593
  when 0 then ''
@@ -27,6 +27,12 @@ module Inform
27
27
  Thread.current[:event]
28
28
  end
29
29
 
30
+ def inflib
31
+ return @inflib unless @inflib.nil?
32
+
33
+ context_event&.cause
34
+ end
35
+
30
36
  def instance_variables_map
31
37
  instance_variables.each_with_object({}) do |key, memo|
32
38
  memo[key] = instance_variable_get(key)
@@ -134,8 +140,8 @@ module Inform
134
140
  frame = [object_id, method]
135
141
 
136
142
  if stack.include?(frame)
137
- raise NoMethodError, "Aborting Prototypical missing method " \
138
- "resolution #{self.class}##{method} on #{inspect}"
143
+ raise NoMethodError, "Aborting Prototypical missing method resolution " +
144
+ "#{self.class}##{method} on #{inspect}"
139
145
  end
140
146
 
141
147
  stack << frame
@@ -366,18 +372,18 @@ module Inform
366
372
  elsif values.first.nil? && self.respond_to?(:_key?) && self._key?(key)
367
373
  result = self._unset_object(key) if self.respond_to?(:_unset_object)
368
374
  elsif values.first.nil?
369
- self.will_change_column(:properties) if self.respond_to?(:will_change_column)
370
375
  self.properties.delete(property_key_for(key))
376
+ self.modified!(:properties) if self.respond_to?(:modified!)
371
377
  self.save_changes if self.respond_to?(:save_changes)
372
378
  elsif values.length > 1
373
- self.will_change_column(:properties) if self.respond_to?(:will_change_column)
374
379
  self.properties[key] = []
375
380
  self.properties[key].concat(values)
376
381
  values.clear
382
+ self.modified!(:properties) if self.respond_to?(:modified!)
377
383
  self.save_changes if self.respond_to?(:save_changes)
378
384
  else
379
- self.will_change_column(:properties) if self.respond_to?(:will_change_column)
380
385
  self.properties[key] = values.first
386
+ self.modified!(:properties) if self.respond_to?(:modified!)
381
387
  self.save_changes if self.respond_to?(:save_changes)
382
388
  end
383
389
  result || self.properties.dup[property_key_for(key)]
@@ -181,10 +181,11 @@ class Session
181
181
  include SessionManagementMethods
182
182
  end
183
183
  attr_accessor :channel, :machine, :player, :state, :status
184
- attr_reader :last_good_state, :last_activity, :previous, :inbound, :outbound, :settings
184
+ attr_reader :last_good_state, :last_activity, :previous, :inbound, :outbound, :settings, :output
185
185
 
186
186
  # These states accept any input, including no input
187
187
  Promiscuous = Set.new
188
+ class BadStateError < StandardError; end
188
189
 
189
190
  # rubocop: disable Metrics/MethodLength
190
191
  def initialize(channel = nil, machine: nil, player: nil, state: :playing, settings: {})
@@ -197,7 +198,7 @@ class Session
197
198
  @settings = settings
198
199
  @status = nil
199
200
  @last_activity = Time.now
200
- @outbound = OutputBuffer.new
201
+ self.output = OutputBuffer.new
201
202
 
202
203
  self.class.register(channel, self) unless channel.nil?
203
204
  control(player) unless player.nil?
@@ -206,8 +207,13 @@ class Session
206
207
 
207
208
  alias preferences settings
208
209
 
210
+ def output=(sink)
211
+ @output = sink
212
+ @outbound = sink
213
+ end
214
+
209
215
  def write_output(value)
210
- @outbound.write(value)
216
+ @output.write(value)
211
217
  end
212
218
 
213
219
  def drain_output
@@ -221,7 +227,6 @@ class Session
221
227
  columns = columns.to_i
222
228
  return output if columns <= 0
223
229
 
224
- # output = output.gsub(/^\n+/, "\n")
225
230
  output.each_line.map do |line|
226
231
  newline = line.end_with?("\n") ? "\n" : ''
227
232
  wrap_line(line.chomp, columns) + newline
@@ -268,14 +273,6 @@ class Session
268
273
  @outbound.reset
269
274
  end
270
275
 
271
- def init_channel_session(channel)
272
- Session[channel] = self
273
- @channel = channel
274
- @inbound = Inbound.new(self) if defined?(Inbound)
275
- @outbound = OutputBuffer.new
276
- @last_activity = Time.now
277
- end
278
-
279
276
  def control(player)
280
277
  self.class.release_player(@player, self)
281
278
  @player = player
@@ -336,8 +333,11 @@ class Session
336
333
  drain_output
337
334
  end
338
335
 
339
- def safely_progress(machine = self)
340
- @state = machine.public_send(@state)
336
+ # def safely_progress(machine = self, state = machine.public_send(@state))
337
+ def safely_progress(machine = self, state = nil)
338
+ state ||= machine.public_send(@state)
339
+ raise BadStateError, "Bad state: #{state}" unless state.is_a? Symbol
340
+ @state = state
341
341
  raise 'Bad state machine implementation: nil state' if @state.nil?
342
342
  rescue StandardError => e
343
343
  log.error "Error updating state: #{e.message}", e
@@ -21,6 +21,8 @@
21
21
 
22
22
  require_relative 'version'
23
23
 
24
+ VN_1610 = '1610'.freeze
25
+
24
26
  # The Inform module
25
27
  module Inform
26
28
  # Support WriteListFrom implementations that prefer to skip animate objects
@@ -114,13 +116,13 @@ module StdLib
114
116
  end
115
117
 
116
118
  def location
117
- @player.location
119
+ @location
118
120
  end
119
121
 
120
122
  def location=(o)
121
123
  log.warn "Trying to set the location directly"
122
124
  log.warn caller[1] and abort
123
- @player.location = o
125
+ @location = @player.location = o
124
126
  end
125
127
 
126
128
  def area
@@ -145,10 +147,6 @@ module StdLib
145
147
  false
146
148
  end
147
149
 
148
- def DisplayStatus
149
- false
150
- end
151
-
152
150
  def Message(*args)
153
151
  println(args[:fatalerror] || args.values.first)
154
152
  end
@@ -187,6 +185,7 @@ module StdLib
187
185
  ctx.results[2],
188
186
  ctx.results[3]
189
187
  ]
188
+ ctx.player = player
190
189
  ctx.actor = player
191
190
  ctx.special_number1 = ctx.special_number2 = nil
192
191
  ctx.special_number1 = ctx.results[2] if ctx.results[2].number?
@@ -202,50 +201,80 @@ module StdLib
202
201
  # rubocop: enable Metrics/AbcSize
203
202
  # rubocop: enable Metrics/MethodLength
204
203
 
205
- # The invoke method executes without prompting afterward
204
+ # The perform_action method causes the given action to be executed
205
+ # while intending that the original action routine resumes
206
+ # execution where perform_action was called.
207
+ #
208
+ # The perform_action method executes without prompting afterward
206
209
  # but appends the output to the contextual event's buffer
207
210
  # which will be displayed upon completion of the contextual
208
211
  # event.
209
212
  #
213
+ # See: https://inform-fiction.org/manual/html/s6.html
214
+ #
210
215
  # TODO: Test: Does this mean that invoke must always be executed
211
216
  # within an event context to have its output displayed?
212
217
  # !rubocop: disable Metrics/AbcSize
213
- def invoke(action, *args)
218
+ def perform_action(action, *args)
214
219
  raise "The action parameter may not be nil" if action.nil?
215
220
 
216
221
  # log.debug 'Invocation: ' + name(@player) + ' <' + action + (args.empty? ? '' : ' ') + args.to_s + '>'
217
222
  ctx = invocation_context(action, *args)
223
+
224
+ Inform::Context.get.members.each do |attr|
225
+ instance_variable_set("@#{attr}", ctx.send(attr))
226
+ end
227
+
218
228
  immediately(ctx) do
219
- if BeforeRoutines(action) == false
220
- send(VerbRoutine(action))
221
- end
229
+ # elementally(ctx) do
230
+ R_Process(action, args.first, args.second)
222
231
  end
223
232
  end
224
233
  # !rubocop: enable Metrics/AbcSize
225
-
226
- # Under invoke execute a routine but tries to prevent
227
- # any output at all and does not provide other objects
228
- # the opportunity to react to the action, like invoke
229
- # does, above.
234
+ alias invoke perform_action
235
+
236
+ # Divert from the current action to the given action, and
237
+ # return true. The invoking routine method is also expected
238
+ # to also immediately return true, to maintain fidelity to
239
+ # the original Inform 6 functionality. (Unfortunately, there
240
+ # is no way to implement this in Ruby, so every place where
241
+ # divert_to_action is called will have to return on its own.)
230
242
  #
231
243
  # This should be used to cause an action outcome during
232
244
  # the execution of another Verb action subroutine. It
233
245
  # should appear as if the invoked action is part of the
234
246
  # original action.
247
+ #
248
+ # The Inform 6 designer's manual explains using the example of
249
+ # a "steps" object which defines a before Search rule, so that
250
+ # the statement <<Enter self>> will generate the action Enter
251
+ # steps instead, and return true to stop the original Search
252
+ # action from going any further. The net effect is that one
253
+ # action has been diverted into another.
254
+ #
255
+ # See: https://inform-fiction.org/manual/html/s6.html
256
+ #
235
257
  # !rubocop: disable Metrics/AbcSize
236
- def _invoke(action, *args)
258
+ def divert_to_action(action, *args)
237
259
  raise "The action parameter may not be nil" if action.nil?
238
260
 
239
- # log.debug 'Invocation: ' + name(@player) + ' <<' + action + (args.empty? ? '' : ' ') + args.to_s + '>>'
261
+ # log.debug 'Diverting to action: ' + name(@player) + ' <<' + action + (args.empty? ? '' : ' ') + args.to_s + '>>'
262
+ perform_action(action, *args)
263
+ true
264
+ end
265
+ alias _invoke divert_to_action
266
+ # !rubocop: enable Metrics/AbcSize
267
+
268
+ # TODO: Testme (2026-09-13)
269
+ # It's unclear if this method is needed, but if it is, it is
270
+ # almost certainly only required within a multiplayer realtime
271
+ # context.
272
+ def proxy(action, *args)
240
273
  ctx = invocation_context(action, *args)
241
- elementally(ctx) do
242
- silently do
243
- send(VerbRoutine(action))
244
- end
274
+ immediately(ctx) do
275
+ perform_action(action, *args)
245
276
  end
246
277
  end
247
- # !rubocop: enable Metrics/AbcSize
248
- alias silently_invoke _invoke
249
278
 
250
279
  # rubocop: disable Metrics/AbcSize
251
280
  # rubocop: disable Metrics/CyclomaticComplexity
@@ -321,7 +350,7 @@ module StdLib
321
350
  return false unless ReactionHandlers.include?(prop)
322
351
  # TODO: Only apply Behavior modules if object is not being controlled
323
352
  # by a player.
324
- obj.apply_modules if obj.respond_to?(:apply_modules)
353
+ obj.apply_modules! if obj.respond_to?(:apply_modules!)
325
354
  si = obj.inflib
326
355
  obj.inflib = self
327
356
 
@@ -452,6 +481,16 @@ module StdLib
452
481
  # rubocop: enable Metrics/CyclomaticComplexity
453
482
  # rubocop: enable Metrics/PerceivedComplexity
454
483
  # rubocop: enable Style/ReturnNilInPredicateMethodDefinition
484
+
485
+ def parser_error(e, message)
486
+ log.error "Error parsing user command: #{e.class}: #{e}"
487
+ e.backtrace.each { |t| log.error t }
488
+ log.error " parser: #{self.identity}"
489
+ log.error " input: #{@input}"
490
+ log.error " actor: #{@actor}"
491
+ log.error " action: #{@action}"
492
+ message
493
+ end
455
494
  end
456
495
  # module StdLib
457
496
 
@@ -651,7 +690,7 @@ module Rules
651
690
 
652
691
  # rubocop: disable Metrics/MethodLength
653
692
  def yesorno?
654
- reset_io # TODO: Remove this. Why is the buffer getting a newline character added to it?
693
+ # reset_io # TODO: Remove this. Why is the buffer getting a newline character added to it?
655
694
  case @buffer.split.first
656
695
  when /^(y|yes)$/i
657
696
  println(resume)
@@ -23,12 +23,11 @@
23
23
  module StoryTeller
24
24
  # module Engine
25
25
  module Engine
26
- VERSION = '1.4.0'.freeze
26
+ VERSION = '1.6.0'.freeze
27
27
  end
28
28
  end
29
29
 
30
30
  # module Inform
31
31
  module Inform
32
32
  VERSION = StoryTeller::Engine::VERSION
33
- VN_1610 = '1610'.freeze
34
33
  end
@@ -21,7 +21,6 @@
21
21
 
22
22
  require_relative 'inform/models'
23
23
  require_relative 'tree'
24
- require_relative 'daemon'
25
24
 
26
25
  # module Inform
27
26
  module Inform
@@ -34,7 +33,6 @@ module Inform
34
33
  include StoryTeller::Builtins
35
34
  include StoryTeller::IO if defined?(StoryTeller::IO)
36
35
  include StoryTeller::Publisher if defined?(StoryTeller::Publisher)
37
- include StoryTeller::Daemons if defined?(StoryTeller::Daemons)
38
36
  include Inform::Context if defined?(Inform::Context)
39
37
  include Inform::Events if defined?(Inform::Events)
40
38
  include Inform::Prototypical if defined?(Inform::Prototypical)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inform-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nels Nelson
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.1.2
46
+ version: 0.1.4
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 0.1.2
53
+ version: 0.1.4
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: logger
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -95,7 +95,6 @@ files:
95
95
  - lib/story_teller/color.rb
96
96
  - lib/story_teller/command.rb
97
97
  - lib/story_teller/context.rb
98
- - lib/story_teller/daemon.rb
99
98
  - lib/story_teller/engine.rb
100
99
  - lib/story_teller/events.rb
101
100
  - lib/story_teller/experimental/handler_dsl.rb
@@ -112,7 +111,6 @@ files:
112
111
  - lib/story_teller/io.rb
113
112
  - lib/story_teller/kernel.rb
114
113
  - lib/story_teller/library.rb
115
- - lib/story_teller/library/bootstrap.rb
116
114
  - lib/story_teller/library/declarations.rb
117
115
  - lib/story_teller/library/directives.rb
118
116
  - lib/story_teller/library/loader.rb
@@ -150,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
148
  - !ruby/object:Gem::Version
151
149
  version: '0'
152
150
  requirements: []
153
- rubygems_version: 4.0.11
151
+ rubygems_version: 4.0.17
154
152
  specification_version: 4
155
153
  summary: StoryTeller Engine for Ruby.
156
154
  test_files: []