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,553 @@
|
|
|
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
|
+
require 'set'
|
|
22
|
+
|
|
23
|
+
# In-file Grammar support implementation follows.
|
|
24
|
+
#
|
|
25
|
+
# Usage example:
|
|
26
|
+
#
|
|
27
|
+
# Inform::Grammar <<~EOT
|
|
28
|
+
# Verb meta 'example'
|
|
29
|
+
# * -> Example;
|
|
30
|
+
# Verb 'smile'
|
|
31
|
+
# * -> Smile;
|
|
32
|
+
# EOT
|
|
33
|
+
module Inform
|
|
34
|
+
def Grammar(src)
|
|
35
|
+
source_location = caller_locations(1, 1).first
|
|
36
|
+
mod_name = File.basename(source_location.path, '.*').gsub(/\W+/, '_').to_sym
|
|
37
|
+
Inform::Grammar::Parser.new.parse_grammar(mod_name, src.each_line,
|
|
38
|
+
origin_path: source_location.path, origin_line: source_location.lineno)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
module_function :Grammar
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The Inform module
|
|
45
|
+
module Inform
|
|
46
|
+
Dictionary = Set.new
|
|
47
|
+
|
|
48
|
+
### Verbs
|
|
49
|
+
class Verb < Set
|
|
50
|
+
attr_reader :meta, :emote
|
|
51
|
+
attr_accessor :grammars, :source
|
|
52
|
+
|
|
53
|
+
def initialize(verbs, source, meta = nil, emote = nil)
|
|
54
|
+
super()
|
|
55
|
+
merge(verbs)
|
|
56
|
+
@meta = meta unless meta.nil?
|
|
57
|
+
@emote = emote unless emote.nil?
|
|
58
|
+
@grammars = []
|
|
59
|
+
@source = source
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ==(other)
|
|
63
|
+
!other.nil? && include?(other.to_s) &&
|
|
64
|
+
meta? == other.meta? && grammars == other.grammars && super
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def <=>(other)
|
|
68
|
+
!other.nil? && include?(other.to_s) &&
|
|
69
|
+
meta? == other.meta? && grammars == other.grammars && super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def eql?(other)
|
|
73
|
+
!other.nil? && include?(other.to_s) &&
|
|
74
|
+
meta? == other.meta? && grammars == other.grammars && super
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def verb
|
|
78
|
+
first
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def meta?
|
|
82
|
+
!meta.nil?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def emote?
|
|
86
|
+
!emote.nil?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
SpaceString = ' '.freeze
|
|
90
|
+
VerbString = 'Verb'.freeze
|
|
91
|
+
MetaString = 'meta'.freeze
|
|
92
|
+
EmoteString = 'emote'.freeze
|
|
93
|
+
Newline = "\n".freeze
|
|
94
|
+
|
|
95
|
+
def to_s
|
|
96
|
+
VerbString + SpaceString +
|
|
97
|
+
if meta?
|
|
98
|
+
MetaString + SpaceString
|
|
99
|
+
elsif emote?
|
|
100
|
+
EmoteString + SpaceString
|
|
101
|
+
else
|
|
102
|
+
''
|
|
103
|
+
end +
|
|
104
|
+
map { |verb| "'#{verb}'" }.join(SpaceString) + Newline +
|
|
105
|
+
grammars.map(&:to_s).join(Newline)
|
|
106
|
+
end
|
|
107
|
+
alias to_str to_s
|
|
108
|
+
|
|
109
|
+
def hash
|
|
110
|
+
[super, meta, emote, grammars].hash
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#### Grammars
|
|
114
|
+
class Grammar < Array
|
|
115
|
+
MultipleEnquotedPrepositionPattern = %r{^'(.*)'/'(.*)'+$}.freeze
|
|
116
|
+
SingleEnquotedPrepositionPattern = %r{^'(.*)'$}.freeze
|
|
117
|
+
CustomScopePattern = %r{^(.*)=(.*)$}.freeze
|
|
118
|
+
attr_reader :source, :action, :expected_tokens
|
|
119
|
+
|
|
120
|
+
def initialize(grammar, source, action, reverse = nil)
|
|
121
|
+
super()
|
|
122
|
+
concat(symbolize_grammar(grammar)) unless grammar.empty?
|
|
123
|
+
push(:end)
|
|
124
|
+
@action = action
|
|
125
|
+
@expected_tokens = grammar.map { |a| a =~ CustomScopePattern ? a.to_s.split('=').first.to_sym : a }
|
|
126
|
+
@reverse = reverse.nil? ? false : reverse
|
|
127
|
+
@source = source
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def empty?
|
|
131
|
+
length == (include?(:end) ? 1 : 0)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
SpaceString = ' '.freeze
|
|
135
|
+
Indention = SpaceString * 4
|
|
136
|
+
AsteriskString = '*'.freeze
|
|
137
|
+
ReverseString = 'reverse'.freeze
|
|
138
|
+
ArrowString = '->'.freeze
|
|
139
|
+
GrammarTokenPadding = 40
|
|
140
|
+
|
|
141
|
+
def to_s
|
|
142
|
+
Indention + AsteriskString + SpaceString +
|
|
143
|
+
tokens_sans_end.ljust(GrammarTokenPadding) +
|
|
144
|
+
ArrowString + SpaceString + action +
|
|
145
|
+
if reverse?
|
|
146
|
+
SpaceString + ReverseString
|
|
147
|
+
else
|
|
148
|
+
''
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
alias to_str to_s
|
|
152
|
+
|
|
153
|
+
def parameters
|
|
154
|
+
@parameters ||= []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def reversed_parameters
|
|
158
|
+
@reversed_parameters ||= [parameters[1], parameters[0]] + parameters[2..]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def expected_parameters
|
|
162
|
+
reverse? ? reversed_parameters : parameters
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def reverse?
|
|
166
|
+
@reverse
|
|
167
|
+
end
|
|
168
|
+
alias reversed? reverse?
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def tokens_sans_end
|
|
173
|
+
self[0..-2].join(SpaceString)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def symbolize_grammar(grammar)
|
|
177
|
+
grammar.map { |token| symbolize_token(token) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def when_matching(regexp, str, &block)
|
|
181
|
+
match_data = regexp.match(str)
|
|
182
|
+
return false if match_data.nil?
|
|
183
|
+
block.call(match_data.captures) if block_given?
|
|
184
|
+
true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def prepositions?(token)
|
|
188
|
+
when_matching(MultipleEnquotedPrepositionPattern, token) do |matches|
|
|
189
|
+
Inform::Dictionary.merge(matches.map(&:to_sym))
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def preposition?(token)
|
|
194
|
+
when_matching(SingleEnquotedPrepositionPattern, token) do |matches|
|
|
195
|
+
Inform::Dictionary << matches.first.to_sym
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def custom_scope?(token)
|
|
200
|
+
when_matching(CustomScopePattern, token) do |matches|
|
|
201
|
+
parameters << matches.first.to_sym
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def symbolize_token(token)
|
|
206
|
+
return token if prepositions?(token) || preposition?(token) || custom_scope?(token)
|
|
207
|
+
parameters << token.to_sym
|
|
208
|
+
token.to_sym
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
# class Grammar
|
|
212
|
+
end
|
|
213
|
+
# class Verb
|
|
214
|
+
end
|
|
215
|
+
# module Inform
|
|
216
|
+
|
|
217
|
+
# The Inform module to implement additional Grammar parsing
|
|
218
|
+
module Inform
|
|
219
|
+
# The DuplicateVerb error class
|
|
220
|
+
class DuplicateVerb < StandardError
|
|
221
|
+
UnknownString = 'unknown'.freeze
|
|
222
|
+
|
|
223
|
+
attr_writer :file, :line
|
|
224
|
+
|
|
225
|
+
def initialize(verb)
|
|
226
|
+
super()
|
|
227
|
+
@verb = verb
|
|
228
|
+
@file = UnknownString
|
|
229
|
+
@line = UnknownString
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def to_s
|
|
233
|
+
"(#{@file}):#{@line}: verb error, verb '#{@verb}', already defined"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The UnexpectedToken error class
|
|
238
|
+
class UnexpectedToken < StandardError
|
|
239
|
+
UnknownString = 'unknown'.freeze
|
|
240
|
+
attr_writer :file, :line
|
|
241
|
+
|
|
242
|
+
def initialize(expected, found)
|
|
243
|
+
super()
|
|
244
|
+
@expected = expected
|
|
245
|
+
@found = found
|
|
246
|
+
@file = UnknownString
|
|
247
|
+
@line = UnknownString
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def to_s
|
|
251
|
+
"(#{@file}):#{@line}: token error, unexpected '#{@found}', expecting '#{@expected}'"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def self.load_grammar(module_name)
|
|
256
|
+
Inform::Grammar.load_by_name(module_name)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def self.load_grammar_by_path(module_path)
|
|
260
|
+
Inform::Grammar.load_by_path(module_path)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def self.unload_grammars!
|
|
264
|
+
log.debug "Unloading all grammars"
|
|
265
|
+
Inform::Grammar::Verbs.clear
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def self.prime_dictionary
|
|
269
|
+
Inform::Object.select_map(:name).compact.each do |n|
|
|
270
|
+
Inform::Dictionary.merge(n.split(/[,\s]+/).grep_v(/[^a-z]/i).map(&:downcase).map(&:to_sym))
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
### Grammar
|
|
275
|
+
module Grammar
|
|
276
|
+
# The Inform::Grammar::Index class
|
|
277
|
+
class Index < Hash
|
|
278
|
+
AdminPattern = %r{admin}.freeze
|
|
279
|
+
BuilderPattern = %r{builder}.freeze
|
|
280
|
+
Profanity = %w[shit damn fuck sod].freeze
|
|
281
|
+
ValidModes = %i[admin builder player].freeze
|
|
282
|
+
ModePatterns = ValidModes.each_with_object({}) { |key, memo| memo[key] = Regexp.new(key.to_s) }
|
|
283
|
+
|
|
284
|
+
def lookup(key, actor = nil, grammar_index = self)
|
|
285
|
+
return nil if key.nil? || !grammar_index.include?(key)
|
|
286
|
+
verb = grammar_index[key].dup
|
|
287
|
+
return verb if actor.nil?
|
|
288
|
+
return nil if guarded?(verb, actor)
|
|
289
|
+
filter_by_permission(verb, actor)
|
|
290
|
+
verb
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def all(actor = nil, grammar_index = self)
|
|
294
|
+
return grammar_index.map(&:keys).flatten.sort if actor.nil?
|
|
295
|
+
mode = role(actor)
|
|
296
|
+
init_all_verbs_by_mode(mode, grammar_index, actor) unless Inform::Grammar.partitions.include?(mode)
|
|
297
|
+
Inform::Grammar.partitions[mode]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def by_mode(mode = nil, grammar_index = self)
|
|
301
|
+
mode = validate(mode)
|
|
302
|
+
init_verbs_filtered_by_mode(mode, grammar_index) unless Inform::Grammar.selections.include?(mode)
|
|
303
|
+
Inform::Grammar.selections[mode]
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private
|
|
307
|
+
|
|
308
|
+
def init_all_verbs_by_mode(mode, grammar_index, actor, set = verbs_set_instance)
|
|
309
|
+
verbs = grammar_index.each_with_object(set) do |(key, verb), memo|
|
|
310
|
+
memo.add(key) unless Profanity.include?(key) || guarded?(verb, actor)
|
|
311
|
+
end
|
|
312
|
+
Inform::Grammar.partitions[mode] = verbs.to_a
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def init_verbs_filtered_by_mode(mode, grammar_index, set = verbs_set_instance)
|
|
316
|
+
verbs = grammar_index.each_with_object(set) do |(key, verb), memo|
|
|
317
|
+
memo.add(key) if ModePatterns[mode].match?(verb.source)
|
|
318
|
+
end
|
|
319
|
+
Inform::Grammar.selections[mode] = verbs.to_a
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def verbs_set_instance
|
|
323
|
+
defined?(Java) ? java.util.TreeSet.new : Set.new
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def filter_by_permission(verb, actor)
|
|
327
|
+
verb.grammars.delete_if do |grammar|
|
|
328
|
+
guarded?(grammar, actor)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def guarded?(resource, obj)
|
|
333
|
+
(AdminPattern.match?(resource.source) && !obj.admin?) ||
|
|
334
|
+
(BuilderPattern.match?(resource.source) && !obj.builder?)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def role(obj)
|
|
338
|
+
return :builder if obj.builder?
|
|
339
|
+
return :admin if obj.admin?
|
|
340
|
+
:player
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def validate(mode)
|
|
344
|
+
return mode.to_sym if ValidModes.include?(mode.to_sym)
|
|
345
|
+
:player
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
Verbs = Inform::Grammar::Index.new
|
|
350
|
+
|
|
351
|
+
def self.partitions
|
|
352
|
+
@partitions ||= defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def self.selections
|
|
356
|
+
@selections ||= defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
VALID_EXTENSIONS = ['h.inf.rb', '.inf.rb', '.rb', ''].freeze
|
|
360
|
+
|
|
361
|
+
def self.absolute_basename(file_path)
|
|
362
|
+
File.basename(file_path, File.extname(file_path)).split(/\./).first
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
SUPPORT_DIR_PATH = File.expand_path(__dir__) unless defined?(SUPPORT_DIR_PATH)
|
|
366
|
+
def self.get_path_for_best_match(str)
|
|
367
|
+
VALID_EXTENSIONS.each do |ext|
|
|
368
|
+
grammar_guess = str + ext
|
|
369
|
+
guess = File.expand_path(
|
|
370
|
+
File.join(
|
|
371
|
+
Inform::Library.inform_gem_lib_path,
|
|
372
|
+
Inform::Runtime.inform_dir_path,
|
|
373
|
+
grammar_guess))
|
|
374
|
+
return guess if File.exist?(guess)
|
|
375
|
+
end
|
|
376
|
+
nil
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def self.load_by_name(module_name)
|
|
380
|
+
module_path = get_path_for_best_match(module_name)
|
|
381
|
+
load_by_path(module_path, module_name)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def self.load_by_path(module_path, module_name = nil)
|
|
385
|
+
raise ArgumentError, 'Parameter may not be nil: module_path' if module_path.nil?
|
|
386
|
+
raise LoadError, 'No such file: ' + module_path unless File.exist?(module_path)
|
|
387
|
+
module_name ||= absolute_basename(module_path)
|
|
388
|
+
log.debug "Loading grammar file: #{module_path}"
|
|
389
|
+
Inform::Grammar::Parser.new.parse_grammar(
|
|
390
|
+
module_name.to_sym, File.foreach(module_path), origin_path: module_path)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# The Inform::Grammar::Parser class will parse inform-style
|
|
394
|
+
# grammar files
|
|
395
|
+
# rubocop: disable Metrics/ClassLength
|
|
396
|
+
class Parser
|
|
397
|
+
CommentedOrBlankLine = %r{^\s*[^\#\n]}.freeze
|
|
398
|
+
TerminalLinePattern = %r{;$}.freeze
|
|
399
|
+
TokenPattern = %r{'[^\s']+'}.freeze
|
|
400
|
+
ArrowPattern = %r{->}.freeze
|
|
401
|
+
def initialize
|
|
402
|
+
# Base state for block comment toggling
|
|
403
|
+
@block_comment = false
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# rubocop: disable Metrics/MethodLength
|
|
407
|
+
def parse_grammar(grammar_module, lines, origin_path: nil, origin_line: 0)
|
|
408
|
+
line_index = Integer(origin_line || 0)
|
|
409
|
+
lines.each do |line|
|
|
410
|
+
line_index += 1
|
|
411
|
+
next unless /^\s*[^\#\n]/.match?(line)
|
|
412
|
+
begin
|
|
413
|
+
parse(grammar_module, line)
|
|
414
|
+
rescue DuplicateVerb => e
|
|
415
|
+
e.file = File.basename(origin_path)
|
|
416
|
+
e.line = line_index
|
|
417
|
+
log.warn e
|
|
418
|
+
rescue UnexpectedToken => e
|
|
419
|
+
e.file = File.basename(origin_path)
|
|
420
|
+
e.line = line_index
|
|
421
|
+
raise e
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
true
|
|
425
|
+
end
|
|
426
|
+
# rubocop: enable Metrics/MethodLength
|
|
427
|
+
|
|
428
|
+
# rubocop: disable Metrics/AbcSize
|
|
429
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
|
430
|
+
# rubocop: disable Metrics/MethodLength
|
|
431
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
432
|
+
# rubocop: disable Style/RaiseArgs
|
|
433
|
+
def parse(source, line)
|
|
434
|
+
return if block_comment?(line)
|
|
435
|
+
terminal = /;$/.match(line)
|
|
436
|
+
# terminal = TerminalLinePattern.match?(line)
|
|
437
|
+
line.gsub!(/\#.*/, '')
|
|
438
|
+
line.gsub!(/;$/, '')
|
|
439
|
+
tokens = line.split
|
|
440
|
+
return if tokens.empty?
|
|
441
|
+
token = tokens.first
|
|
442
|
+
@grammars ||= []
|
|
443
|
+
case token
|
|
444
|
+
when /^Verb/
|
|
445
|
+
raise UnexpectedToken.new('*', 'Verb') unless @grammars.empty?
|
|
446
|
+
tokens.shift
|
|
447
|
+
meta = tokens.delete 'meta'
|
|
448
|
+
emote = tokens.delete 'emote'
|
|
449
|
+
@first = false
|
|
450
|
+
@last = true
|
|
451
|
+
synonym = tokens.delete '='
|
|
452
|
+
replace = tokens.delete 'replace' # rubocop: disable Lint/UselessAssignment
|
|
453
|
+
tokens.delete_if { |a| !/'[^\s']+'/.match?(a) }
|
|
454
|
+
# tokens.delete_if { |a| !TokenPattern.match?(a) }
|
|
455
|
+
keys = tokens.map { |a| a[1..-2] }
|
|
456
|
+
key = synonym ? keys.last : keys.first
|
|
457
|
+
log.debug "Loading verb: #{key}"
|
|
458
|
+
@verb = Inform::Grammar::Verbs.lookup(key)
|
|
459
|
+
if @verb
|
|
460
|
+
raise DuplicateVerb.new(key) unless synonym
|
|
461
|
+
@verb.merge keys
|
|
462
|
+
else
|
|
463
|
+
@verb = Verb.new(keys, source, meta, emote)
|
|
464
|
+
end
|
|
465
|
+
keys.each do |k|
|
|
466
|
+
Inform::Grammar::Verbs[k] = @verb
|
|
467
|
+
end
|
|
468
|
+
when /^Extend/
|
|
469
|
+
raise UnexpectedToken.new('*', 'Extend') unless @grammars.empty?
|
|
470
|
+
tokens.shift
|
|
471
|
+
meta = tokens.delete('meta')
|
|
472
|
+
emote = tokens.delete('emote')
|
|
473
|
+
only = tokens.delete('only')
|
|
474
|
+
@first = tokens.delete('first')
|
|
475
|
+
@last = tokens.delete('last')
|
|
476
|
+
replace = tokens.delete('replace')
|
|
477
|
+
tokens.delete_if { |verb_token| !/'[^\s']+'/.match?(verb_token) }
|
|
478
|
+
# tokens.delete_if { |token| !TokenPattern.match?(token) }
|
|
479
|
+
keys = tokens.map { |verb_token| verb_token[1..-2] }
|
|
480
|
+
key = keys.first
|
|
481
|
+
existing_verb = Inform::Grammar::Verbs.lookup(key)
|
|
482
|
+
if existing_verb.nil?
|
|
483
|
+
@verb = Verb.new(keys, source, meta, emote)
|
|
484
|
+
elsif only.nil?
|
|
485
|
+
existing_verb.merge(keys)
|
|
486
|
+
@verb = existing_verb
|
|
487
|
+
unless replace.nil?
|
|
488
|
+
@verb.source = source
|
|
489
|
+
@verb.grammars.clear
|
|
490
|
+
end
|
|
491
|
+
else
|
|
492
|
+
keys.each do |existing_key|
|
|
493
|
+
Inform::Grammar::Verbs.delete(existing_key)
|
|
494
|
+
existing_verb.delete(existing_key)
|
|
495
|
+
end
|
|
496
|
+
@verb = Verb.new(keys, source, meta, emote)
|
|
497
|
+
@verb.grammars = existing_verb.grammars if replace.nil?
|
|
498
|
+
keys.each do |new_key|
|
|
499
|
+
Inform::Grammar::Verbs[new_key] = @verb
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
Inform::Grammar::Verbs[key] = @verb
|
|
503
|
+
when /\*/
|
|
504
|
+
tokens, action = line.split(ArrowPattern)
|
|
505
|
+
tokens = tokens.split
|
|
506
|
+
tokens.shift
|
|
507
|
+
reverse = false; reverse = true if /reverse/.match?(action)
|
|
508
|
+
action = action.gsub(/;/, '').split.first.to_sym
|
|
509
|
+
grammar = Verb::Grammar.new(tokens, source, action, reverse)
|
|
510
|
+
@grammars.push(grammar)
|
|
511
|
+
else
|
|
512
|
+
unless @verb.nil?
|
|
513
|
+
new_keys = tokens.map { |verb_token| verb_token[1..-2] }
|
|
514
|
+
@verb.merge(new_keys) # Set#merge modifies in-place
|
|
515
|
+
new_keys.each do |new_key|
|
|
516
|
+
Inform::Grammar::Verbs[new_key] = @verb
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
return if terminal.nil?
|
|
522
|
+
|
|
523
|
+
if @first.nil?
|
|
524
|
+
@verb.grammars.concat(@grammars)
|
|
525
|
+
else
|
|
526
|
+
@verb.grammars.unshift(*@grammars)
|
|
527
|
+
end
|
|
528
|
+
@grammars.clear
|
|
529
|
+
end
|
|
530
|
+
# rubocop: enable Metrics/AbcSize
|
|
531
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
532
|
+
# rubocop: enable Metrics/MethodLength
|
|
533
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
534
|
+
# rubocop: enable Style/RaiseArgs
|
|
535
|
+
# def parse
|
|
536
|
+
|
|
537
|
+
def block_comment?(line)
|
|
538
|
+
if line[/^=begin/]
|
|
539
|
+
@block_comment = true
|
|
540
|
+
elsif line[/^=end/]
|
|
541
|
+
@block_comment = false
|
|
542
|
+
true
|
|
543
|
+
else
|
|
544
|
+
@block_comment
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
# rubocop: enable Metrics/ClassLength
|
|
549
|
+
# class Parser
|
|
550
|
+
end
|
|
551
|
+
# module Grammar
|
|
552
|
+
end
|
|
553
|
+
# module Inform
|