antlr3 1.2.3
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/ANTLR-LICENSE.txt +26 -0
- data/History.txt +66 -0
- data/README.txt +139 -0
- data/bin/antlr4ruby +33 -0
- data/java/RubyTarget.java +524 -0
- data/java/antlr-full-3.2.1.jar +0 -0
- data/lib/antlr3.rb +176 -0
- data/lib/antlr3/constants.rb +88 -0
- data/lib/antlr3/debug.rb +701 -0
- data/lib/antlr3/debug/event-hub.rb +210 -0
- data/lib/antlr3/debug/record-event-listener.rb +25 -0
- data/lib/antlr3/debug/rule-tracer.rb +55 -0
- data/lib/antlr3/debug/socket.rb +360 -0
- data/lib/antlr3/debug/trace-event-listener.rb +92 -0
- data/lib/antlr3/dfa.rb +247 -0
- data/lib/antlr3/dot.rb +174 -0
- data/lib/antlr3/error.rb +657 -0
- data/lib/antlr3/main.rb +561 -0
- data/lib/antlr3/modes/ast-builder.rb +41 -0
- data/lib/antlr3/modes/filter.rb +56 -0
- data/lib/antlr3/profile.rb +322 -0
- data/lib/antlr3/recognizers.rb +1280 -0
- data/lib/antlr3/streams.rb +985 -0
- data/lib/antlr3/streams/interactive.rb +91 -0
- data/lib/antlr3/streams/rewrite.rb +412 -0
- data/lib/antlr3/test/call-stack.rb +57 -0
- data/lib/antlr3/test/config.rb +23 -0
- data/lib/antlr3/test/core-extensions.rb +269 -0
- data/lib/antlr3/test/diff.rb +165 -0
- data/lib/antlr3/test/functional.rb +207 -0
- data/lib/antlr3/test/grammar.rb +371 -0
- data/lib/antlr3/token.rb +592 -0
- data/lib/antlr3/tree.rb +1415 -0
- data/lib/antlr3/tree/debug.rb +163 -0
- data/lib/antlr3/tree/visitor.rb +84 -0
- data/lib/antlr3/tree/wizard.rb +481 -0
- data/lib/antlr3/util.rb +149 -0
- data/lib/antlr3/version.rb +27 -0
- data/samples/ANTLRv3Grammar.g +621 -0
- data/samples/Cpp.g +749 -0
- data/templates/AST.stg +335 -0
- data/templates/ASTDbg.stg +40 -0
- data/templates/ASTParser.stg +153 -0
- data/templates/ASTTreeParser.stg +272 -0
- data/templates/Dbg.stg +192 -0
- data/templates/Ruby.stg +1514 -0
- data/test/functional/ast-output/auto-ast.rb +797 -0
- data/test/functional/ast-output/construction.rb +555 -0
- data/test/functional/ast-output/hetero-nodes.rb +753 -0
- data/test/functional/ast-output/rewrites.rb +1327 -0
- data/test/functional/ast-output/tree-rewrite.rb +1662 -0
- data/test/functional/debugging/debug-mode.rb +689 -0
- data/test/functional/debugging/profile-mode.rb +165 -0
- data/test/functional/debugging/rule-tracing.rb +74 -0
- data/test/functional/delegation/import.rb +379 -0
- data/test/functional/lexer/basic.rb +559 -0
- data/test/functional/lexer/filter-mode.rb +245 -0
- data/test/functional/lexer/nuances.rb +47 -0
- data/test/functional/lexer/properties.rb +104 -0
- data/test/functional/lexer/syn-pred.rb +32 -0
- data/test/functional/lexer/xml.rb +206 -0
- data/test/functional/main/main-scripts.rb +245 -0
- data/test/functional/parser/actions.rb +224 -0
- data/test/functional/parser/backtracking.rb +244 -0
- data/test/functional/parser/basic.rb +282 -0
- data/test/functional/parser/calc.rb +98 -0
- data/test/functional/parser/ll-star.rb +143 -0
- data/test/functional/parser/nuances.rb +165 -0
- data/test/functional/parser/predicates.rb +103 -0
- data/test/functional/parser/properties.rb +242 -0
- data/test/functional/parser/rule-methods.rb +132 -0
- data/test/functional/parser/scopes.rb +274 -0
- data/test/functional/token-rewrite/basic.rb +318 -0
- data/test/functional/token-rewrite/via-parser.rb +100 -0
- data/test/functional/tree-parser/basic.rb +750 -0
- data/test/unit/sample-input/file-stream-1 +2 -0
- data/test/unit/sample-input/teststreams.input2 +2 -0
- data/test/unit/test-dfa.rb +52 -0
- data/test/unit/test-exceptions.rb +44 -0
- data/test/unit/test-recognizers.rb +55 -0
- data/test/unit/test-scheme.rb +62 -0
- data/test/unit/test-streams.rb +459 -0
- data/test/unit/test-tree-wizard.rb +535 -0
- data/test/unit/test-trees.rb +854 -0
- metadata +205 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module ANTLR3
|
5
|
+
module Debug
|
6
|
+
=begin rdoc ANTLR3::Debug::EventListener
|
7
|
+
|
8
|
+
A listener that simply records text representations of the events.
|
9
|
+
Useful for debugging the debugging facility ;)
|
10
|
+
Subclasses can override the record() method (which defaults to printing to
|
11
|
+
stdout) to record the events in a different way.
|
12
|
+
|
13
|
+
=end
|
14
|
+
class TraceEventListener
|
15
|
+
include EventListener
|
16
|
+
|
17
|
+
def initialize(adaptor = nil, device = $stderr)
|
18
|
+
super()
|
19
|
+
@device = device
|
20
|
+
@adaptor = adaptor ||= ANTLR3::AST::CommonTreeAdaptor.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def record(event_message, *interpolation_arguments)
|
24
|
+
event_message = event_message.to_s << "\n"
|
25
|
+
@device.printf(event_message, *interpolation_arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def enter_alternative( alt_number )
|
29
|
+
record "(%s): number=%s", __method__, alt_number
|
30
|
+
end
|
31
|
+
|
32
|
+
def enter_rule( grammar_file_name, rule_name )
|
33
|
+
record "(%s): rule=%s", __method__, rule_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def exit_rule( grammar_file_name, rule_name )
|
37
|
+
record "(%s): rule=%s", __method__, rule_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def enter_subrule( decision_number )
|
41
|
+
record "(%s): decision=%s", __method__, decision_number
|
42
|
+
end
|
43
|
+
|
44
|
+
def exit_subrule( decision_number )
|
45
|
+
record "(%s): decision=%s", __method__, decision_number
|
46
|
+
end
|
47
|
+
|
48
|
+
def location( line, position )
|
49
|
+
record '(%s): line=%s position=%s', __method__, line, position
|
50
|
+
end
|
51
|
+
|
52
|
+
def consume_node( tree )
|
53
|
+
record '(%s) unique_id=%s text=%p type=%s[%s]', __method__, @adaptor.unique_id(tree),
|
54
|
+
@adaptor.text_of(tree), @adaptor.type_name( tree ), @adaptor.type_of(tree)
|
55
|
+
end
|
56
|
+
|
57
|
+
def look(i, tree)
|
58
|
+
record '(%s): k=%s unique_id=%s text=%p type=%s[%s]', __method__, i, @adaptor.unique_id( tree ),
|
59
|
+
@adaptor.text_of( tree ), @adaptor.type_name( tree ), @adaptor.type_of( tree )
|
60
|
+
end
|
61
|
+
|
62
|
+
def flat_node( tree )
|
63
|
+
record '(%s): unique_id=%s', __method__, @adaptor.unique_id( tree )
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_node(tree, token = nil)
|
67
|
+
unless token
|
68
|
+
record '(%s): unique_id=%s text=%p type=%s[%s]', __method__, @adaptor.unique_id( tree ),
|
69
|
+
@adaptor.text_of( tree ), @adaptor.type_name( tree ), @adaptor.type_of( tree )
|
70
|
+
else
|
71
|
+
record '(%s): unique_id=%s type=%s[%s]', __method__, @adaptor.unique_id( tree ),
|
72
|
+
@adaptor.type_of( tree ), @adaptor.type_name( tree ), @adaptor.type_of( tree )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def become_root( new_root, old_root )
|
77
|
+
record '(%s): old_root_id=%s new_root_id=%s', __method__, @adaptor.unique_id( new_root ),
|
78
|
+
@adaptor.unique_id( old_root )
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_child( root, child )
|
82
|
+
record '(%s): root_id=%s child_id=%s', __method__, @adaptor.unique_id( root ),
|
83
|
+
@adaptor.unique_id( child )
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_token_boundaries( tree, token_start_index, token_stop_index )
|
87
|
+
record '(%s): unique_id=%s index_range=%s..%s', __method__, @adaptor.unique_id( tree ),
|
88
|
+
token_start_index, token_stop_index
|
89
|
+
end
|
90
|
+
end # class TraceEventListener
|
91
|
+
end # module Debug
|
92
|
+
end # module ANTLR3
|
data/lib/antlr3/dfa.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
=begin LICENSE
|
5
|
+
|
6
|
+
[The "BSD licence"]
|
7
|
+
Copyright (c) 2009 Kyle Yetter
|
8
|
+
All rights reserved.
|
9
|
+
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
11
|
+
modification, are permitted provided that the following conditions
|
12
|
+
are met:
|
13
|
+
|
14
|
+
1. Redistributions of source code must retain the above copyright
|
15
|
+
notice, this list of conditions and the following disclaimer.
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright
|
17
|
+
notice, this list of conditions and the following disclaimer in the
|
18
|
+
documentation and/or other materials provided with the distribution.
|
19
|
+
3. The name of the author may not be used to endorse or promote products
|
20
|
+
derived from this software without specific prior written permission.
|
21
|
+
|
22
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
23
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
24
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
25
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
26
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
27
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
28
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
29
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
30
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
31
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
|
33
|
+
=end
|
34
|
+
|
35
|
+
module ANTLR3
|
36
|
+
|
37
|
+
=begin rdoc ANTLR3::DFA
|
38
|
+
|
39
|
+
DFA is a class that implements a finite state machine that chooses between
|
40
|
+
alternatives in a rule based upon lookahead symbols from an input stream.
|
41
|
+
|
42
|
+
Deterministic Finite Automata (DFA) are finite state machines that are capable
|
43
|
+
of recognizing <i>regular languages</i>. For more background on the subject,
|
44
|
+
check out http://en.wikipedia.org/wiki/Deterministic_finite-state_machine or
|
45
|
+
check out general ANTLR documentation at http://www.antlr.org
|
46
|
+
|
47
|
+
ANTLR implements most of its decision logic directly using code branching
|
48
|
+
structures in methods. However, for certain types of decisions, ANTLR will
|
49
|
+
generate a special DFA class definition to implement a decision.
|
50
|
+
|
51
|
+
Conceptually, these state machines are defined by a number of states, each state
|
52
|
+
represented by an integer indexed upward from zero. State number +0+ is the
|
53
|
+
<i>start state</i> of the machine; every prediction will begin in state +0+. At
|
54
|
+
each step, the machine examines the next symbol on the input stream, checks the
|
55
|
+
value against the transition parameters associated with the current state, and
|
56
|
+
either moves to a new state number to repeat the process or decides that the
|
57
|
+
machine cannot transition any further. If the machine cannot transition any
|
58
|
+
further and the current state is defined as an <i>accept state</i>, an
|
59
|
+
alternative has been chosen successfully and the prediction procedure ends. If
|
60
|
+
the current state is not an <i>accept state</i>, the prediction has failed and
|
61
|
+
there is <i>no viable alternative</i>.
|
62
|
+
|
63
|
+
In generated code, ANTLR defines DFA states using seven parameters, each defined
|
64
|
+
as a member of seven seperate array constants -- +MIN+, +MAX+, +EOT+, +EOF+,
|
65
|
+
+SPECIAL+, +ACCEPT+, and +TRANSITION+. The parameters that characterize state
|
66
|
+
+s+ are defined by the value of these lists at index +s+.
|
67
|
+
|
68
|
+
MIN[s]::
|
69
|
+
The smallest value of the next input symbol that has
|
70
|
+
a transition for state +s+
|
71
|
+
MAX[s]::
|
72
|
+
The largest value of the next input symbol that has
|
73
|
+
a transition for state +s+
|
74
|
+
TRANSITION[s]::
|
75
|
+
A list that defines the next state number based upon
|
76
|
+
the current input symbol.
|
77
|
+
EOT[s]::
|
78
|
+
If positive, it specifies a state transition in
|
79
|
+
situations where a non-matching input symbol does
|
80
|
+
not indicate failure.
|
81
|
+
SPECIAL[s]::
|
82
|
+
If positive, it indicates that the prediction
|
83
|
+
algorithm must defer to a special code block
|
84
|
+
to determine the next state. The value is used
|
85
|
+
by the special state code to determine the next
|
86
|
+
state.
|
87
|
+
ACCEPT[s]::
|
88
|
+
If positive and there are no possible state
|
89
|
+
transitions, this is the alternative number
|
90
|
+
that has been predicted
|
91
|
+
EOF[s]::
|
92
|
+
If positive and the input stream has been exhausted,
|
93
|
+
this is the alternative number that has been predicted.
|
94
|
+
|
95
|
+
For more information on the prediction algorithm, check out the #predict method
|
96
|
+
below.
|
97
|
+
|
98
|
+
=end
|
99
|
+
|
100
|
+
class DFA
|
101
|
+
include Constants
|
102
|
+
include Error
|
103
|
+
|
104
|
+
attr_reader :recognizer, :decision_number, :eot, :eof, :min, :max,
|
105
|
+
:accept, :special, :transition, :special_block
|
106
|
+
|
107
|
+
class << self
|
108
|
+
attr_reader :decision
|
109
|
+
|
110
|
+
def unpack(*data)
|
111
|
+
data.empty? and return [].freeze
|
112
|
+
|
113
|
+
n = data.length / 2
|
114
|
+
size = 0
|
115
|
+
n.times { |i| size += data[2*i] }
|
116
|
+
if size > 1024
|
117
|
+
values = Hash.new(0)
|
118
|
+
data.each_slice(2) do |count, value|
|
119
|
+
values[value] += count
|
120
|
+
end
|
121
|
+
default = values.keys.max_by { |v| values[v] }
|
122
|
+
|
123
|
+
unpacked = Hash.new(default)
|
124
|
+
position = 0
|
125
|
+
data.each_slice(2) do |count, value|
|
126
|
+
unless value == default
|
127
|
+
count.times { |i| unpacked[position + i] = value }
|
128
|
+
end
|
129
|
+
position += count
|
130
|
+
end
|
131
|
+
else
|
132
|
+
unpacked = []
|
133
|
+
data.each_slice(2) do |count, value|
|
134
|
+
unpacked.concat Array.new(count, value)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
return unpacked.freeze
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize(recognizer, decision_number = nil, eot = nil, eof = nil,
|
143
|
+
min = nil, max = nil, accept = nil, special = nil,
|
144
|
+
transition = nil, &special_block)
|
145
|
+
@recognizer = recognizer
|
146
|
+
@decision_number = decision_number || self.class.decision
|
147
|
+
@eot = eot || self.class::EOT
|
148
|
+
@eof = eof || self.class::EOF
|
149
|
+
@min = min || self.class::MIN
|
150
|
+
@max = max || self.class::MAX
|
151
|
+
@accept = accept || self.class::ACCEPT
|
152
|
+
@special = special || self.class::SPECIAL
|
153
|
+
@transition = transition || self.class::TRANSITION
|
154
|
+
@special_block = special_block
|
155
|
+
rescue NameError => e
|
156
|
+
raise unless e.message =~ /uninitialized constant/
|
157
|
+
constant = e.name
|
158
|
+
message = Util.tidy( <<-END )
|
159
|
+
| No #{constant} information provided.
|
160
|
+
| DFA cannot be instantiated without providing state array information.
|
161
|
+
| When DFAs are generated by ANTLR, this information should already be
|
162
|
+
| provided in the DFA subclass constants.
|
163
|
+
END
|
164
|
+
end
|
165
|
+
|
166
|
+
def predict(input)
|
167
|
+
mark = input.mark
|
168
|
+
state = 0
|
169
|
+
|
170
|
+
50000.times do
|
171
|
+
special_state = @special[state]
|
172
|
+
if special_state >= 0
|
173
|
+
state = @special_block.call(special_state)
|
174
|
+
if state == -1
|
175
|
+
no_viable_alternative(state, input)
|
176
|
+
return 0
|
177
|
+
end
|
178
|
+
input.consume
|
179
|
+
next
|
180
|
+
end
|
181
|
+
@accept[state] >= 1 and return @accept[state]
|
182
|
+
|
183
|
+
# look for a normal char transition
|
184
|
+
|
185
|
+
c = input.peek
|
186
|
+
# the @min and @max arrays contain the bounds of the character (or token type)
|
187
|
+
# ranges for the transition decisions
|
188
|
+
if c.between?(@min[state], @max[state])
|
189
|
+
# c - @min[state] is the position of the character within the range
|
190
|
+
# so for a range like ?a..?z, a match of ?a would be 0,
|
191
|
+
# ?c would be 2, and ?z would be 25
|
192
|
+
next_state = @transition[state][c - @min[state]]
|
193
|
+
if next_state < 0
|
194
|
+
if @eot[state] >= 0
|
195
|
+
state = @eot[state]
|
196
|
+
input.consume
|
197
|
+
next
|
198
|
+
end
|
199
|
+
no_viable_alternative(state, input)
|
200
|
+
return 0
|
201
|
+
end
|
202
|
+
|
203
|
+
state = next_state
|
204
|
+
input.consume()
|
205
|
+
next
|
206
|
+
end
|
207
|
+
if @eot[state] >= 0
|
208
|
+
state = @eot[state]
|
209
|
+
input.consume()
|
210
|
+
next
|
211
|
+
end
|
212
|
+
(c == EOF && @eof[state] >= 0) and return @accept[@eof[state]]
|
213
|
+
no_viable_alternative(state, input)
|
214
|
+
return 0
|
215
|
+
end
|
216
|
+
|
217
|
+
ANTLR3.bug!( Util.tidy(<<-END) )
|
218
|
+
| DFA BANG!
|
219
|
+
| The prediction loop has exceeded a maximum limit of 50000 iterations
|
220
|
+
| ----
|
221
|
+
| decision: #@decision_number
|
222
|
+
| description: #{description}
|
223
|
+
END
|
224
|
+
ensure
|
225
|
+
input.rewind(mark)
|
226
|
+
end
|
227
|
+
|
228
|
+
def no_viable_alternative(state, input)
|
229
|
+
raise(BacktrackingFailed) if @recognizer.state.backtracking > 0
|
230
|
+
except = NoViableAlternative.new(description, @decision_number, state, input)
|
231
|
+
error(except)
|
232
|
+
raise(except)
|
233
|
+
end
|
234
|
+
|
235
|
+
def error(except)
|
236
|
+
# overridable debugging hook
|
237
|
+
end
|
238
|
+
|
239
|
+
def special_state_transition(state, input)
|
240
|
+
return -1
|
241
|
+
end
|
242
|
+
|
243
|
+
def description
|
244
|
+
return "n/a"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
data/lib/antlr3/dot.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
=begin LICENSE
|
5
|
+
|
6
|
+
[The "BSD licence"]
|
7
|
+
Copyright (c) 2009 Kyle Yetter
|
8
|
+
All rights reserved.
|
9
|
+
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
11
|
+
modification, are permitted provided that the following conditions
|
12
|
+
are met:
|
13
|
+
|
14
|
+
1. Redistributions of source code must retain the above copyright
|
15
|
+
notice, this list of conditions and the following disclaimer.
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright
|
17
|
+
notice, this list of conditions and the following disclaimer in the
|
18
|
+
documentation and/or other materials provided with the distribution.
|
19
|
+
3. The name of the author may not be used to endorse or promote products
|
20
|
+
derived from this software without specific prior written permission.
|
21
|
+
|
22
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
23
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
24
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
25
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
26
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
27
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
28
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
29
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
30
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
31
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
|
33
|
+
=end
|
34
|
+
|
35
|
+
require 'antlr3'
|
36
|
+
require 'erb'
|
37
|
+
|
38
|
+
module ANTLR3
|
39
|
+
|
40
|
+
=begin rdoc ANTLR3::DOT
|
41
|
+
|
42
|
+
An extra utility for generating graphviz DOT file representations of ANTLR
|
43
|
+
Abstract Syntax Tree nodes.
|
44
|
+
|
45
|
+
This module has been directly ported to Ruby from the ANTLR Python runtime
|
46
|
+
library.
|
47
|
+
|
48
|
+
=end
|
49
|
+
|
50
|
+
module DOT
|
51
|
+
class Context
|
52
|
+
def []=(var, value)
|
53
|
+
instance_variable_set(:"@#{var}", value)
|
54
|
+
end
|
55
|
+
def [](var)
|
56
|
+
instance_variable_get(:"@#{var}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(template, vars = {})
|
60
|
+
@__template__ = template
|
61
|
+
vars.each do |var, value|
|
62
|
+
self[var] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
@__template__.result(binding)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
class TreeGenerator
|
71
|
+
TREE_TEMPLATE = ERB.new( Util.tidy(<<-END) )
|
72
|
+
| digraph {
|
73
|
+
| ordering=out;
|
74
|
+
| ranksep=.4;
|
75
|
+
| node [shape=plaintext, fixedsize=true, fontsize=11, fontname="Courier",
|
76
|
+
| width=.25, height=.25];
|
77
|
+
| edge [arrowsize=.5];
|
78
|
+
| <%= @nodes.join("\n ") %>
|
79
|
+
| <%= @edges.join("\n ") %>
|
80
|
+
| }
|
81
|
+
END
|
82
|
+
|
83
|
+
NODE_TEMPLATE = ERB.new( Util.tidy(<<-END) )
|
84
|
+
| <%= @name %> [label="<%= @text %>"];
|
85
|
+
END
|
86
|
+
|
87
|
+
EDGE_TEMPLATE = ERB.new( Util.tidy(<<-END) )
|
88
|
+
| <%= @parent %> -> <%= @child %>; // "<%= @parent_text %>" -> "<%= @child_text %>"
|
89
|
+
END
|
90
|
+
|
91
|
+
def self.generate(tree, adaptor = nil, tree_template = TREE_TEMPLATE,
|
92
|
+
edge_template = EDGE_TEMPLATE)
|
93
|
+
new.to_dot(tree, adaptor, tree_template, edge_template)
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@node_number = 0
|
98
|
+
@node_to_number_map = Hash.new do |map, node|
|
99
|
+
map[node] = @node_number
|
100
|
+
@node_number += 1
|
101
|
+
@node_number - 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_dot(tree, adaptor = nil, tree_template = TREE_TEMPLATE,
|
106
|
+
edge_template = EDGE_TEMPLATE)
|
107
|
+
adaptor ||= AST::CommonTreeAdaptor.new
|
108
|
+
@node_number = 0
|
109
|
+
tree_template = Context.new(tree_template, :nodes => [], :edges => [])
|
110
|
+
define_nodes( tree, adaptor, tree_template )
|
111
|
+
|
112
|
+
@node_number = 0
|
113
|
+
define_edges( tree, adaptor, tree_template, edge_template )
|
114
|
+
return tree_template.to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
def define_nodes( tree, adaptor, tree_template, known_nodes = nil)
|
118
|
+
known_nodes ||= Set.new
|
119
|
+
tree.nil? and return
|
120
|
+
n = adaptor.child_count( tree )
|
121
|
+
n == 0 and return
|
122
|
+
number = node_number( tree )
|
123
|
+
unless known_nodes.include?( number )
|
124
|
+
parent_node_template = node_template_for(adaptor, child)
|
125
|
+
tree_template[:nodes] << parent_node_template
|
126
|
+
known_nodes.add( number )
|
127
|
+
end
|
128
|
+
|
129
|
+
n.times do |index|
|
130
|
+
child = adaptor.child_of( tree, index )
|
131
|
+
number = @node_to_number_map[ child ]
|
132
|
+
unless known_nodes.include?(number)
|
133
|
+
node_template = node_template_for( adaptor, child )
|
134
|
+
tree_template[:nodes] << node_template
|
135
|
+
known_nodes.add( number )
|
136
|
+
end
|
137
|
+
|
138
|
+
define_nodes(child, adaptor, tree_template, edge_template)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def define_edges( tree, adaptor, tree_template, edge_template )
|
143
|
+
tree.nil? or return
|
144
|
+
|
145
|
+
n = adaptor.child_count( tree )
|
146
|
+
n == 0 and return
|
147
|
+
|
148
|
+
parent_name = 'n%i' % @node_to_number_map[ tree ]
|
149
|
+
parent_text = adaptor.text_of( tree )
|
150
|
+
n.times do |index|
|
151
|
+
child = adaptor.child_of( tree, index )
|
152
|
+
child_text = adaptor.text_of( child )
|
153
|
+
child_name = 'n%i' % @node_to_number_map[ tree ]
|
154
|
+
edge_template = Context.new(edge_template,
|
155
|
+
:parent => parent_name, :child => child_name,
|
156
|
+
:parent_text => parent_text, :child_text => child_text
|
157
|
+
)
|
158
|
+
tree_template[:edges] << edge_template
|
159
|
+
define_edges( child, adaptor, tree_template, edge_template )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def node_template_for(adaptor, tree)
|
164
|
+
text = adaptor.text_of( tree )
|
165
|
+
node_template = Context.new(NODE_TEMPLATE)
|
166
|
+
unique_name = 'n%i' % @node_to_number_map[ tree ]
|
167
|
+
node_template[:name] = unique_name
|
168
|
+
text and text = text.gsub(/"/, '\\"')
|
169
|
+
node_template[:text] = text
|
170
|
+
return node_template
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|