parser 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -7
- data/.gitignore +0 -1
- data/README.md +4 -2
- data/bin/{parse → ruby-parse} +2 -2
- data/bin/ruby-rewrite +6 -0
- data/{AST_FORMAT.md → doc/AST_FORMAT.md} +45 -29
- data/doc/CUSTOMIZATION.md +37 -0
- data/doc/INTERNALS.md +21 -0
- data/lib/parser.rb +14 -3
- data/lib/parser/ast/node.rb +6 -0
- data/lib/parser/ast/processor.rb +216 -0
- data/lib/parser/builders/default.rb +613 -215
- data/lib/parser/compatibility/slop.rb +12 -0
- data/lib/parser/lexer.rl +30 -10
- data/lib/parser/lexer/explanation.rb +1 -1
- data/lib/parser/lexer/literal.rb +5 -6
- data/lib/parser/ruby18.y +31 -24
- data/lib/parser/ruby19.y +26 -19
- data/lib/parser/ruby20.y +27 -20
- data/lib/parser/ruby21.y +27 -20
- data/lib/parser/runner.rb +198 -0
- data/lib/parser/runner/ruby_parse.rb +87 -0
- data/lib/parser/runner/ruby_rewrite.rb +13 -0
- data/lib/parser/source/buffer.rb +1 -0
- data/lib/parser/source/map.rb +20 -0
- data/lib/parser/source/map/block.rb +16 -0
- data/lib/parser/source/map/collection.rb +16 -0
- data/lib/parser/source/map/condition.rb +19 -0
- data/lib/parser/source/map/constant.rb +27 -0
- data/lib/parser/source/map/definition.rb +21 -0
- data/lib/parser/source/map/for.rb +17 -0
- data/lib/parser/source/map/keyword.rb +18 -0
- data/lib/parser/source/map/rescue_body.rb +19 -0
- data/lib/parser/source/map/send.rb +29 -0
- data/lib/parser/source/map/ternary.rb +16 -0
- data/lib/parser/source/map/variable.rb +26 -0
- data/lib/parser/source/range.rb +25 -24
- data/lib/parser/version.rb +3 -0
- data/parser.gemspec +4 -2
- data/test/parse_helper.rb +13 -10
- data/test/test_lexer.rb +32 -11
- data/test/test_parse_helper.rb +1 -0
- data/test/test_parser.rb +176 -128
- data/test/test_source_range.rb +18 -6
- metadata +161 -91
- data/bin/benchmark +0 -47
- data/bin/explain-parse +0 -14
- data/lib/parser/source/map/variable_assignment.rb +0 -15
data/lib/parser/ruby21.y
CHANGED
@@ -78,17 +78,17 @@ rule
|
|
78
78
|
bodystmt: compstmt opt_rescue opt_else opt_ensure
|
79
79
|
{
|
80
80
|
rescue_bodies = val[1]
|
81
|
-
|
82
|
-
|
81
|
+
else_t, else_ = val[2]
|
82
|
+
ensure_t, ensure_ = val[3]
|
83
83
|
|
84
84
|
if rescue_bodies.empty? && !else_.nil?
|
85
|
-
diagnostic :warning, :useless_else,
|
85
|
+
diagnostic :warning, :useless_else, else_t
|
86
86
|
end
|
87
87
|
|
88
88
|
result = @builder.begin_body(val[0],
|
89
89
|
rescue_bodies,
|
90
|
-
|
91
|
-
|
90
|
+
else_t, else_,
|
91
|
+
ensure_t, ensure_)
|
92
92
|
}
|
93
93
|
|
94
94
|
compstmt: stmts opt_terms
|
@@ -163,11 +163,11 @@ rule
|
|
163
163
|
}
|
164
164
|
| stmt kWHILE_MOD expr_value
|
165
165
|
{
|
166
|
-
result = @builder.loop_mod(val[0], val[1], val[2])
|
166
|
+
result = @builder.loop_mod(:while, val[0], val[1], val[2])
|
167
167
|
}
|
168
168
|
| stmt kUNTIL_MOD expr_value
|
169
169
|
{
|
170
|
-
result = @builder.loop_mod(val[0], val[1], val[2])
|
170
|
+
result = @builder.loop_mod(:until, val[0], val[1], val[2])
|
171
171
|
}
|
172
172
|
| stmt kRESCUE_MOD stmt
|
173
173
|
{
|
@@ -374,7 +374,7 @@ rule
|
|
374
374
|
}
|
375
375
|
| tLPAREN mlhs_inner rparen
|
376
376
|
{
|
377
|
-
result = @builder.
|
377
|
+
result = @builder.begin(val[0], val[1], val[2])
|
378
378
|
}
|
379
379
|
|
380
380
|
mlhs_inner: mlhs_basic
|
@@ -436,7 +436,7 @@ rule
|
|
436
436
|
mlhs_item: mlhs_node
|
437
437
|
| tLPAREN mlhs_inner rparen
|
438
438
|
{
|
439
|
-
result = @builder.
|
439
|
+
result = @builder.begin(val[0], val[1], val[2])
|
440
440
|
}
|
441
441
|
|
442
442
|
mlhs_head: mlhs_item tCOMMA
|
@@ -992,7 +992,7 @@ rule
|
|
992
992
|
}
|
993
993
|
opt_nl tRPAREN
|
994
994
|
{
|
995
|
-
result = @builder.
|
995
|
+
result = @builder.begin(val[0], val[1], val[4])
|
996
996
|
}
|
997
997
|
| tLPAREN_ARG
|
998
998
|
{
|
@@ -1000,11 +1000,11 @@ rule
|
|
1000
1000
|
}
|
1001
1001
|
opt_nl tRPAREN
|
1002
1002
|
{
|
1003
|
-
result = @builder.
|
1003
|
+
result = @builder.begin(val[0], nil, val[3])
|
1004
1004
|
}
|
1005
1005
|
| tLPAREN compstmt tRPAREN
|
1006
1006
|
{
|
1007
|
-
result = @builder.
|
1007
|
+
result = @builder.begin(val[0], val[1], val[2])
|
1008
1008
|
}
|
1009
1009
|
| primary_value tCOLON2 tCONSTANT
|
1010
1010
|
{
|
@@ -1098,7 +1098,7 @@ rule
|
|
1098
1098
|
}
|
1099
1099
|
compstmt kEND
|
1100
1100
|
{
|
1101
|
-
result = @builder.loop(val[0], val[2], val[3],
|
1101
|
+
result = @builder.loop(:while, val[0], val[2], val[3],
|
1102
1102
|
val[5], val[6])
|
1103
1103
|
}
|
1104
1104
|
| kUNTIL
|
@@ -1111,16 +1111,24 @@ rule
|
|
1111
1111
|
}
|
1112
1112
|
compstmt kEND
|
1113
1113
|
{
|
1114
|
-
result = @builder.loop(val[0], val[2], val[3],
|
1114
|
+
result = @builder.loop(:until, val[0], val[2], val[3],
|
1115
1115
|
val[5], val[6])
|
1116
1116
|
}
|
1117
1117
|
| kCASE expr_value opt_terms case_body kEND
|
1118
1118
|
{
|
1119
|
-
|
1119
|
+
*when_bodies, (else_t, else_body) = *val[3]
|
1120
|
+
|
1121
|
+
result = @builder.case(val[0], val[1],
|
1122
|
+
when_bodies, else_t, else_body,
|
1123
|
+
val[4])
|
1120
1124
|
}
|
1121
1125
|
| kCASE opt_terms case_body kEND
|
1122
1126
|
{
|
1123
|
-
|
1127
|
+
*when_bodies, (else_t, else_body) = *val[2]
|
1128
|
+
|
1129
|
+
result = @builder.case(val[0], nil,
|
1130
|
+
when_bodies, else_t, else_body,
|
1131
|
+
val[3])
|
1124
1132
|
}
|
1125
1133
|
| kFOR for_var kIN
|
1126
1134
|
{
|
@@ -1642,8 +1650,7 @@ opt_block_args_tail:
|
|
1642
1650
|
|
1643
1651
|
cases: opt_else
|
1644
1652
|
{
|
1645
|
-
|
1646
|
-
result = [ else_ ]
|
1653
|
+
result = [ val[0] ]
|
1647
1654
|
}
|
1648
1655
|
| case_body
|
1649
1656
|
|
@@ -1674,13 +1681,13 @@ opt_block_args_tail:
|
|
1674
1681
|
|
1675
1682
|
exc_var: tASSOC lhs
|
1676
1683
|
{
|
1677
|
-
result = val
|
1684
|
+
result = [ val[0], val[1] ]
|
1678
1685
|
}
|
1679
1686
|
| none
|
1680
1687
|
|
1681
1688
|
opt_ensure: kENSURE compstmt
|
1682
1689
|
{
|
1683
|
-
result = val
|
1690
|
+
result = [ val[0], val[1] ]
|
1684
1691
|
}
|
1685
1692
|
| none
|
1686
1693
|
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'find'
|
3
|
+
require 'slop'
|
4
|
+
|
5
|
+
require 'parser'
|
6
|
+
require 'parser/compatibility/slop'
|
7
|
+
|
8
|
+
module Parser
|
9
|
+
|
10
|
+
class Runner
|
11
|
+
def self.go(options)
|
12
|
+
new.execute(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@slop = Slop.new(:strict => true)
|
17
|
+
@parser_class = nil
|
18
|
+
@parser = nil
|
19
|
+
@files = []
|
20
|
+
@fragments = []
|
21
|
+
|
22
|
+
@source_count = 0
|
23
|
+
@source_size = 0
|
24
|
+
|
25
|
+
setup_option_parsing
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute(options)
|
29
|
+
parse_options(options)
|
30
|
+
prepare_parser
|
31
|
+
|
32
|
+
process_all_input
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def runner_name
|
38
|
+
raise NotImplementedError, "implement #{self.class}##{__callee__}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_option_parsing
|
42
|
+
@slop.banner "Usage: #{runner_name} [options] FILE|DIRECTORY..."
|
43
|
+
|
44
|
+
@slop.on 'h', 'help', 'Display this help message and exit', :tail => true do
|
45
|
+
puts @slop.help
|
46
|
+
puts <<-HELP
|
47
|
+
|
48
|
+
If you specify a DIRECTORY, then all *.rb files are fetched
|
49
|
+
from it recursively and appended to the file list.
|
50
|
+
|
51
|
+
The default parsing mode is for current Ruby (#{RUBY_VERSION}).
|
52
|
+
HELP
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
@slop.on 'V', 'version', 'Output version information and exit', :tail => true do
|
57
|
+
puts "#{runner_name} based on parser version #{Parser::VERSION}"
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
@slop.on '18', 'Parse as Ruby 1.8.7 would' do
|
62
|
+
require 'parser/ruby18'
|
63
|
+
@parser_class = Parser::Ruby18
|
64
|
+
end
|
65
|
+
|
66
|
+
@slop.on '19', 'Parse as Ruby 1.9.3 would' do
|
67
|
+
require 'parser/ruby19'
|
68
|
+
@parser_class = Parser::Ruby19
|
69
|
+
end
|
70
|
+
|
71
|
+
@slop.on '20', 'Parse as Ruby 2.0.0 would' do
|
72
|
+
require 'parser/ruby20'
|
73
|
+
@parser_class = Parser::Ruby20
|
74
|
+
end
|
75
|
+
|
76
|
+
@slop.on '21', 'Parse as Ruby trunk would (use with caution)' do
|
77
|
+
require 'parser/ruby21'
|
78
|
+
@parser_class = Parser::Ruby21
|
79
|
+
end
|
80
|
+
|
81
|
+
@slop.on 'w', 'warnings', 'Enable warnings'
|
82
|
+
|
83
|
+
@slop.on 'B', 'benchmark', 'Benchmark the processor'
|
84
|
+
|
85
|
+
@slop.on 'e=', 'Process a fragment of Ruby code' do |fragment|
|
86
|
+
@fragments << fragment
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_options(options)
|
91
|
+
@slop.parse!(options)
|
92
|
+
|
93
|
+
# Slop has just removed recognized options from `options`.
|
94
|
+
options.each do |file_or_dir|
|
95
|
+
if File.directory?(file_or_dir)
|
96
|
+
Find.find(file_or_dir) do |path|
|
97
|
+
@files << path if path.end_with? '.rb'
|
98
|
+
end
|
99
|
+
else
|
100
|
+
@files << file_or_dir
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if @files.empty? && @fragments.empty?
|
105
|
+
$stderr.puts "Need something to parse!"
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
|
109
|
+
if @parser_class.nil?
|
110
|
+
require 'parser/current'
|
111
|
+
@parser_class = Parser::CurrentRuby
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def prepare_parser
|
116
|
+
@parser = @parser_class.new
|
117
|
+
|
118
|
+
@parser.diagnostics.all_errors_are_fatal = true
|
119
|
+
@parser.diagnostics.ignore_warnings = !@slop.warnings?
|
120
|
+
|
121
|
+
@parser.diagnostics.consumer = lambda do |diagnostic|
|
122
|
+
puts(diagnostic.render)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def process_all_input
|
127
|
+
input_size = @files.size + @fragments.size
|
128
|
+
if input_size > 1
|
129
|
+
puts "Using #{@parser_class} to parse #{input_size} files."
|
130
|
+
end
|
131
|
+
|
132
|
+
parsing_time =
|
133
|
+
Benchmark.measure do
|
134
|
+
process_fragments
|
135
|
+
process_files
|
136
|
+
end
|
137
|
+
|
138
|
+
if @slop.benchmark?
|
139
|
+
report_with_time(parsing_time)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def process_fragments
|
144
|
+
@fragments.each_with_index do |fragment, index|
|
145
|
+
buffer = Source::Buffer.new("(fragment:#{index})")
|
146
|
+
buffer.source = fragment
|
147
|
+
|
148
|
+
process_buffer(buffer)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def process_files
|
153
|
+
@files.each do |filename|
|
154
|
+
buffer = Parser::Source::Buffer.new(filename)
|
155
|
+
buffer.read
|
156
|
+
|
157
|
+
process_buffer(buffer)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def process_buffer(buffer)
|
162
|
+
@parser.reset
|
163
|
+
|
164
|
+
process(buffer)
|
165
|
+
|
166
|
+
@source_count += 1
|
167
|
+
@source_size += buffer.source.size
|
168
|
+
|
169
|
+
rescue Parser::SyntaxError
|
170
|
+
# skip
|
171
|
+
|
172
|
+
rescue StandardError
|
173
|
+
$stderr.puts("Failed on: #{buffer.name}")
|
174
|
+
raise
|
175
|
+
end
|
176
|
+
|
177
|
+
def process(buffer)
|
178
|
+
raise NotImplementedError, "implement #{self.class}##{__callee__}"
|
179
|
+
end
|
180
|
+
|
181
|
+
def report_with_time(parsing_time)
|
182
|
+
cpu_time = parsing_time.utime
|
183
|
+
|
184
|
+
speed = '%.3f' % (@source_size / cpu_time / 1000)
|
185
|
+
puts "Parsed #{@source_count} files (#{@source_size} characters)" \
|
186
|
+
" in #{'%.2f' % cpu_time} seconds (#{speed} kchars/s)."
|
187
|
+
|
188
|
+
if defined?(RUBY_ENGINE)
|
189
|
+
engine = RUBY_ENGINE
|
190
|
+
else
|
191
|
+
engine = 'ruby'
|
192
|
+
end
|
193
|
+
|
194
|
+
puts "Running on #{engine} #{RUBY_VERSION}."
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'parser/runner'
|
2
|
+
require 'parser/lexer/explanation'
|
3
|
+
|
4
|
+
module Parser
|
5
|
+
|
6
|
+
class Runner::RubyParse < Parser::Runner
|
7
|
+
|
8
|
+
class LocationProcessor < Parser::AST::Processor
|
9
|
+
def process(node)
|
10
|
+
p node
|
11
|
+
|
12
|
+
if node.src.nil?
|
13
|
+
puts "\e[31m[no location info]\e[0m"
|
14
|
+
elsif node.src.expression.nil?
|
15
|
+
puts "\e[31m[location info present but empty]\e[0m"
|
16
|
+
else
|
17
|
+
puts "\e[32m#{node.src.expression.source_line}\e[0m"
|
18
|
+
|
19
|
+
hilight_line = ""
|
20
|
+
|
21
|
+
print_line = lambda do |line|
|
22
|
+
puts line.
|
23
|
+
gsub(/[a-z_]+/) { |m| "\e[1;33m#{m}\e[0m" }.
|
24
|
+
gsub(/~+/) { |m| "\e[1;35m#{m}\e[0m" }
|
25
|
+
end
|
26
|
+
|
27
|
+
node.src.to_hash.each do |name, range|
|
28
|
+
next if range.nil?
|
29
|
+
|
30
|
+
length = range.length + 1 + name.length
|
31
|
+
end_col = range.begin.column + length
|
32
|
+
col_range = range.begin.column...end_col
|
33
|
+
|
34
|
+
if hilight_line.length < end_col
|
35
|
+
hilight_line = hilight_line.ljust(end_col)
|
36
|
+
end
|
37
|
+
|
38
|
+
if hilight_line[col_range] =~ /^\s*$/
|
39
|
+
hilight_line[col_range] = '~' * range.length + " #{name}"
|
40
|
+
else
|
41
|
+
print_line.call(hilight_line)
|
42
|
+
hilight_line = ""
|
43
|
+
redo
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
print_line.call(hilight_line) unless hilight_line.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def runner_name
|
57
|
+
'ruby-parse'
|
58
|
+
end
|
59
|
+
|
60
|
+
def setup_option_parsing
|
61
|
+
super
|
62
|
+
|
63
|
+
@slop.on 'L', 'locate', 'Explain how source maps for AST nodes are laid out'
|
64
|
+
|
65
|
+
@slop.on 'E', 'explain', 'Explain how the source is tokenized' do
|
66
|
+
ENV['RACC_DEBUG'] = '1'
|
67
|
+
|
68
|
+
Parser::Base.class_eval do
|
69
|
+
def next_token
|
70
|
+
@lexer.advance_and_explain
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def process(buffer)
|
77
|
+
ast = @parser.parse(buffer)
|
78
|
+
|
79
|
+
if @slop.locate?
|
80
|
+
LocationProcessor.new.process(ast)
|
81
|
+
elsif !@slop.benchmark?
|
82
|
+
p ast
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/parser/source/buffer.rb
CHANGED
data/lib/parser/source/map.rb
CHANGED
@@ -14,6 +14,26 @@ module Parser
|
|
14
14
|
|
15
15
|
freeze
|
16
16
|
end
|
17
|
+
|
18
|
+
def with_expression(expression_l)
|
19
|
+
with { |map| map.update_expression(expression_l) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
Hash[instance_variables.map do |ivar|
|
24
|
+
[ ivar[1..-1].to_sym, instance_variable_get(ivar) ]
|
25
|
+
end]
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def with(&block)
|
31
|
+
dup.tap(&block).freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_expression(expression_l)
|
35
|
+
@expression = expression_l
|
36
|
+
end
|
17
37
|
end
|
18
38
|
|
19
39
|
end
|