rockit 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|