rltk 2.2.1 → 3.0.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.
- checksums.yaml +7 -0
- data/LICENSE +12 -12
- data/README.md +458 -285
- data/Rakefile +99 -92
- data/lib/rltk/ast.rb +221 -126
- data/lib/rltk/cfg.rb +218 -239
- data/lib/rltk/cg/basic_block.rb +1 -1
- data/lib/rltk/cg/bindings.rb +9 -26
- data/lib/rltk/cg/builder.rb +40 -8
- data/lib/rltk/cg/context.rb +1 -1
- data/lib/rltk/cg/contractor.rb +51 -0
- data/lib/rltk/cg/execution_engine.rb +45 -8
- data/lib/rltk/cg/function.rb +12 -2
- data/lib/rltk/cg/generated_bindings.rb +2541 -575
- data/lib/rltk/cg/generic_value.rb +2 -2
- data/lib/rltk/cg/instruction.rb +104 -83
- data/lib/rltk/cg/llvm.rb +44 -3
- data/lib/rltk/cg/memory_buffer.rb +22 -5
- data/lib/rltk/cg/module.rb +85 -36
- data/lib/rltk/cg/old_generated_bindings.rb +6152 -0
- data/lib/rltk/cg/pass_manager.rb +87 -43
- data/lib/rltk/cg/support.rb +2 -4
- data/lib/rltk/cg/target.rb +158 -28
- data/lib/rltk/cg/triple.rb +8 -8
- data/lib/rltk/cg/type.rb +69 -25
- data/lib/rltk/cg/value.rb +107 -66
- data/lib/rltk/cg.rb +16 -17
- data/lib/rltk/lexer.rb +21 -11
- data/lib/rltk/lexers/calculator.rb +1 -1
- data/lib/rltk/lexers/ebnf.rb +8 -7
- data/lib/rltk/parser.rb +300 -247
- data/lib/rltk/parsers/infix_calc.rb +1 -1
- data/lib/rltk/parsers/postfix_calc.rb +2 -2
- data/lib/rltk/parsers/prefix_calc.rb +2 -2
- data/lib/rltk/token.rb +1 -2
- data/lib/rltk/version.rb +3 -3
- data/lib/rltk.rb +6 -6
- data/test/cg/tc_basic_block.rb +83 -0
- data/test/cg/tc_control_flow.rb +191 -0
- data/test/cg/tc_function.rb +54 -0
- data/test/cg/tc_generic_value.rb +33 -0
- data/test/cg/tc_instruction.rb +256 -0
- data/test/cg/tc_llvm.rb +25 -0
- data/test/cg/tc_math.rb +88 -0
- data/test/cg/tc_module.rb +89 -0
- data/test/cg/tc_transforms.rb +68 -0
- data/test/cg/tc_type.rb +69 -0
- data/test/cg/tc_value.rb +151 -0
- data/test/cg/ts_cg.rb +23 -0
- data/test/tc_ast.rb +105 -8
- data/test/tc_cfg.rb +63 -48
- data/test/tc_lexer.rb +84 -96
- data/test/tc_parser.rb +224 -52
- data/test/tc_token.rb +6 -6
- data/test/ts_rltk.rb +12 -15
- metadata +149 -75
- data/lib/rltk/cg/generated_extended_bindings.rb +0 -287
- data/lib/rltk/util/abstract_class.rb +0 -25
- data/lib/rltk/util/monkeys.rb +0 -129
data/lib/rltk/cfg.rb
CHANGED
@@ -10,6 +10,9 @@
|
|
10
10
|
# Requires #
|
11
11
|
############
|
12
12
|
|
13
|
+
# Standard Library
|
14
|
+
require 'set'
|
15
|
+
|
13
16
|
# Ruby Language Toolkit
|
14
17
|
require 'rltk/lexers/ebnf'
|
15
18
|
|
@@ -17,8 +20,7 @@ require 'rltk/lexers/ebnf'
|
|
17
20
|
# Classes and Modules #
|
18
21
|
#######################
|
19
22
|
|
20
|
-
module RLTK
|
21
|
-
|
23
|
+
module RLTK
|
22
24
|
# An exception class that represents a problem with a context-free
|
23
25
|
# grammar's definition.
|
24
26
|
class GrammarError < StandardError; end
|
@@ -71,22 +73,22 @@ module RLTK # :nodoc:
|
|
71
73
|
#
|
72
74
|
# @param [Proc] callback A Proc object to be called when EBNF operators are expanded.
|
73
75
|
def initialize(&callback)
|
74
|
-
@curr_lhs
|
75
|
-
@callback
|
76
|
-
@lexer
|
77
|
-
@production_counter
|
78
|
-
@start_symbol
|
79
|
-
@wrapper_symbol
|
80
|
-
|
81
|
-
@productions_id
|
82
|
-
@productions_sym
|
83
|
-
@production_buffer
|
84
|
-
|
85
|
-
@terms
|
86
|
-
@nonterms
|
87
|
-
|
88
|
-
@firsts
|
89
|
-
@follows
|
76
|
+
@curr_lhs = nil
|
77
|
+
@callback = callback || Proc.new {}
|
78
|
+
@lexer = Lexers::EBNF.new
|
79
|
+
@production_counter = -1
|
80
|
+
@start_symbol = nil
|
81
|
+
@wrapper_symbol = nil
|
82
|
+
|
83
|
+
@productions_id = Hash.new
|
84
|
+
@productions_sym = Hash.new { |h, k| h[k] = [] }
|
85
|
+
@production_buffer = Array.new
|
86
|
+
|
87
|
+
@terms = Set.new([:EOS])
|
88
|
+
@nonterms = Set.new
|
89
|
+
|
90
|
+
@firsts = Hash.new
|
91
|
+
@follows = Hash.new { |h,k| h[k] = Array.new }
|
90
92
|
end
|
91
93
|
|
92
94
|
# Adds *production* to the appropriate internal data structures.
|
@@ -100,13 +102,136 @@ module RLTK # :nodoc:
|
|
100
102
|
production
|
101
103
|
end
|
102
104
|
|
105
|
+
# Builds a production representing a (possibly empty) list of tokens.
|
106
|
+
# These tokens may optionally be separated by a provided token. This
|
107
|
+
# function is used to eliminate the EBNF * operator.
|
108
|
+
#
|
109
|
+
# @param [Symbol] name The name of the production to add
|
110
|
+
# @param [String, Symbol, Array<String>] list_elements Expression(s) that may appear in the list
|
111
|
+
# @param [Symbol, String] separator The list separator symbol or symbols
|
112
|
+
#
|
113
|
+
# @return [void]
|
114
|
+
def build_list_production(name, list_elements, separator = '')
|
115
|
+
# Add the items for the following productions:
|
116
|
+
#
|
117
|
+
# name: | name_prime
|
118
|
+
|
119
|
+
name_prime = "#{name}_prime".to_sym
|
120
|
+
|
121
|
+
# 1st Production
|
122
|
+
production, _ = self.production(name, '')
|
123
|
+
@callback.call(:elp, :empty, production)
|
124
|
+
|
125
|
+
# 2nd Production
|
126
|
+
production, _ = self.production(name, name_prime)
|
127
|
+
@callback.call(:elp, :nonempty, production)
|
128
|
+
|
129
|
+
# Add remaining productions via nonempty_list helper.
|
130
|
+
self.nonempty_list(name_prime, list_elements, separator)
|
131
|
+
|
132
|
+
name
|
133
|
+
end
|
134
|
+
alias :list :build_list_production
|
135
|
+
|
136
|
+
# Builds a production representing a non-empty list of tokens. These
|
137
|
+
# tokens may optionally be separated by a provided token. This
|
138
|
+
# function is used to eliminate the EBNF + operator.
|
139
|
+
#
|
140
|
+
# @param [Symbol] name The name of the production to add
|
141
|
+
# @param [String, Symbol, Array<String, Symbol>] list_elements Expression(s) that may appear in the list
|
142
|
+
# @param [Symbol, String] separator The list separator symbol or symbols
|
143
|
+
#
|
144
|
+
# @return [void]
|
145
|
+
def build_nonempty_list_production(name, list_elements, separator = '')
|
146
|
+
# Add the items for the following productions:
|
147
|
+
#
|
148
|
+
# If there is only one list element:
|
149
|
+
#
|
150
|
+
# name: list_element | name separator list_element
|
151
|
+
#
|
152
|
+
# else
|
153
|
+
#
|
154
|
+
# name: name_list_elements | name separator name_list_elements
|
155
|
+
#
|
156
|
+
# name_list_elements: #{list_elements.join('|')}
|
157
|
+
|
158
|
+
build_elements_productions = false
|
159
|
+
|
160
|
+
list_element_string =
|
161
|
+
if list_elements.is_a?(Array)
|
162
|
+
if list_elements.empty?
|
163
|
+
raise ArgumentError, 'Parameter list_elements must not be empty.'
|
164
|
+
|
165
|
+
elsif list_elements.length == 1
|
166
|
+
list_elements.first
|
167
|
+
|
168
|
+
else
|
169
|
+
build_elements_productions = true
|
170
|
+
"#{name}_list_elements"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
list_elements
|
174
|
+
end
|
175
|
+
|
176
|
+
list_element_selected_string = list_element_string.to_s.split.map { |s| ".#{s}" }.join(' ')
|
177
|
+
|
178
|
+
# Single Element Production
|
179
|
+
production, _ = self.production(name, list_element_string)
|
180
|
+
@callback.call(:nelp, :single, production)
|
181
|
+
|
182
|
+
# Multiple Element Production
|
183
|
+
production, selections = self.production(name, ".#{name} #{separator} #{list_element_selected_string}")
|
184
|
+
@callback.call(:nelp, :multiple, production, selections)
|
185
|
+
|
186
|
+
if build_elements_productions
|
187
|
+
# List Element Productions
|
188
|
+
list_elements.each do |element|
|
189
|
+
production, _ = self.production(list_element_string, element)
|
190
|
+
@callback.call(:nelp, :elements, production)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
name
|
195
|
+
end
|
196
|
+
alias :nonempty_list :build_nonempty_list_production
|
197
|
+
|
198
|
+
# Build a production for an optional symbol. This is used to
|
199
|
+
# eliminate the EBNF ? operator.
|
200
|
+
#
|
201
|
+
# @param [Symbol] name The name for the new production
|
202
|
+
# @param [Symbol] opt_symbol Symbol to expand
|
203
|
+
#
|
204
|
+
# @return [Symbol] The value of the name argument
|
205
|
+
def build_optional_production(name, opt_symbol)
|
206
|
+
if not @productions_sym.has_key?(name)
|
207
|
+
# Add the items for the following productions:
|
208
|
+
#
|
209
|
+
# name: | opt_symbol
|
210
|
+
|
211
|
+
# Empty production.
|
212
|
+
production = self.add_production(Production.new(self.next_id, name, []))
|
213
|
+
@callback.call(:optional, :empty, production)
|
214
|
+
|
215
|
+
# Nonempty production
|
216
|
+
production = self.add_production(Production.new(self.next_id, name, [opt_symbol]))
|
217
|
+
@callback.call(:optional, :nonempty, production)
|
218
|
+
|
219
|
+
# Add the new symbol to the list of nonterminals.
|
220
|
+
@nonterms << name
|
221
|
+
end
|
222
|
+
|
223
|
+
name
|
224
|
+
end
|
225
|
+
|
103
226
|
# Sets the EBNF callback to *callback*.
|
104
227
|
#
|
105
228
|
# @param [Proc] callback A Proc object to be called when EBNF operators are expanded and list productions are added.
|
106
229
|
#
|
107
230
|
# @return [void]
|
108
231
|
def callback(&callback)
|
109
|
-
@callback = callback
|
232
|
+
@callback = callback if callback
|
233
|
+
|
234
|
+
nil
|
110
235
|
end
|
111
236
|
|
112
237
|
# This function MUST be called inside a CFG.production block. It will
|
@@ -114,90 +239,73 @@ module RLTK # :nodoc:
|
|
114
239
|
# CFG.production call's argument. This is the function that is
|
115
240
|
# responsible for removing EBNF symbols from the grammar.
|
116
241
|
#
|
117
|
-
# @param [String] expression The right-hand side of a CFG production.
|
242
|
+
# @param [String, Symbol] expression The right-hand side of a CFG production.
|
118
243
|
#
|
119
|
-
# @return [Production]
|
244
|
+
# @return [Array(Production, Array<Integer>)]
|
120
245
|
def clause(expression)
|
121
246
|
raise GrammarError, 'CFG#clause called outside of CFG#production block.' if not @curr_lhs
|
122
247
|
|
123
|
-
lhs
|
124
|
-
rhs
|
125
|
-
tokens
|
248
|
+
lhs = @curr_lhs.to_sym
|
249
|
+
rhs = Array.new
|
250
|
+
tokens = @lexer.lex(expression.to_s)
|
251
|
+
selections = Array.new
|
126
252
|
|
127
253
|
# Set this as the start symbol if there isn't one already
|
128
254
|
# defined.
|
129
255
|
@start_symbol ||= lhs
|
130
256
|
|
131
257
|
# Remove EBNF tokens and replace them with new productions.
|
258
|
+
symbol_count = 0
|
132
259
|
tokens.each_index do |i|
|
133
|
-
ttype0
|
134
|
-
tvalue0
|
260
|
+
ttype0 = tokens[i].type
|
261
|
+
tvalue0 = tokens[i].value
|
135
262
|
|
136
263
|
if ttype0 == :TERM or ttype0 == :NONTERM
|
137
264
|
|
138
265
|
# Add this symbol to the correct collection.
|
139
|
-
(ttype0 == :TERM ? @terms : @nonterms)
|
266
|
+
(ttype0 == :TERM ? @terms : @nonterms) << tvalue0
|
140
267
|
|
141
268
|
if i + 1 < tokens.length
|
142
|
-
ttype1
|
143
|
-
tvalue1
|
269
|
+
ttype1 = tokens[i + 1].type
|
270
|
+
tvalue1 = tokens[i + 1].value
|
144
271
|
|
145
272
|
rhs <<
|
146
273
|
case ttype1
|
147
|
-
when :
|
148
|
-
when
|
149
|
-
when
|
150
|
-
else
|
274
|
+
when :QUESTION then self.build_optional_production("#{tvalue0.downcase}_optional".to_sym, tvalue0)
|
275
|
+
when :STAR then self.build_list_production("#{tvalue0.downcase}_list".to_sym, tvalue0)
|
276
|
+
when :PLUS then self.build_nonempty_list_production("#{tvalue0.downcase}_nonempty_list".to_sym, tvalue0)
|
277
|
+
else tvalue0
|
151
278
|
end
|
152
279
|
else
|
153
280
|
rhs << tvalue0
|
154
281
|
end
|
282
|
+
|
283
|
+
symbol_count += 1
|
284
|
+
|
285
|
+
elsif ttype0 == :DOT
|
286
|
+
selections << symbol_count
|
155
287
|
end
|
156
288
|
end
|
157
289
|
|
158
290
|
# Make the production.
|
159
|
-
@production_buffer << (production = Production.new(self.next_id, lhs, rhs))
|
291
|
+
@production_buffer << [(production = Production.new(self.next_id, lhs, rhs)), selections]
|
160
292
|
|
161
293
|
# Make sure the production symbol is collected.
|
162
|
-
@nonterms
|
294
|
+
@nonterms << lhs
|
163
295
|
|
164
296
|
# Add the new production to our collections.
|
165
297
|
self.add_production(production)
|
166
298
|
|
167
|
-
return production
|
299
|
+
return [production, selections]
|
168
300
|
end
|
169
301
|
|
170
|
-
# This
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
# @param [Symbol] symbol The name of the production to add.
|
175
|
-
# @param [Array<String>] list_elements An array of expressions that may appear in the list.
|
176
|
-
# @param [Symbol] separator The list separator symbol.
|
302
|
+
# This function calculates the *first* set of a series of tokens. It
|
303
|
+
# uses the {CFG#first_set} helper function to find the first set of
|
304
|
+
# individual symbols.
|
177
305
|
#
|
178
|
-
# @
|
179
|
-
def empty_list_production(symbol, list_elements, separator)
|
180
|
-
# Add the items for the following productions:
|
181
|
-
#
|
182
|
-
# symbol: | symbol_prime
|
183
|
-
|
184
|
-
prime = symbol.to_s + '_prime'
|
185
|
-
|
186
|
-
# 1st Production
|
187
|
-
production = self.production(symbol, '').first
|
188
|
-
@callback.call(production, :elp, :first)
|
189
|
-
|
190
|
-
# 2nd Production
|
191
|
-
production = self.production(symbol, prime.to_s).first
|
192
|
-
@callback.call(production, :elp, :second)
|
193
|
-
|
194
|
-
self.nonempty_list(prime, list_elements, separator)
|
195
|
-
end
|
196
|
-
alias :empty_list :empty_list_production
|
197
|
-
|
198
|
-
# @param [Symbol, Array<Symbol>] sentence Sentence to find the *first set* for.
|
306
|
+
# @param [Symbol, Array<Symbol>] sentence Sentence to find the *first set* for.
|
199
307
|
#
|
200
|
-
# @return [Array<Symbol>]
|
308
|
+
# @return [Array<Symbol>] The *first set* for the given sentence.
|
201
309
|
def first_set(sentence)
|
202
310
|
if sentence.is_a?(Symbol)
|
203
311
|
first_set_prime(sentence)
|
@@ -217,19 +325,16 @@ module RLTK # :nodoc:
|
|
217
325
|
end
|
218
326
|
|
219
327
|
# This function is responsible for calculating the *first* set of
|
220
|
-
# individual symbols.
|
221
|
-
# function to provide support for calculating the *first* set for
|
222
|
-
# sentences.
|
328
|
+
# individual symbols.
|
223
329
|
#
|
224
|
-
# @param [Symbol]
|
225
|
-
# @param [Array<Symbol>]
|
330
|
+
# @param [Symbol] sym0 The symbol to find the *first set* of.
|
331
|
+
# @param [Array<Symbol>] seen_lh_sides Previously seen LHS symbols.
|
226
332
|
#
|
227
333
|
# @return [Array<Symbol>]
|
228
334
|
def first_set_prime(sym0, seen_lh_sides = [])
|
229
335
|
if self.symbols.include?(sym0)
|
230
336
|
# Memoize the result for later.
|
231
337
|
@firsts[sym0] ||=
|
232
|
-
|
233
338
|
if CFG::is_terminal?(sym0)
|
234
339
|
# If the symbol is a terminal, it is the only symbol in
|
235
340
|
# its follow set.
|
@@ -238,7 +343,7 @@ module RLTK # :nodoc:
|
|
238
343
|
set0 = []
|
239
344
|
|
240
345
|
@productions_sym[sym0].each do |production|
|
241
|
-
if production.rhs
|
346
|
+
if production.rhs.empty?
|
242
347
|
# If this is an empty production we should
|
243
348
|
# add the empty string to the First set.
|
244
349
|
set0 << :'ɛ'
|
@@ -277,8 +382,8 @@ module RLTK # :nodoc:
|
|
277
382
|
# used to avoid infinite recursion when mutually recursive rules are
|
278
383
|
# encountered.
|
279
384
|
#
|
280
|
-
# @param [Symbol]
|
281
|
-
# @param [Array<Symbol>]
|
385
|
+
# @param [Symbol] sym0 The symbol to find the *follow set* for.
|
386
|
+
# @param [Array<Symbol>] seen_lh_sides Previously seen LHS symbols.
|
282
387
|
#
|
283
388
|
# @return [Array<Symbol>]
|
284
389
|
def follow_set(sym0, seen_lh_sides = [])
|
@@ -286,7 +391,7 @@ module RLTK # :nodoc:
|
|
286
391
|
# Use the memoized set if possible.
|
287
392
|
return @follows[sym0] if @follows.has_key?(sym0)
|
288
393
|
|
289
|
-
if @nonterms
|
394
|
+
if @nonterms.member? sym0
|
290
395
|
set0 = []
|
291
396
|
|
292
397
|
# Add EOS to the start symbol's follow set.
|
@@ -317,146 +422,14 @@ module RLTK # :nodoc:
|
|
317
422
|
end
|
318
423
|
end
|
319
424
|
|
320
|
-
#
|
321
|
-
#
|
322
|
-
# @param [Symbol] symbol Symbol to expand.
|
323
|
-
#
|
324
|
-
# @return [Symbol]
|
325
|
-
def get_plus(symbol)
|
326
|
-
new_symbol = (symbol.to_s.downcase + '_plus').to_sym
|
327
|
-
|
328
|
-
if not @productions_sym.has_key?(new_symbol)
|
329
|
-
# Add the items for the following productions:
|
330
|
-
#
|
331
|
-
# token_plus: token | token token_plus
|
332
|
-
|
333
|
-
# 1st production
|
334
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, [symbol]))
|
335
|
-
@callback.call(production, :+, :first)
|
336
|
-
|
337
|
-
# 2nd production
|
338
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, [new_symbol, symbol]))
|
339
|
-
@callback.call(production, :+, :second)
|
340
|
-
|
341
|
-
# Add the new symbol to the list of nonterminals.
|
342
|
-
@nonterms[new_symbol] = true
|
343
|
-
end
|
344
|
-
|
345
|
-
return new_symbol
|
346
|
-
end
|
347
|
-
|
348
|
-
# Builds productions used to eliminate the ? EBNF operator.
|
349
|
-
#
|
350
|
-
# @param [Symbol] symbol Symbol to expand.
|
351
|
-
#
|
352
|
-
# @return [Symbol]
|
353
|
-
def get_question(symbol)
|
354
|
-
new_symbol = (symbol.to_s.downcase + '_question').to_sym
|
355
|
-
|
356
|
-
if not @productions_sym.has_key?(new_symbol)
|
357
|
-
# Add the items for the following productions:
|
358
|
-
#
|
359
|
-
# nonterm_question: | nonterm
|
360
|
-
|
361
|
-
# 1st (empty) production.
|
362
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, []))
|
363
|
-
@callback.call(production, :'?', :first)
|
364
|
-
|
365
|
-
# 2nd production
|
366
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, [symbol]))
|
367
|
-
@callback.call(production, :'?', :second)
|
368
|
-
|
369
|
-
# Add the new symbol to the list of nonterminals.
|
370
|
-
@nonterms[new_symbol] = true
|
371
|
-
end
|
372
|
-
|
373
|
-
return new_symbol
|
374
|
-
end
|
375
|
-
|
376
|
-
# Builds productions used to eliminate the * EBNF operator.
|
377
|
-
#
|
378
|
-
# @param [Symbol] symbol Symbol to expand.
|
379
|
-
#
|
380
|
-
# @return [Symbol]
|
381
|
-
def get_star(symbol)
|
382
|
-
new_symbol = (symbol.to_s.downcase + '_star').to_sym
|
383
|
-
|
384
|
-
if not @productions_sym.has_key?(new_symbol)
|
385
|
-
# Add the items for the following productions:
|
386
|
-
#
|
387
|
-
# token_star: | token token_star
|
388
|
-
|
389
|
-
# 1st (empty) production
|
390
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, []))
|
391
|
-
@callback.call(production, :*, :first)
|
392
|
-
|
393
|
-
# 2nd production
|
394
|
-
production = self.add_production(Production.new(self.next_id, new_symbol, [new_symbol, symbol]))
|
395
|
-
@callback.call(production, :*, :second)
|
396
|
-
|
397
|
-
# Add the new symbol to the list of nonterminals.
|
398
|
-
@nonterms[new_symbol] = true
|
399
|
-
end
|
400
|
-
|
401
|
-
return new_symbol
|
402
|
-
end
|
403
|
-
|
404
|
-
# @return [Integer] ID for the next production to be defined.
|
425
|
+
# @return [Integer] ID for the next production to be defined.
|
405
426
|
def next_id
|
406
427
|
@production_counter += 1
|
407
428
|
end
|
408
429
|
|
409
|
-
#
|
410
|
-
# the grammar. These productions are named `symbol` and
|
411
|
-
# `symbol + '_elements'`
|
412
|
-
#
|
413
|
-
# @param [Symbol] symbol The name of the production to add.
|
414
|
-
# @param [String, Symbol, Array<String>] list_elements Expression(s) that may appear in the list.
|
415
|
-
# @param [Symbol] separator The list separator symbol.
|
416
|
-
#
|
417
|
-
# @return [void]
|
418
|
-
def nonempty_list_production(symbol, list_elements, separator)
|
419
|
-
# Add the items for the following productions:
|
420
|
-
#
|
421
|
-
# symbol_elements: list_elements.join('|')
|
422
|
-
#
|
423
|
-
# symbol: symbol_elements | symbol separator symbol_elements
|
424
|
-
|
425
|
-
if list_elements.is_a?(String) or list_elements.is_a?(Symbol)
|
426
|
-
list_elements = [list_elements.to_s]
|
427
|
-
|
428
|
-
elsif list_elements.is_a?(Array)
|
429
|
-
if list_elements.empty?
|
430
|
-
raise ArgumentError, 'Parameter list_elements must not be empty.'
|
431
|
-
else
|
432
|
-
list_elements.map! { |el| el.to_s }
|
433
|
-
end
|
434
|
-
|
435
|
-
else
|
436
|
-
raise ArgumentError, 'Parameter list_elements must be a String, Symbol, or array of Strings and Symbols.'
|
437
|
-
end
|
438
|
-
|
439
|
-
el_symbol = (symbol.to_s + '_elements').to_sym
|
440
|
-
|
441
|
-
# 1st Production
|
442
|
-
production = self.production(symbol, el_symbol.to_s).first
|
443
|
-
@callback.call(production, :nelp, :first)
|
444
|
-
|
445
|
-
# 2nd Production
|
446
|
-
production = self.production(symbol, "#{symbol} #{separator} #{el_symbol}").first
|
447
|
-
@callback.call(production, :nelp, :second)
|
448
|
-
|
449
|
-
# 3rd Productions
|
450
|
-
list_elements.each do |el|
|
451
|
-
production = self.production(el_symbol, el).first
|
452
|
-
@callback.call(production, :nelp, :third)
|
453
|
-
end
|
454
|
-
end
|
455
|
-
alias :nonempty_list :nonempty_list_production
|
456
|
-
|
457
|
-
# @return [Array<Symbol>] All terminal symbols used in the grammar's definition.
|
430
|
+
# @return [Set<Symbol>] All terminal symbols used in the grammar's definition.
|
458
431
|
def nonterms
|
459
|
-
@nonterms.
|
432
|
+
@nonterms.clone
|
460
433
|
end
|
461
434
|
|
462
435
|
# Builds a new production with the left-hand side value of *symbol*.
|
@@ -464,23 +437,29 @@ module RLTK # :nodoc:
|
|
464
437
|
# production. If *expression* is nil then *block* is evaluated, and
|
465
438
|
# expected to make one or more calls to {CFG#clause}.
|
466
439
|
#
|
467
|
-
# @param [Symbol]
|
468
|
-
# @param [String]
|
469
|
-
# @param [Proc]
|
440
|
+
# @param [Symbol] symbol The left-hand side of a production
|
441
|
+
# @param [String, Symbol] expression The right-hand side of a production
|
442
|
+
# @param [Proc] block Optional block for defining production clauses
|
470
443
|
#
|
471
|
-
# @return [Array<Production>]
|
444
|
+
# @return [Production, Array<Production>] A single production if called with an expression;
|
445
|
+
# an array of productions otherwise
|
472
446
|
def production(symbol, expression = nil, &block)
|
473
447
|
@production_buffer = Array.new
|
448
|
+
|
449
|
+
prev_lhs = @curr_lhs
|
474
450
|
@curr_lhs = symbol
|
475
451
|
|
452
|
+
ret_val =
|
476
453
|
if expression
|
477
454
|
self.clause(expression)
|
478
455
|
else
|
479
456
|
self.instance_exec(&block)
|
457
|
+
|
458
|
+
@production_buffer.clone
|
480
459
|
end
|
481
460
|
|
482
|
-
@curr_lhs =
|
483
|
-
return
|
461
|
+
@curr_lhs = prev_lhs
|
462
|
+
return ret_val
|
484
463
|
end
|
485
464
|
|
486
465
|
# If *by* is :sym, returns a hash of the grammar's productions, using
|
@@ -488,7 +467,7 @@ module RLTK # :nodoc:
|
|
488
467
|
# an array of productions is returned in the order of their
|
489
468
|
# definition.
|
490
469
|
#
|
491
|
-
# @param [:sym, :id]
|
470
|
+
# @param [:sym, :id] by The way in which productions should be returned.
|
492
471
|
#
|
493
472
|
# @return [Array<Production>, Hash{Symbol => Production}]
|
494
473
|
def productions(by = :sym)
|
@@ -503,7 +482,7 @@ module RLTK # :nodoc:
|
|
503
482
|
|
504
483
|
# Sets the start symbol for this grammar.
|
505
484
|
#
|
506
|
-
# @param [Symbol]
|
485
|
+
# @param [Symbol] symbol The new start symbol.
|
507
486
|
#
|
508
487
|
# @return [Symbol]
|
509
488
|
def start(symbol)
|
@@ -514,34 +493,34 @@ module RLTK # :nodoc:
|
|
514
493
|
@start_symbol = symbol
|
515
494
|
end
|
516
495
|
|
517
|
-
# @return [Array<Symbol>]
|
496
|
+
# @return [Array<Symbol>] All symbols used in the grammar's definition.
|
518
497
|
def symbols
|
519
498
|
self.terms + self.nonterms
|
520
499
|
end
|
521
500
|
|
522
|
-
# @return [
|
501
|
+
# @return [Set<Symbol>] All terminal symbols used in the grammar's definition.
|
523
502
|
def terms
|
524
|
-
@terms.
|
503
|
+
@terms.clone
|
525
504
|
end
|
526
505
|
|
527
506
|
# Oddly enough, the Production class represents a production in a
|
528
507
|
# context-free grammar.
|
529
508
|
class Production
|
530
|
-
# @return [Integer]
|
509
|
+
# @return [Integer] ID of this production.
|
531
510
|
attr_reader :id
|
532
511
|
|
533
|
-
# @return [Symbol]
|
512
|
+
# @return [Symbol] Left-hand side of this production.
|
534
513
|
attr_reader :lhs
|
535
514
|
|
536
|
-
# @return [Array<Symbol>]
|
515
|
+
# @return [Array<Symbol>] Right-hand side of this production.
|
537
516
|
attr_reader :rhs
|
538
517
|
|
539
518
|
# Instantiates a new Production object with the specified ID,
|
540
519
|
# and left- and right-hand sides.
|
541
520
|
#
|
542
|
-
# @param [Integer]
|
543
|
-
# @param [Symbol]
|
544
|
-
# @param [Array<Symbol>]
|
521
|
+
# @param [Integer] id ID number of this production.
|
522
|
+
# @param [Symbol] lhs Left-hand side of the production.
|
523
|
+
# @param [Array<Symbol>] rhs Right-hand side of the production.
|
545
524
|
def initialize(id, lhs, rhs)
|
546
525
|
@id = id
|
547
526
|
@lhs = lhs
|
@@ -551,35 +530,35 @@ module RLTK # :nodoc:
|
|
551
530
|
# Comparese on production to another. Returns true only if the
|
552
531
|
# left- and right- hand sides match.
|
553
532
|
#
|
554
|
-
# @param [Production]
|
533
|
+
# @param [Production] other Another production to compare to.
|
555
534
|
#
|
556
535
|
# @return [Boolean]
|
557
536
|
def ==(other)
|
558
537
|
self.lhs == other.lhs and self.rhs == other.rhs
|
559
538
|
end
|
560
539
|
|
561
|
-
# @return [Production]
|
540
|
+
# @return [Production] A new copy of this production.
|
562
541
|
def copy
|
563
542
|
Production.new(@id, @lhs, @rhs.clone)
|
564
543
|
end
|
565
544
|
|
566
|
-
# @return [Symbol]
|
545
|
+
# @return [Symbol] The last terminal in the right-hand side of the production.
|
567
546
|
def last_terminal
|
568
547
|
@rhs.inject(nil) { |m, sym| if CFG::is_terminal?(sym) then sym else m end }
|
569
548
|
end
|
570
549
|
|
571
|
-
# @return [Item]
|
550
|
+
# @return [Item] An Item based on this production.
|
572
551
|
def to_item
|
573
552
|
Item.new(0, @id, @lhs, @rhs)
|
574
553
|
end
|
575
554
|
|
576
555
|
# Returns a string representation of this production.
|
577
556
|
#
|
578
|
-
# @param [Integer]
|
557
|
+
# @param [Integer] padding The ammount of padding spaces to add to the beginning of the string.
|
579
558
|
#
|
580
559
|
# @return [String]
|
581
560
|
def to_s(padding = 0)
|
582
|
-
"#{format("%-#{padding}s", @lhs)} -> #{@rhs.map { |s| s.to_s }.join(' ')}"
|
561
|
+
"#{format("%-#{padding}s", @lhs)} -> #{@rhs.empty? ? 'ɛ' : @rhs.map { |s| s.to_s }.join(' ')}"
|
583
562
|
end
|
584
563
|
end
|
585
564
|
|
@@ -593,8 +572,8 @@ module RLTK # :nodoc:
|
|
593
572
|
# arguments (*args*) should be as specified by
|
594
573
|
# {Production#initialize}.
|
595
574
|
#
|
596
|
-
# @param [Integer]
|
597
|
-
# @param [Array<Object>]
|
575
|
+
# @param [Integer] dot Location of the dot in this Item.
|
576
|
+
# @param [Array<Object>] args (see {Production#initialize})
|
598
577
|
def initialize(dot, *args)
|
599
578
|
super(*args)
|
600
579
|
|
@@ -604,7 +583,7 @@ module RLTK # :nodoc:
|
|
604
583
|
|
605
584
|
# Compares two items.
|
606
585
|
#
|
607
|
-
# @param [Item]
|
586
|
+
# @param [Item] other Another item to compare to.
|
608
587
|
#
|
609
588
|
# @return [Boolean]
|
610
589
|
def ==(other)
|
@@ -628,21 +607,21 @@ module RLTK # :nodoc:
|
|
628
607
|
@dot == @rhs.length
|
629
608
|
end
|
630
609
|
|
631
|
-
# @return [Item]
|
610
|
+
# @return [Item] A new copy of this item.
|
632
611
|
def copy
|
633
612
|
Item.new(@dot, @id, @lhs, @rhs.clone)
|
634
613
|
end
|
635
614
|
|
636
615
|
# Returns the symbol located after the dot.
|
637
616
|
#
|
638
|
-
# @return [Symbol]
|
617
|
+
# @return [Symbol] Symbol located after the dot (at the index indicated by the {#dot} attribute).
|
639
618
|
def next_symbol
|
640
619
|
@rhs[@dot]
|
641
620
|
end
|
642
621
|
|
643
622
|
# Returns a string representation of this item.
|
644
623
|
#
|
645
|
-
# @param [Integer]
|
624
|
+
# @param [Integer] padding The ammount of padding spaces to add to the beginning of the string.
|
646
625
|
#
|
647
626
|
# @return [String]
|
648
627
|
def to_s(padding = 0)
|