rockit 0.7.1
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.
- data/BUGS +13 -0
- data/LICENSE +280 -0
- data/README +172 -0
- data/TODO +53 -0
- data/VERSION +1 -0
- data/lib/packrat/grammar.rb +537 -0
- data/lib/rockit/prettyprint/box.rb +60 -0
- data/lib/rockit/prettyprint/renderer.rb +41 -0
- data/lib/rockit/prettyprint/text_renderer.rb +47 -0
- data/lib/rockit/tree/base.rb +223 -0
- data/lib/rockit/tree/enter_leave_visitor.rb +12 -0
- data/lib/rockit/tree/graphviz.rb +69 -0
- data/lib/rockit/tree/visitor.rb +12 -0
- data/lib/util/array_alternatives.rb +20 -0
- data/lib/util/enter_leave_visitor.rb +69 -0
- data/lib/util/graphviz_dot.rb +182 -0
- data/lib/util/string_location.rb +42 -0
- data/lib/util/visitor.rb +49 -0
- data/lib/util/visitor_combinators.rb +14 -0
- data/rakefile +200 -0
- data/tests/acceptance/packrat/minibasic/atest_minibasic.rb +45 -0
- data/tests/acceptance/packrat/minibasic/minibasic.rb +137 -0
- data/tests/acceptance/rockit/dparser/atest_any_operator.rb +33 -0
- data/tests/acceptance/rockit/dparser/atest_arithmetic_grammar.rb +30 -0
- data/tests/acceptance/rockit/dparser/atest_list_operator.rb +57 -0
- data/tests/acceptance/rockit/dparser/atest_mult_operator.rb +60 -0
- data/tests/acceptance/rockit/dparser/atest_operator_grammar.rb +61 -0
- data/tests/acceptance/rockit/dparser/atest_plus_operator.rb +55 -0
- data/tests/acceptance/rockit/dparser/atest_samples_calculator.rb +14 -0
- data/tests/acceptance/rockit/dparser/atest_samples_minibasic.rb +20 -0
- data/tests/acceptance/rockit/dparser/atest_samples_multifunccalculator.rb +36 -0
- data/tests/acceptance/rockit/dparser/atest_simple_grammar.rb +34 -0
- data/tests/acceptance/rockit/dparser/atest_speculative_code_action.rb +128 -0
- data/tests/acceptance/rockit/dparser/calc_tests_common.rb +103 -0
- data/tests/unit/packrat/test_interpreting_parser.rb +296 -0
- data/tests/unit/parse/utest_ebnf_grammar.rb +50 -0
- data/tests/unit/parse/utest_expand_grammar.rb +23 -0
- data/tests/unit/parse/utest_grammar.rb +160 -0
- data/tests/unit/rockit/assembler/llvm/utest_instructions.rb +41 -0
- data/tests/unit/rockit/assembler/llvm/utest_module.rb +19 -0
- data/tests/unit/rockit/prettyprint/utest_box.rb +44 -0
- data/tests/unit/rockit/tree/utest_tree_base.rb +301 -0
- data/tests/unit/rockit/tree/utest_tree_enter_leave_visitor.rb +69 -0
- data/tests/unit/rockit/tree/utest_tree_visitor.rb +63 -0
- data/tests/unit/rockit/utest_grammar.rb +145 -0
- data/tests/unit/rockit/utest_grammar_symbol.rb +11 -0
- data/tests/unit/rockit/utest_maybe_operator.rb +12 -0
- data/tests/unit/rockit/utest_regexp_terminal.rb +45 -0
- data/tests/unit/rockit/utest_repetition_operators.rb +35 -0
- data/tests/unit/rockit/utest_rule.rb +23 -0
- data/tests/unit/rockit/utest_string_terminal.rb +40 -0
- data/tests/unit/util/utest_array_alternatives.rb +23 -0
- data/tests/unit/util/utest_enter_leave_visitor.rb +89 -0
- data/tests/unit/util/utest_string_location.rb +42 -0
- data/tests/unit/util/utest_visitor.rb +92 -0
- data/tests/unit/util/utest_visitor_combinators.rb +64 -0
- metadata +112 -0
@@ -0,0 +1,537 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Packrat; end
|
4
|
+
|
5
|
+
# A version of puts that limits output to 80 columns width
|
6
|
+
def lputs(str)
|
7
|
+
tabs = 0
|
8
|
+
len = (0...(str.length)).inject(0) do |l,i|
|
9
|
+
if str[i,1] == "\t"
|
10
|
+
tabs += 1
|
11
|
+
l + 8
|
12
|
+
else
|
13
|
+
l + 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
if len > 80
|
17
|
+
s = str[0,80-3-(tabs*8)] + "..."
|
18
|
+
else
|
19
|
+
s = str
|
20
|
+
end
|
21
|
+
puts s
|
22
|
+
end
|
23
|
+
|
24
|
+
class Regexp
|
25
|
+
def to_packrat_grammar_element
|
26
|
+
Packrat::RegexpLiteral.new(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Symbol
|
31
|
+
def to_packrat_grammar_element
|
32
|
+
Packrat::RuleRef.new(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class String
|
37
|
+
def to_packrat_grammar_element
|
38
|
+
Packrat::StringLiteral.new(self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Packrat::GrammarElement
|
43
|
+
def to_packrat_grammar_element; self; end
|
44
|
+
# A GrammarElement is hidden if it does not produce a result that should
|
45
|
+
# be used in any way. This is mostly used for whitespace.
|
46
|
+
attr_accessor :hidden
|
47
|
+
end
|
48
|
+
|
49
|
+
class Packrat::RegexpLiteral < Packrat::GrammarElement
|
50
|
+
def initialize(re)
|
51
|
+
@re = re
|
52
|
+
end
|
53
|
+
def inspect; @re.inspect; end
|
54
|
+
end
|
55
|
+
|
56
|
+
# A StringLiteral works like a RegexpLiteral. The only reason we use
|
57
|
+
# a special class for it is so that we can inspect it in a more natural way
|
58
|
+
# (as a string instead of a Regexp).
|
59
|
+
class Packrat::StringLiteral < Packrat::RegexpLiteral
|
60
|
+
def initialize(str)
|
61
|
+
super(Regexp.new(Regexp.escape(str)))
|
62
|
+
@str = str
|
63
|
+
end
|
64
|
+
def inspect; @str.inspect; end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Packrat::RuleRef < Packrat::GrammarElement
|
68
|
+
attr_reader :rule_name
|
69
|
+
def initialize(ruleName)
|
70
|
+
@rule_name = ruleName
|
71
|
+
end
|
72
|
+
def inspect; @rule_name.inspect; end
|
73
|
+
end
|
74
|
+
|
75
|
+
# A grammar Rule is a set of one or more Productions for the same
|
76
|
+
# (lhs) nonterminal. It makes an ordered choice between its productions
|
77
|
+
# by trying to parse with them in order.
|
78
|
+
class Packrat::Rule
|
79
|
+
attr_reader :name, :prods, :grammar
|
80
|
+
def initialize(name, prods = [])
|
81
|
+
@name, @prods = name, prods
|
82
|
+
end
|
83
|
+
def grammar=(grammar)
|
84
|
+
@grammar = grammar
|
85
|
+
@prods.each {|p| p.grammar = grammar}
|
86
|
+
end
|
87
|
+
def <<(prod)
|
88
|
+
@prods << prod
|
89
|
+
end
|
90
|
+
def inspect
|
91
|
+
s = "#{name.to_s} ->"
|
92
|
+
"\n" + s + " " +
|
93
|
+
@prods.map {|p| p.inspect(false)}.join("\n" +
|
94
|
+
" " * (s.length - 1) + "| ")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# A grammar Production is sequence of rhs elements describing how the lhs
|
99
|
+
# symbol should be parsed.
|
100
|
+
class Packrat::Production
|
101
|
+
attr_accessor :grammar
|
102
|
+
attr_reader :name, :rhs, :result_modifier
|
103
|
+
def initialize(name, rhs)
|
104
|
+
@name, @rhs = name, rhs
|
105
|
+
if Packrat::ResultModifier === rhs.last
|
106
|
+
@result_modifier = @rhs.pop
|
107
|
+
else
|
108
|
+
# Default modifier is to create a Sexpr with the production name
|
109
|
+
# as the head of the returned array.
|
110
|
+
@result_modifier = Packrat::SexprModifier.new(@name)
|
111
|
+
end
|
112
|
+
@rhs.map! {|e| e.to_packrat_grammar_element}
|
113
|
+
end
|
114
|
+
def inspect(withLhs = true)
|
115
|
+
rhs = @rhs.map {|e| e.inspect}.join(' ')
|
116
|
+
withLhs ? "#{name.to_s} -> " + rhs : rhs
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Report results of parsing a prod or grammar element
|
121
|
+
class Packrat::ErrorReporter < Packrat::GrammarElement
|
122
|
+
def initialize(sub)
|
123
|
+
@sub = sub
|
124
|
+
end
|
125
|
+
def parse(parser)
|
126
|
+
res = @sub.parse(parser)
|
127
|
+
if res == false
|
128
|
+
lputs "\t\t\t FAIL #{@sub.inspect}"
|
129
|
+
puts ""
|
130
|
+
else
|
131
|
+
lputs " Match #{@sub.inspect}"
|
132
|
+
puts ""
|
133
|
+
end
|
134
|
+
res
|
135
|
+
end
|
136
|
+
def method_missing(method, *args)
|
137
|
+
@sub.send(method, *args)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module Packrat::GrammarBuild
|
142
|
+
attr_reader :start
|
143
|
+
def start_symbol(name); @start = name; end
|
144
|
+
def rules; @rules ||= (Hash.new {|h,k| h[k] = Packrat::Rule.new(k)}); end
|
145
|
+
def rule(name, rhss)
|
146
|
+
rhss.each {|rhs| prod(name, rhs)}
|
147
|
+
end
|
148
|
+
def prod(name, rhs)
|
149
|
+
pr = Packrat::Production.new(name, rhs)
|
150
|
+
pr = Packrat::ErrorReporter.new(pr) if $DEBUG
|
151
|
+
rules[name] << pr
|
152
|
+
end
|
153
|
+
def [](name); @rules[name]; end
|
154
|
+
def start_rule; self[self.start]; end
|
155
|
+
def hidden(elem)
|
156
|
+
e = elem.to_packrat_grammar_element
|
157
|
+
e.hidden = true
|
158
|
+
e
|
159
|
+
end
|
160
|
+
# Finalize the building of the grammar by conducting postprocessing.
|
161
|
+
def finalize!
|
162
|
+
postprocess_set_grammar_on_rules
|
163
|
+
postprocess_create_ast_classes
|
164
|
+
end
|
165
|
+
def postprocess_set_grammar_on_rules
|
166
|
+
rules.values.each {|r| r.grammar = self}
|
167
|
+
end
|
168
|
+
def postprocess_create_ast_classes
|
169
|
+
each_prod do |p|
|
170
|
+
if Packrat::ASTBuilder === p.result_modifier
|
171
|
+
ast_class(p.result_modifier.name, p)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
def each_prod
|
176
|
+
rules.values.each {|r| r.prods.each {|p| yield(p)}}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Packrat::Grammar
|
181
|
+
extend Packrat::GrammarBuild
|
182
|
+
|
183
|
+
class <<self
|
184
|
+
def new(&grammarBuilder)
|
185
|
+
# We must name all the sub-classes so that its AST tree classes
|
186
|
+
# are named.
|
187
|
+
@num_grammars ||= 0
|
188
|
+
const_set("Grammar" + (@num_grammars += 1).to_s,
|
189
|
+
klass = Class.new(self))
|
190
|
+
# Add a module to hold the AST classes
|
191
|
+
klass.const_set("ASTs", Module.new)
|
192
|
+
klass.module_eval(&grammarBuilder)
|
193
|
+
klass
|
194
|
+
end
|
195
|
+
|
196
|
+
def interpreting_parser(klass = Packrat::InterpretingParser)
|
197
|
+
self.finalize!
|
198
|
+
klass.new_subclass(self)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class Packrat::Repeat < Packrat::GrammarElement
|
204
|
+
def initialize(subElement, minimumReps = 0, maximumReps = false)
|
205
|
+
@min, @max = minimumReps, maximumReps
|
206
|
+
@sub = subElement.to_packrat_grammar_element
|
207
|
+
end
|
208
|
+
def inspect
|
209
|
+
subi = @sub.inspect
|
210
|
+
return "mult(#{subi})" if @min == 0 && @max == false
|
211
|
+
return "plus(#{subi})" if @min == 1 && @max == false
|
212
|
+
return "rep(@min, @max, #{subi})"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
module Packrat::GrammarBuild
|
217
|
+
def plus(element); Packrat::Repeat.new(element, 1, false); end
|
218
|
+
def mult(element); Packrat::Repeat.new(element, 0, false); end
|
219
|
+
def rep(min, max, element); Packrat::Repeat.new(element, min, max); end
|
220
|
+
end
|
221
|
+
|
222
|
+
class Packrat::Maybe < Packrat::GrammarElement
|
223
|
+
def initialize(sub)
|
224
|
+
@sub = sub.to_packrat_grammar_element
|
225
|
+
end
|
226
|
+
def parse(parser)
|
227
|
+
res = @sub.parse(parser)
|
228
|
+
return res if res
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
def inspect
|
232
|
+
"(#{@sub.inspect})?"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
module Packrat::GrammarBuild
|
237
|
+
def maybe(element); Packrat::Maybe.new(element); end
|
238
|
+
end
|
239
|
+
|
240
|
+
# The last element of a prod can be a result modifier that modifies
|
241
|
+
# the result to be returned by the prod when parsing.
|
242
|
+
class Packrat::ResultModifier
|
243
|
+
end
|
244
|
+
|
245
|
+
# Create a Sexpr based on the name of the matched production and the
|
246
|
+
# result-array.
|
247
|
+
class Packrat::SexprModifier < Packrat::ResultModifier
|
248
|
+
def initialize(name)
|
249
|
+
@name = name
|
250
|
+
end
|
251
|
+
def modify_result(prod, result)
|
252
|
+
# Add the production name in the front
|
253
|
+
result.unshift @name
|
254
|
+
result
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Lift one of the sub-results as the result from parsing a production.
|
259
|
+
class Packrat::LiftModifier < Packrat::ResultModifier
|
260
|
+
def initialize(valueIndex, &block)
|
261
|
+
@value_index = valueIndex
|
262
|
+
@block = block
|
263
|
+
end
|
264
|
+
def modify_result(prod, result)
|
265
|
+
extracted_result = result[@value_index]
|
266
|
+
@block? @block.call(extracted_result) : extracted_result
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
module Packrat::GrammarBuild
|
271
|
+
def sexpr(name); Packrat::SexprModifier.new(name); end
|
272
|
+
def lift(index, &b); Packrat::LiftModifier.new(index, &b); end
|
273
|
+
end
|
274
|
+
|
275
|
+
module Packrat::GrammarBuild
|
276
|
+
# any() can be implemented in many ways but if all the sub-elements are
|
277
|
+
# strings we simply create a regexp matching any of them. If they are not
|
278
|
+
# all strings we add an internal rule with the alternatives as productions.
|
279
|
+
def any(*subs)
|
280
|
+
if subs.all? {|e| String === e}
|
281
|
+
re_string = subs.map {|s| "(" + Regexp.escape(s) + ")"}.join("|")
|
282
|
+
Packrat::RegexpLiteral.new(Regexp.new(re_string))
|
283
|
+
else
|
284
|
+
name = internal_rule_name()
|
285
|
+
rule(name, subs.map {|s| [s, lift(0)]})
|
286
|
+
Packrat::RuleRef.new(name)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def next_internal_rule_num
|
291
|
+
@internal_rule_counter ||= 0
|
292
|
+
@internal_rule_counter += 1
|
293
|
+
end
|
294
|
+
|
295
|
+
def internal_rule_name()
|
296
|
+
("_r_" + next_internal_rule_num.to_s).intern
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class Packrat::EOS < Packrat::GrammarElement
|
301
|
+
def parse(parser)
|
302
|
+
parser.eos? ? 0 : false
|
303
|
+
end
|
304
|
+
def inspect; "EOS"; end
|
305
|
+
end
|
306
|
+
|
307
|
+
module Packrat::GrammarBuild
|
308
|
+
def eos(); hidden(Packrat::EOS.new); end
|
309
|
+
end
|
310
|
+
|
311
|
+
class Packrat::ASTBuilder < Packrat::ResultModifier
|
312
|
+
attr_reader :name
|
313
|
+
def initialize(nodeName)
|
314
|
+
@name = nodeName
|
315
|
+
end
|
316
|
+
def modify_result(prod, result)
|
317
|
+
astklass = prod.grammar.ast_class(@name, prod)
|
318
|
+
astklass.new(*result)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
module Packrat::GrammarBuild
|
323
|
+
def ast(name)
|
324
|
+
Packrat::ASTBuilder.new(name)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Return the ast class with the given <nodeName> for the given <production>.
|
328
|
+
# If not previously created we create it and add it to the Tree module.
|
329
|
+
def ast_class(name, prod)
|
330
|
+
acn = ast_class_name(name)
|
331
|
+
begin
|
332
|
+
const_get("ASTs").const_get(acn)
|
333
|
+
rescue
|
334
|
+
const_get("ASTs").const_set(acn, make_ast_class(acn, prod))
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def ast_class_name(name)
|
339
|
+
s = name.to_s
|
340
|
+
s[0,1].upcase + s[1..-1]
|
341
|
+
end
|
342
|
+
|
343
|
+
def make_ast_class(klassName, production)
|
344
|
+
Packrat::AST.new_subclass(klassName, production)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Node in AST trees.
|
349
|
+
class Packrat::AST
|
350
|
+
class <<self
|
351
|
+
attr_accessor :sig
|
352
|
+
def new_subclass(name, production)
|
353
|
+
klass = Class.new(self)
|
354
|
+
klass.sig = extract_sig(production)
|
355
|
+
# Add accessor methods for all symbols in the sig
|
356
|
+
num_strings = 0
|
357
|
+
klass.sig.each_with_index do |sn, i|
|
358
|
+
if Symbol === sn
|
359
|
+
# We should subtract the num_strings in the index below
|
360
|
+
# if we optimize this so that non-named children are never
|
361
|
+
# added to the result array!
|
362
|
+
klass.module_eval %{
|
363
|
+
def #{sn.to_s}
|
364
|
+
@children[#{i}]
|
365
|
+
end
|
366
|
+
}
|
367
|
+
elsif String === sn
|
368
|
+
num_strings += 1
|
369
|
+
end
|
370
|
+
end
|
371
|
+
klass
|
372
|
+
end
|
373
|
+
|
374
|
+
# Return a sig for the given <production>. The sig has strings in the
|
375
|
+
# positions where the production rhs has a String or StringLiteral,
|
376
|
+
# has symbols in the positions where a rhs element refer to another
|
377
|
+
# production, and has nil in other positions.
|
378
|
+
def extract_sig(production)
|
379
|
+
production.rhs.map do |e|
|
380
|
+
case e
|
381
|
+
when String
|
382
|
+
e
|
383
|
+
when Packrat::StringLiteral
|
384
|
+
e.inspect # gives us the string itself
|
385
|
+
when Packrat::RuleRef
|
386
|
+
sub_element_name(e.rule_name)
|
387
|
+
else
|
388
|
+
nil # Expand this so that names are lifted out of Maybe, and "s" is added when plus and mult etc
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def sub_element_name(name)
|
394
|
+
parts = name.to_s.split(/([A-Z][a-z0-9]*)/).select {|e| e.length > 0}
|
395
|
+
parts.map {|p| p.downcase}.join("_").intern
|
396
|
+
end
|
397
|
+
|
398
|
+
def [](*args); new(*args); end
|
399
|
+
end
|
400
|
+
|
401
|
+
def initialize(*children)
|
402
|
+
@children = children
|
403
|
+
end
|
404
|
+
attr_reader :children
|
405
|
+
|
406
|
+
def [](index); @children[index]; end
|
407
|
+
|
408
|
+
def ==(other)
|
409
|
+
self.class == other.class && @children == other.children
|
410
|
+
end
|
411
|
+
|
412
|
+
def inspect
|
413
|
+
self.class.inspect.split("::").last + "[" +
|
414
|
+
@children.map {|c| c.inspect}.join(", ") + "]"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
class Packrat::InterpretingParser
|
419
|
+
class <<self
|
420
|
+
attr_accessor :grammar
|
421
|
+
def new_subclass(grammar)
|
422
|
+
klass = Class.new(self)
|
423
|
+
klass.grammar = grammar
|
424
|
+
klass
|
425
|
+
end
|
426
|
+
def parse_string(str)
|
427
|
+
# We always add a whitespace since StringScanner cannot match /\s*/
|
428
|
+
# (typically used as whitespace) at EOS
|
429
|
+
new(str + " ").parse_string
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
attr_reader :results, :grammar
|
434
|
+
|
435
|
+
def initialize(string)
|
436
|
+
@str = string
|
437
|
+
@s = StringScanner.new(string)
|
438
|
+
@grammar = self.class.grammar
|
439
|
+
end
|
440
|
+
|
441
|
+
def parse_string
|
442
|
+
@grammar.start_rule.parse(self)
|
443
|
+
end
|
444
|
+
|
445
|
+
# Get and Set current position in string.
|
446
|
+
def pos; @s.pos; end
|
447
|
+
def pos=(p); @s.pos = p; end
|
448
|
+
|
449
|
+
def eos?; @s.eos?; end
|
450
|
+
|
451
|
+
# Extract a lexeme of length <len> from the given <pos>.
|
452
|
+
def lexeme(pos, len)
|
453
|
+
@str[pos, len]
|
454
|
+
end
|
455
|
+
|
456
|
+
# Skip using <re> at the current position in the string. Returns nil
|
457
|
+
# if the re did not match or the length of the match if it matched.
|
458
|
+
def skip(re)
|
459
|
+
@s.skip(re)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
class Packrat::ErrorLoggingInterpretingParser < Packrat::InterpretingParser
|
464
|
+
def skip(re)
|
465
|
+
oldpos = pos
|
466
|
+
r = super
|
467
|
+
if r
|
468
|
+
endp = pos - ((r > 0) ? 1 : 0)
|
469
|
+
puts "#{oldpos.to_s.rjust(3)} - #{endp.to_s.ljust(3)} #{lexeme(oldpos,r).inspect} #{re.inspect}"
|
470
|
+
else
|
471
|
+
puts "\t\t\tNOT #{re.inspect}"
|
472
|
+
end
|
473
|
+
r
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
class Packrat::RegexpLiteral
|
478
|
+
def parse(parser)
|
479
|
+
oldpos = parser.pos
|
480
|
+
len = parser.skip(@re)
|
481
|
+
len ? parser.lexeme(oldpos, len) : false
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
class Packrat::Production
|
486
|
+
def parse(parser)
|
487
|
+
result = []
|
488
|
+
@rhs.each do |e|
|
489
|
+
res = e.parse(parser)
|
490
|
+
if res == false
|
491
|
+
return false
|
492
|
+
else
|
493
|
+
result << res unless e.hidden
|
494
|
+
end
|
495
|
+
end
|
496
|
+
return @result_modifier.modify_result(self, result)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
class Packrat::Rule
|
501
|
+
def parse(parser)
|
502
|
+
prods.each do |prod|
|
503
|
+
oldpos = parser.pos
|
504
|
+
res = prod.parse(parser)
|
505
|
+
return res if res
|
506
|
+
parser.pos = oldpos
|
507
|
+
end
|
508
|
+
return false
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
class Packrat::RuleRef
|
513
|
+
def parse(parser)
|
514
|
+
parser.grammar[@rule_name].parse(parser)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
class Packrat::Repeat
|
519
|
+
def parse(parser)
|
520
|
+
result_list = []
|
521
|
+
oldpos = parser.pos
|
522
|
+
while (res = @sub.parse(parser))
|
523
|
+
result_list << res
|
524
|
+
end
|
525
|
+
if valid_result?(result_list)
|
526
|
+
return result_list
|
527
|
+
else
|
528
|
+
parser.pos = oldpos
|
529
|
+
return false
|
530
|
+
end
|
531
|
+
end
|
532
|
+
def valid_result?(list)
|
533
|
+
return false if @min && list.length < @min
|
534
|
+
return false if @max && list.length > @max
|
535
|
+
true
|
536
|
+
end
|
537
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# A Formatting Language similar to BOX described in
|
2
|
+
# [vandenbrand1996a] and [dejonge2003a]
|
3
|
+
#
|
4
|
+
module Rockit
|
5
|
+
module PrettyPrint
|
6
|
+
module Box
|
7
|
+
# An element of the Box language
|
8
|
+
class Term
|
9
|
+
end
|
10
|
+
|
11
|
+
# H formats its elements horizontally with a given number of spaces
|
12
|
+
# in between.
|
13
|
+
class H < Term
|
14
|
+
attr_reader :num_hdelims, :elements
|
15
|
+
|
16
|
+
def initialize(numHorizontalDelims = 1, *elements)
|
17
|
+
@num_hdelims = numHdelims
|
18
|
+
@elements = elements
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# H formats its elements horizontally with a given number of spaces
|
23
|
+
# in between.
|
24
|
+
class V < Term
|
25
|
+
attr_reader :elements
|
26
|
+
|
27
|
+
def initialize(*elements)
|
28
|
+
@elements = elements
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Epsilon is a H operator with no spaces in between
|
33
|
+
class Epsilon < H
|
34
|
+
def initialize(*elements)
|
35
|
+
super(0, *elements)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'rockit/prettyprint/text_renderer'
|
43
|
+
|
44
|
+
# We must do the def's below after requiring the text_renderer
|
45
|
+
# since there is a circular dependency between the definitions.
|
46
|
+
module Rockit
|
47
|
+
module PrettyPrint
|
48
|
+
module Box
|
49
|
+
class Term
|
50
|
+
def render
|
51
|
+
render_on(Box::TextRenderer.new)
|
52
|
+
end
|
53
|
+
|
54
|
+
def render_on(renderer)
|
55
|
+
renderer.render(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Renderers for Box
|
2
|
+
#
|
3
|
+
module Rockit
|
4
|
+
module PrettyPrint
|
5
|
+
module Box
|
6
|
+
class Renderer
|
7
|
+
def reset_buffer
|
8
|
+
@buffer = ""
|
9
|
+
end
|
10
|
+
|
11
|
+
def string_buffer
|
12
|
+
@buffer
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(boxTree)
|
16
|
+
reset_buffer
|
17
|
+
render_term(boxTree)
|
18
|
+
string_buffer
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(string)
|
22
|
+
@buffer << string
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_term(boxTree)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_hdelim(num)
|
30
|
+
render_term(horizontal_delim * num)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class String
|
38
|
+
def render_as_text(renderer)
|
39
|
+
renderer << self
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Basic Text Renderer for Box
|
2
|
+
#
|
3
|
+
require 'rockit/prettyprint/renderer'
|
4
|
+
|
5
|
+
module Rockit
|
6
|
+
module PrettyPrint
|
7
|
+
module Box
|
8
|
+
class TextRenderer < Renderer
|
9
|
+
attr_reader :max_horizontal
|
10
|
+
attr_reader :horizontal_delim, :vertical_delim
|
11
|
+
|
12
|
+
def initialize(maxHorizontal = 80, horizontalDelim = " ",
|
13
|
+
verticalDelim = "\n")
|
14
|
+
@max_horizontal = maxHorizontal
|
15
|
+
@horizontal_delim = horizontalDelim
|
16
|
+
@vertical_delim = verticalDelim
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_term(term)
|
20
|
+
# Instead of fiddling with visitors we simply add a uniquely named
|
21
|
+
# method render_as_text to each Box term we support.
|
22
|
+
term.render_as_text(self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class H < Term
|
27
|
+
def render_as_text(renderer)
|
28
|
+
elements[0..-2].each do |e|
|
29
|
+
renderer.render_term(e)
|
30
|
+
renderer.render_hdelim(num_spaces)
|
31
|
+
end
|
32
|
+
renderer.render_term(elements[-1])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class V < Term
|
37
|
+
def render_as_text(renderer)
|
38
|
+
elements[0..-2].each do |e|
|
39
|
+
renderer.render_term(e)
|
40
|
+
renderer << "\n"
|
41
|
+
end
|
42
|
+
renderer.render_term(elements[-1])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|