rockit 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/BUGS +13 -0
  2. data/LICENSE +280 -0
  3. data/README +172 -0
  4. data/TODO +53 -0
  5. data/VERSION +1 -0
  6. data/lib/packrat/grammar.rb +537 -0
  7. data/lib/rockit/prettyprint/box.rb +60 -0
  8. data/lib/rockit/prettyprint/renderer.rb +41 -0
  9. data/lib/rockit/prettyprint/text_renderer.rb +47 -0
  10. data/lib/rockit/tree/base.rb +223 -0
  11. data/lib/rockit/tree/enter_leave_visitor.rb +12 -0
  12. data/lib/rockit/tree/graphviz.rb +69 -0
  13. data/lib/rockit/tree/visitor.rb +12 -0
  14. data/lib/util/array_alternatives.rb +20 -0
  15. data/lib/util/enter_leave_visitor.rb +69 -0
  16. data/lib/util/graphviz_dot.rb +182 -0
  17. data/lib/util/string_location.rb +42 -0
  18. data/lib/util/visitor.rb +49 -0
  19. data/lib/util/visitor_combinators.rb +14 -0
  20. data/rakefile +200 -0
  21. data/tests/acceptance/packrat/minibasic/atest_minibasic.rb +45 -0
  22. data/tests/acceptance/packrat/minibasic/minibasic.rb +137 -0
  23. data/tests/acceptance/rockit/dparser/atest_any_operator.rb +33 -0
  24. data/tests/acceptance/rockit/dparser/atest_arithmetic_grammar.rb +30 -0
  25. data/tests/acceptance/rockit/dparser/atest_list_operator.rb +57 -0
  26. data/tests/acceptance/rockit/dparser/atest_mult_operator.rb +60 -0
  27. data/tests/acceptance/rockit/dparser/atest_operator_grammar.rb +61 -0
  28. data/tests/acceptance/rockit/dparser/atest_plus_operator.rb +55 -0
  29. data/tests/acceptance/rockit/dparser/atest_samples_calculator.rb +14 -0
  30. data/tests/acceptance/rockit/dparser/atest_samples_minibasic.rb +20 -0
  31. data/tests/acceptance/rockit/dparser/atest_samples_multifunccalculator.rb +36 -0
  32. data/tests/acceptance/rockit/dparser/atest_simple_grammar.rb +34 -0
  33. data/tests/acceptance/rockit/dparser/atest_speculative_code_action.rb +128 -0
  34. data/tests/acceptance/rockit/dparser/calc_tests_common.rb +103 -0
  35. data/tests/unit/packrat/test_interpreting_parser.rb +296 -0
  36. data/tests/unit/parse/utest_ebnf_grammar.rb +50 -0
  37. data/tests/unit/parse/utest_expand_grammar.rb +23 -0
  38. data/tests/unit/parse/utest_grammar.rb +160 -0
  39. data/tests/unit/rockit/assembler/llvm/utest_instructions.rb +41 -0
  40. data/tests/unit/rockit/assembler/llvm/utest_module.rb +19 -0
  41. data/tests/unit/rockit/prettyprint/utest_box.rb +44 -0
  42. data/tests/unit/rockit/tree/utest_tree_base.rb +301 -0
  43. data/tests/unit/rockit/tree/utest_tree_enter_leave_visitor.rb +69 -0
  44. data/tests/unit/rockit/tree/utest_tree_visitor.rb +63 -0
  45. data/tests/unit/rockit/utest_grammar.rb +145 -0
  46. data/tests/unit/rockit/utest_grammar_symbol.rb +11 -0
  47. data/tests/unit/rockit/utest_maybe_operator.rb +12 -0
  48. data/tests/unit/rockit/utest_regexp_terminal.rb +45 -0
  49. data/tests/unit/rockit/utest_repetition_operators.rb +35 -0
  50. data/tests/unit/rockit/utest_rule.rb +23 -0
  51. data/tests/unit/rockit/utest_string_terminal.rb +40 -0
  52. data/tests/unit/util/utest_array_alternatives.rb +23 -0
  53. data/tests/unit/util/utest_enter_leave_visitor.rb +89 -0
  54. data/tests/unit/util/utest_string_location.rb +42 -0
  55. data/tests/unit/util/utest_visitor.rb +92 -0
  56. data/tests/unit/util/utest_visitor_combinators.rb +64 -0
  57. 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