parser 1.1.0 → 1.2.0

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