Module: Inform::StdLib

Defined in:
lib/runtime/stdlib.rb,
lib/runtime/stdlib.rb,
lib/runtime/stdlib.rb

Overview

The StdLib module to re-define the InScope method rubocop: disable Metrics/AbcSize rubocop: disable Lint/DuplicateMethods

Constant Summary collapse

DotPattern =
%r{\.}.freeze
UnderscoreString =
'_'.freeze
DefaultXMLAssociations =
%i[tagged modularized].freeze
ParseRoutineString =

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/CyclomaticComplexity rubocop: enable Metrics/PerceivedComplexity

'_parse_routine'.freeze
SubString =
'Sub'.freeze
DefaultVerbRoutineOptions =
{ fail_on_error: true }.freeze
PrimaryHandlers =

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/MethodLength rubocop: enable Metrics/CyclomaticComplexity rubocop: enable Metrics/PerceivedComplexity

%i[before after life].freeze
SynchronousHandlers =
PrimaryHandlers + %i[react_before].freeze
ReactionHandlers =
SynchronousHandlers + %i[react_after].freeze

Instance Method Summary collapse

Instance Method Details

#_invoke(action, *args) ⇒ Object Also known as: silently_invoke

Under invoke execute a routine but tries to prevent any output at all and does not provide other objects the opportunity to react to the action, like invoke does, above.

This should be used to delegate an action outcome to another Verb action subroutine. It should appear as if the invoked action is part of the original action. !rubocop: disable Metrics/AbcSize



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/runtime/stdlib.rb', line 222

def _invoke(action, *args)
  raise "The action parameter may not be nil" if action.nil?

  # log.debug 'Invocation: ' + name(@player) + ' <<' + action + (args.empty? ? '' : ' ') + args.to_s + '>>'
  ctx = invocation_context(action, *args)
  elementally(ctx) do
    silently do
      send(VerbRoutine(action))
    end
  end
end

#ADirectionObject

Stubs

Designers may override these.



121
122
123
# File 'lib/runtime/stdlib.rb', line 121

def ADirection(*)
  compass.include?(noun)
end

#AfterLifeObject



452
453
454
# File 'lib/runtime/stdlib.rb', line 452

def AfterLife(*)
  false
end

#AfterPromptObject



456
457
458
# File 'lib/runtime/stdlib.rb', line 456

def AfterPrompt(*)
  false
end

#AmusingObject



460
461
462
# File 'lib/runtime/stdlib.rb', line 460

def Amusing(*)
  false
end

#areaObject



113
114
115
# File 'lib/runtime/stdlib.rb', line 113

def area
  location&.area
end

#BeforeParsingObject



464
465
466
# File 'lib/runtime/stdlib.rb', line 464

def BeforeParsing(*)
  false
end

#BeginActivityObject



143
144
145
# File 'lib/runtime/stdlib.rb', line 143

def BeginActivity(*)
  false
end

#ChooseObjects(_object, _code) ⇒ Object



468
469
470
# File 'lib/runtime/stdlib.rb', line 468

def ChooseObjects(_object, _code)
  2
end

#consume_remaining_textObject



88
89
90
91
92
93
# File 'lib/runtime/stdlib.rb', line 88

def consume_remaining_text
  s = preceding_text
  @wn = num_words
  remaining_text = @input[s.length..]
  remaining_text ? remaining_text.strip : ''
end

#DarkToDarkObject



472
473
474
# File 'lib/runtime/stdlib.rb', line 472

def DarkToDark(*)
  false
end

#DeathMessageObject



476
477
478
# File 'lib/runtime/stdlib.rb', line 476

def DeathMessage(*)
  false
end

#debug(n = nil) ⇒ Object



25
26
27
28
29
30
31
32
# File 'lib/runtime/stdlib.rb', line 25

def debug(n = nil)
  if n.nil?
    toggle_constant :DEBUG
  else
    @parser_trace = n
    reset_constant :DEBUG, true
  end
end

#DisplayStatusObject



135
136
137
# File 'lib/runtime/stdlib.rb', line 135

def DisplayStatus
  false
end

#EndActivityObject



147
148
149
# File 'lib/runtime/stdlib.rb', line 147

def EndActivity(*)
  false
end

#EpilogueObject



480
481
482
# File 'lib/runtime/stdlib.rb', line 480

def Epilogue(*)
  false
end

#executableObject



34
35
36
# File 'lib/runtime/stdlib.rb', line 34

def executable
  Runtime.main_gem_spec_executable
end

#export(o, type = :xml) ⇒ Object



54
55
56
# File 'lib/runtime/stdlib.rb', line 54

def export(o, type = :xml)
  o.send('export_' + type) if !o.nil? && o.respond_to?(('export_' + type).to_sym)
end

#flush_automaticallyObject



50
51
52
# File 'lib/runtime/stdlib.rb', line 50

def flush_automatically
  toggle_constant :FLUSH_IO_INSTANTLY
end

#ForActivityObject



151
152
153
# File 'lib/runtime/stdlib.rb', line 151

def ForActivity(*)
  false
end

#FullScoreSubObject



131
132
133
# File 'lib/runtime/stdlib.rb', line 131

def FullScoreSub
  false
end

#GamePostRoutineObject



484
485
486
# File 'lib/runtime/stdlib.rb', line 484

def GamePostRoutine(*)
  false
end

#GamePreRoutineObject



488
489
490
# File 'lib/runtime/stdlib.rb', line 488

def GamePreRoutine(*)
  false
end

#import_object(file) ⇒ Object



61
62
63
64
65
# File 'lib/runtime/stdlib.rb', line 61

def import_object(file)
  type = File.extname(file)
  return if type.nil?
  send('import_object_' + type.gsub(DotPattern, UnderscoreString), file)
end

#import_object_json(file) ⇒ Object



82
83
84
85
86
# File 'lib/runtime/stdlib.rb', line 82

def import_object_json(file)
  return unless defined? JSON
  return if file.nil?
  JSON.parse(File.read(file))
end

#import_object_xml(file, associations = DefaultXMLAssociations) ⇒ Object



69
70
71
72
73
# File 'lib/runtime/stdlib.rb', line 69

def import_object_xml(file, associations = DefaultXMLAssociations)
  return unless defined? Nokogiri
  return if file.nil?
  Inform::Object.from_xml(File.open(file), associations: associations)
end

#import_object_yaml(file) ⇒ Object Also known as: import_object_yml



75
76
77
78
79
# File 'lib/runtime/stdlib.rb', line 75

def import_object_yaml(file)
  return unless defined? YAML
  return if file.nil?
  YAML.safe_load(File.read(file))
end

#indirect(action, *args) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/runtime/stdlib.rb', line 239

def indirect(action, *args)
  return false if action.nil?
  # raise "The action parameter may not be nil" if action.nil?
  action = action.to_sym
  subroutine = VerbRoutine(action, fail_on_error: false)
  contextualize # TODO: Test removal
  # Direct objects might need library built-ins
  # noun.inflib = actor.inflib if (!noun.nil?) && noun.object? && noun.inflib.nil?
  # second.inflib = actor.inflib if (!second.nil?) && second.object? && second.inflib.nil?
  @noun.inflib = self if !@noun.nil? && @noun.object? && @noun.inflib.nil?
  @second.inflib = self if !@second.nil? && @second.object? && @second.inflib.nil?
  return send(subroutine, *args) if respond_to? subroutine
  return send(action, *args) if respond_to? action
  false
end

#InScope(obj, scope = []) ⇒ Object


(From the Inform Designer's Manual)
§32 Scope and what you can see

"In scope" roughly means "the compass directions, what you're carrying
and what you can see". It exactly means this:

1. the compass directions;
2. the player's immediate possessions;
3. if there is light, then the contents of the player's visibility
   ceiling (see §21 for definition, but roughly speaking the
   outermost object containing the player which remains visible,
   which is usually the player's location);
4. if there is darkness, then the contents of the library's object
   thedark (by default there are no such contents, but some
   designers have been known to move objects into thedark: see
   'Ruins');
5. if the player is inside a container, then that container;
6. if O is in scope and is see-through (see §21), then the contents of O;
7. if O is in scope, then any object which it "adds to scope".



583
584
585
# File 'lib/runtime/stdlib.rb', line 583

def InScope(*)
  true
end

#inversionObject



38
39
40
# File 'lib/runtime/stdlib.rb', line 38

def inversion
  print Inform::VERSION
end

#invocation_context(action, *args) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/runtime/stdlib.rb', line 164

def invocation_context(action, *args)
  ctx = Inform.context
  ctx.results = [
    ctx.action = ctx.action_to_be = action,
    ctx.parameters = args.length,
    ctx.noun = ctx.inp1 = args.shift,
    ctx.second = ctx.inp2 = args.shift
  ]
  ctx.pattern = [
    ctx.results[0].to_s.downcase,
    ctx.results[2],
    ctx.results[3]
  ]
  ctx.actor = player
  ctx.special_number1 = ctx.special_number2 = nil
  ctx.special_number1 = ctx.results[2] if ctx.results[2].number?
  ctx.special_number2 = ctx.results[3] if ctx.results[3].number?
  ctx.special_word = args.shift
  ctx.consult_words = args.shift
  ctx.consult_words = ctx.special_word = ctx.results[2] if ctx.results[2].is_a?(String)
  ctx.consult_words = ctx.special_word = ctx.results[3] if ctx.results[3].is_a?(String)
  ctx.match_from = ctx.wn = ctx.verb_wordnum = 0
  ctx.words = []
  ctx
end

#invoke(action, *args) ⇒ Object

The invoke method executes without prompting afterward but appends the output to the contextual event’s buffer which will be displayed upon completion of the contextual event.

TODO: Test: Does this mean that invoke must always be executed within an event context to have its output displayed? !rubocop: disable Metrics/AbcSize



200
201
202
203
204
205
206
207
208
209
210
# File 'lib/runtime/stdlib.rb', line 200

def invoke(action, *args)
  raise "The action parameter may not be nil" if action.nil?

  # log.debug 'Invocation: ' + name(@player) + ' <' + action + (args.empty? ? '' : ' ') + args.to_s + '>'
  ctx = invocation_context(action, *args)
  immediately(ctx) do
    if BeforeRoutines(action) == false
      send(VerbRoutine(action))
    end
  end
end

#locationObject



103
104
105
# File 'lib/runtime/stdlib.rb', line 103

def location
  @player.location
end

#location=(o) ⇒ Object



107
108
109
110
111
# File 'lib/runtime/stdlib.rb', line 107

def location=(o)
  log.warn "Trying to set the location directly"
  log.warn caller[1] and abort
  @player.location = o
end

#LookRoutineObject



496
497
498
# File 'lib/runtime/stdlib.rb', line 496

def LookRoutine(*)
  false
end

#Message(*args) ⇒ Object



139
140
141
# File 'lib/runtime/stdlib.rb', line 139

def Message(*args)
  println(args[:fatalerror] || args.values.first)
end

#NewRoomObject



500
501
502
# File 'lib/runtime/stdlib.rb', line 500

def NewRoom(*)
  false
end

#ObjectDoesNotFitObject



504
505
506
# File 'lib/runtime/stdlib.rb', line 504

def ObjectDoesNotFit(*)
  2
end

#ParseNoun(obj) ⇒ Object

rubocop: disable Lint/SelfAssignment rubocop: disable Lint/UselessAssignment



547
# File 'lib/runtime/stdlib.rb', line 547

def ParseNoun(obj); obj = obj; return -1; end

#ParseNumberObject



508
509
510
# File 'lib/runtime/stdlib.rb', line 508

def ParseNumber(*)
  2
end

#ParserErrorObject



512
513
514
# File 'lib/runtime/stdlib.rb', line 512

def ParserError(*)
  true
end

#pcountObject



99
100
101
# File 'lib/runtime/stdlib.rb', line 99

def pcount
  @pattern ? @pattern.length - 1 : 0
end

#preceding_textObject



95
96
97
# File 'lib/runtime/stdlib.rb', line 95

def preceding_text
  @words[0..(@wn - 1)].join(' ')
end

#PrintTaskNameObject



516
517
518
# File 'lib/runtime/stdlib.rb', line 516

def PrintTaskName(*)
  true
end

#PrintVerbObject



520
521
522
# File 'lib/runtime/stdlib.rb', line 520

def PrintVerb(*)
  true
end

#reacts_to?(obj, prop, *args) ⇒ Boolean

Hack for Ruby so we can do action-specific reaction handlers. With Inform, one can just implement before [; Whatever: “hi” ] on an Object and include some action-specifying cases as if the before method were a functional switch or case statement. rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity rubocop: disable Style/ReturnNilInPredicateMethodDefinition

Returns:

  • (Boolean)


305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/runtime/stdlib.rb', line 305

def reacts_to?(obj, prop, *args)
  raise "Nothing is not an object" if obj.nil?
  return false unless ReactionHandlers.include?(prop)
  # TODO: Only apply Behavior modules if object is not being controlled
  # by a player.
  obj.apply_modules if obj.respond_to?(:apply_modules)
  si = obj.inflib
  obj.inflib = self

  # TODO: I have prototyped a possible Inform-esque
  # switch-style dsl for reaction methods based on
  # action name.  Here, the prop would be sent the
  # object normally, but the object reaction method
  # would contain a series of method invocations with
  # a block parameter.  If any of the method missing
  # invocations method name parameter resembled the
  # action name, then the given block would get
  # executed on the subject, thereby simulating the
  # Inform-style functional switch idiom. Like this:
  #
  #   def method_missing(method, *args, &block)
  #     return yield if method == action
  #     super
  #   end
  #
  #   def before
  #     Touch {
  #       publish The(self) + " shudders."
  #       println "Your skin crawls."
  #       true
  #     }
  #     Show {
  #       publish The(self) + " is uninterested in " + the(noun) + "."
  #       "Nothing special."
  #     }
  #   end
  #

  # TODO: Zarf has recommended implementing a check for code persisted as
  # a property of an observant object.  For instance:
  # object:
  #   properties:
  #     ---
  #     before:
  #       ---
  #       Touch: publish The(self) + "shudders."; println "Your skin crawls."; true

  # To preserve support for a game designer to implement catch-all
  # reactions by observant objects, just have the receiver object
  # read the action variable in order to determine course.  For example:
  #
  #   def before
  #     case action
  #     when :Poke then println "Keep your hands to yourself!"
  #
  if PrimaryHandlers.include?(prop) && obj.respond_to?(prop)
    # contextualize
    # obj.contextualize
    # TODO: Test
    context_action = Thread.current[:event]&.action
    log.debug "Inform#reacts_to?(obj=#{obj}, prop=#{prop}) action: #{context_action}"
    if (result = obj.send(prop, *args))
      return result
    end
  end

  # TODO: TESTME Confirm that using the parser's @action variable
  # is sufficiently reliable for composing reaction handler methods.
  # As of 2021-07-21, there appears to be some evidence that using
  # the #invoke method to generate reaction callbacks here is not
  # usable with the @action approach. Apparently, @action is the least
  # reliable because it can include a leftover value from a previous
  # interaction, since the #invoke method does not set the value on
  # the library (and nor should it, since #invoke is for ad-hoc events).
  # The longer term and more correct fix here is likely to consolidate
  # all references to @action as simply action which would pull from an
  # event context. For now I think that I'll simply include @action at
  # the end of the chain, in order to ensure that some action is
  # available for reaction handler method reference construction.
  # act = obj.action || action || @action
  # act = @action || obj.inflib.action || action
  # act = obj.inflib.action || action
  # act = obj.inflib.action || action || @action
  act = Thread.current[:event]&.action || action || @action
  # TODO: Remove debug logging here.
  # log.debug "Inform#reacts_to? #{prop}"
  # log.debug "  #{self}.@action: #{@action}"
  # log.debug "  #{obj.inflib}.action: #{obj.inflib.action}"
  # log.debug "  #{Thread.current[:event]}.current.thread.event.action: #{action}"
  # log.debug "  act: #{act}"
  if act.nil?
    log.warn "Failed to reference action invoking #reacts_to?"
    return nil
  end
  reaction = "#{prop}_#{act}"
  # log.debug "obj: #{obj.identity}, reaction: #{reaction}"
  # log.debug "obj.respond_to?(reaction.downcase!):"
  # log.debug "  #{obj.respond_to?(reaction.downcase!)} (#{obj.respond_to?(reaction)})"
  if obj.respond_to?(reaction)
    handler = obj.method(reaction)
  elsif obj.respond_to?(reaction.downcase!)
    handler = obj.method(reaction)
  else
    return nil # false
  end
  log.debug "Found reaction handler for #{obj}.#{reaction}: #{handler}"
  if SynchronousHandlers.include?(prop)
    # Go ahead and print any string results from designer-implemented
    # handlers, and return true if there are any.
    # contextualize
    result = obj.instance_exec(*args, &handler)
    return result unless result.is_a?(String)
    println result
    return true
  end
  # ctx = invocation_context(@action, @noun, @second, @special_word, @consult_words)
  ctx = invocation_context(act, noun, second, special_word, consult_words)
  obj.register_callback(ctx, *args, &handler)
  # Because of the event-oriented nature of actions in a multiplayer
  # system, other objects may not interfere with original actions
  # once they have taken place. So, false must always be returned.
  # However, game designers must still have the ability to direct an
  # object, particular an animate one, to respond to an action if
  # appropriate.
  nil # false
rescue StandardError => e
  log.error "Error getting reaction from #{obj}", e
  e.backtrace.each { |t| log.error t }
ensure
  obj.inflib = si
end

#ScoreSubObject



127
128
129
# File 'lib/runtime/stdlib.rb', line 127

def ScoreSub
  false
end

#silentlyObject



155
156
157
158
159
160
# File 'lib/runtime/stdlib.rb', line 155

def silently(*)
  k = @keep_silent; @keep_silent = true
  yield
ensure
  @keep_silent = k
end

#standard_interpreterObject



42
43
44
# File 'lib/runtime/stdlib.rb', line 42

def standard_interpreter
  0
end

#StatusLineHeightObject



125
# File 'lib/runtime/stdlib.rb', line 125

def StatusLineHeight; end

#TimePassesObject



524
525
526
# File 'lib/runtime/stdlib.rb', line 524

def TimePasses(*)
  false
end

#UnknownVerb(verb) ⇒ Object



528
529
530
# File 'lib/runtime/stdlib.rb', line 528

def UnknownVerb(verb, *)
  verb
end

#VerbRoutine(action, opts = {}) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/runtime/stdlib.rb', line 266

def VerbRoutine(action, opts = {})
  raise 'The action parameter may not be nil' if action.nil?
  opts = DefaultVerbRoutineOptions.merge(opts)
  if action.respond_to?(:underscore) && respond_to?(
    sub_routine = (action.underscore(downcase: false) + ParseRoutineString))
    sub_routine.to_sym
  elsif respond_to?(sub_routine = (action + SubString))
    sub_routine.to_sym
  elsif respond_to?(sub_routine = (action.camelize + SubString))
    sub_routine.to_sym
  elsif action.respond_to?(:snake_case) && respond_to?(sub_routine = action.snake_case)
    sub_routine.to_sym
  elsif respond_to?(sub_routine = action)
    sub_routine.to_sym
  elsif opts[:fail_on_error]
    raise NoVerbRoutine, 'Please define a verb subroutine for the action ' + action
  else
    log.warn "Please define a verb subroutine for the action #{action}"
    false
  end
end

#version_numberObject



46
47
48
# File 'lib/runtime/stdlib.rb', line 46

def version_number
  0
end