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.
Files changed (48) hide show
  1. checksums.yaml +7 -7
  2. data/.gitignore +0 -1
  3. data/README.md +4 -2
  4. data/bin/{parse → ruby-parse} +2 -2
  5. data/bin/ruby-rewrite +6 -0
  6. data/{AST_FORMAT.md → doc/AST_FORMAT.md} +45 -29
  7. data/doc/CUSTOMIZATION.md +37 -0
  8. data/doc/INTERNALS.md +21 -0
  9. data/lib/parser.rb +14 -3
  10. data/lib/parser/ast/node.rb +6 -0
  11. data/lib/parser/ast/processor.rb +216 -0
  12. data/lib/parser/builders/default.rb +613 -215
  13. data/lib/parser/compatibility/slop.rb +12 -0
  14. data/lib/parser/lexer.rl +30 -10
  15. data/lib/parser/lexer/explanation.rb +1 -1
  16. data/lib/parser/lexer/literal.rb +5 -6
  17. data/lib/parser/ruby18.y +31 -24
  18. data/lib/parser/ruby19.y +26 -19
  19. data/lib/parser/ruby20.y +27 -20
  20. data/lib/parser/ruby21.y +27 -20
  21. data/lib/parser/runner.rb +198 -0
  22. data/lib/parser/runner/ruby_parse.rb +87 -0
  23. data/lib/parser/runner/ruby_rewrite.rb +13 -0
  24. data/lib/parser/source/buffer.rb +1 -0
  25. data/lib/parser/source/map.rb +20 -0
  26. data/lib/parser/source/map/block.rb +16 -0
  27. data/lib/parser/source/map/collection.rb +16 -0
  28. data/lib/parser/source/map/condition.rb +19 -0
  29. data/lib/parser/source/map/constant.rb +27 -0
  30. data/lib/parser/source/map/definition.rb +21 -0
  31. data/lib/parser/source/map/for.rb +17 -0
  32. data/lib/parser/source/map/keyword.rb +18 -0
  33. data/lib/parser/source/map/rescue_body.rb +19 -0
  34. data/lib/parser/source/map/send.rb +29 -0
  35. data/lib/parser/source/map/ternary.rb +16 -0
  36. data/lib/parser/source/map/variable.rb +26 -0
  37. data/lib/parser/source/range.rb +25 -24
  38. data/lib/parser/version.rb +3 -0
  39. data/parser.gemspec +4 -2
  40. data/test/parse_helper.rb +13 -10
  41. data/test/test_lexer.rb +32 -11
  42. data/test/test_parse_helper.rb +1 -0
  43. data/test/test_parser.rb +176 -128
  44. data/test/test_source_range.rb +18 -6
  45. metadata +161 -91
  46. data/bin/benchmark +0 -47
  47. data/bin/explain-parse +0 -14
  48. 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
- else_, t_else = val[2]
82
- ensure_, t_ensure = val[3]
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, t_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
- else_, t_else,
91
- ensure_, t_ensure)
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.parenthesize(val[0], val[1], val[2])
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.parenthesize(val[0], val[1], val[2])
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.parenthesize(val[0], val[1], val[4])
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.parenthesize(val[0], nil, val[3])
1003
+ result = @builder.begin(val[0], nil, val[3])
1004
1004
  }
1005
1005
  | tLPAREN compstmt tRPAREN
1006
1006
  {
1007
- result = @builder.parenthesize(val[0], val[1], val[2])
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
- result = @builder.case(val[0], val[1], val[3], val[4])
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
- result = @builder.case(val[0], nil, val[2], val[3])
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
- else_t, else_ = val[0]
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
@@ -0,0 +1,13 @@
1
+ require 'parser/runner'
2
+
3
+ module Parser
4
+
5
+ class Runner::RubyRewrite < Runner
6
+ private
7
+
8
+ def runner_name
9
+ 'ruby-rewrite'
10
+ end
11
+ end
12
+
13
+ end
@@ -72,6 +72,7 @@ module Parser
72
72
 
73
73
  def source=(source)
74
74
  if source.respond_to? :encoding
75
+ source = source.dup if source.frozen?
75
76
  source = self.class.reencode_string(source)
76
77
  end
77
78
 
@@ -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