parser 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.jrubyrc +1 -0
- data/.yardopts +4 -0
- data/Gemfile +2 -0
- data/README.md +3 -1
- data/doc/AST_FORMAT.md +93 -1
- data/lib/parser.rb +5 -0
- data/lib/parser/builders/default.rb +29 -19
- data/lib/parser/lexer.rl +3 -2
- data/lib/parser/lexer/explanation.rb +2 -2
- data/lib/parser/rewriter.rb +34 -0
- data/lib/parser/runner.rb +4 -5
- data/lib/parser/runner/ruby_parse.rb +76 -26
- data/lib/parser/runner/ruby_rewrite.rb +80 -0
- data/lib/parser/source/map.rb +4 -0
- data/lib/parser/source/range.rb +23 -4
- data/lib/parser/source/rewriter.rb +89 -0
- data/lib/parser/source/rewriter/action.rb +27 -0
- data/lib/parser/version.rb +1 -1
- data/parser.gemspec +3 -0
- data/test/parse_helper.rb +1 -1
- data/test/test_diagnostic.rb +5 -5
- data/test/test_parse_helper.rb +9 -9
- data/test/test_source_range.rb +2 -2
- data/test/test_source_rewriter.rb +81 -0
- data/test/test_source_rewriter_action.rb +44 -0
- metadata +40 -2
data/lib/parser.rb
CHANGED
@@ -17,6 +17,9 @@ module Parser
|
|
17
17
|
require 'parser/source/buffer'
|
18
18
|
require 'parser/source/range'
|
19
19
|
|
20
|
+
require 'parser/source/rewriter'
|
21
|
+
require 'parser/source/rewriter/action'
|
22
|
+
|
20
23
|
require 'parser/source/map'
|
21
24
|
require 'parser/source/map/operator'
|
22
25
|
require 'parser/source/map/collection'
|
@@ -47,6 +50,8 @@ module Parser
|
|
47
50
|
|
48
51
|
require 'parser/base'
|
49
52
|
|
53
|
+
require 'parser/rewriter'
|
54
|
+
|
50
55
|
ERRORS = {
|
51
56
|
# Lexer errors
|
52
57
|
:unicode_point_too_large => "invalid Unicode codepoint (too large)",
|
@@ -339,9 +339,6 @@ module Parser
|
|
339
339
|
when :back_ref, :nth_ref
|
340
340
|
message = ERRORS[:backref_assignment]
|
341
341
|
diagnostic :error, message, node.src.expression
|
342
|
-
|
343
|
-
else
|
344
|
-
raise NotImplementedError, "build_assignable #{node.inspect}"
|
345
342
|
end
|
346
343
|
end
|
347
344
|
|
@@ -375,9 +372,6 @@ module Parser
|
|
375
372
|
when :back_ref, :nth_ref
|
376
373
|
message = ERRORS[:backref_assignment]
|
377
374
|
diagnostic :error, message, lhs.src.expression
|
378
|
-
|
379
|
-
else
|
380
|
-
raise NotImplementedError, "build op_assign #{lhs.inspect}"
|
381
375
|
end
|
382
376
|
end
|
383
377
|
|
@@ -664,7 +658,7 @@ module Parser
|
|
664
658
|
def condition(cond_t, cond, then_t,
|
665
659
|
if_true, else_t, if_false, end_t)
|
666
660
|
n(:if, [ check_condition(cond), if_true, if_false ],
|
667
|
-
condition_map(cond_t, then_t, if_true, else_t, if_false, end_t))
|
661
|
+
condition_map(cond_t, cond, then_t, if_true, else_t, if_false, end_t))
|
668
662
|
end
|
669
663
|
|
670
664
|
def condition_mod(if_true, if_false, cond_t, cond)
|
@@ -687,7 +681,7 @@ module Parser
|
|
687
681
|
|
688
682
|
def case(case_t, expr, when_bodies, else_t, else_body, end_t)
|
689
683
|
n(:case, [ expr, *(when_bodies << else_body)],
|
690
|
-
condition_map(case_t, nil, nil, else_t, else_body, end_t))
|
684
|
+
condition_map(case_t, expr, nil, nil, else_t, else_body, end_t))
|
691
685
|
end
|
692
686
|
|
693
687
|
# Loops
|
@@ -891,7 +885,17 @@ module Parser
|
|
891
885
|
end
|
892
886
|
|
893
887
|
def string_part_map(string_t)
|
894
|
-
|
888
|
+
str_range = loc(string_t)
|
889
|
+
|
890
|
+
begin_l = Source::Range.new(str_range.source_buffer,
|
891
|
+
str_range.begin_pos,
|
892
|
+
str_range.begin_pos + 1)
|
893
|
+
|
894
|
+
end_l = Source::Range.new(str_range.source_buffer,
|
895
|
+
str_range.end_pos - 1,
|
896
|
+
str_range.end_pos)
|
897
|
+
|
898
|
+
Source::Map::Collection.new(begin_l, end_l,
|
895
899
|
loc(string_t))
|
896
900
|
end
|
897
901
|
|
@@ -1052,17 +1056,19 @@ module Parser
|
|
1052
1056
|
join_exprs(pre_e, post_e))
|
1053
1057
|
end
|
1054
1058
|
|
1055
|
-
def condition_map(keyword_t, begin_t, body_e, else_t, else_e, end_t)
|
1059
|
+
def condition_map(keyword_t, cond_e, begin_t, body_e, else_t, else_e, end_t)
|
1056
1060
|
if end_t
|
1057
1061
|
end_l = loc(end_t)
|
1058
1062
|
elsif else_e && else_e.src.expression
|
1059
1063
|
end_l = else_e.src.expression
|
1060
|
-
elsif else_t
|
1064
|
+
elsif loc(else_t)
|
1061
1065
|
end_l = loc(else_t)
|
1062
1066
|
elsif body_e.src.expression
|
1063
1067
|
end_l = body_e.src.expression
|
1064
|
-
|
1068
|
+
elsif loc(begin_t)
|
1065
1069
|
end_l = loc(begin_t)
|
1070
|
+
else
|
1071
|
+
end_l = cond_e.src.expression
|
1066
1072
|
end
|
1067
1073
|
|
1068
1074
|
Source::Map::Condition.new(loc(keyword_t),
|
@@ -1084,11 +1090,10 @@ module Parser
|
|
1084
1090
|
def rescue_body_map(keyword_t, exc_list_e, assoc_t,
|
1085
1091
|
exc_var_e, then_t,
|
1086
1092
|
compstmt_e)
|
1087
|
-
end_l = compstmt_e.src.expression ||
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
loc(keyword_t)
|
1093
|
+
end_l = compstmt_e.src.expression || loc(then_t)
|
1094
|
+
end_l = exc_var_e.src.expression if end_l.nil? && exc_var_e
|
1095
|
+
end_l = exc_list_e.src.expression if end_l.nil? && exc_list_e
|
1096
|
+
end_l = loc(keyword_t) if end_l.nil?
|
1092
1097
|
|
1093
1098
|
Source::Map::RescueBody.new(loc(keyword_t), loc(assoc_t), loc(then_t),
|
1094
1099
|
loc(keyword_t).join(end_l))
|
@@ -1097,7 +1102,11 @@ module Parser
|
|
1097
1102
|
def eh_keyword_map(compstmt_e, keyword_t, body_es,
|
1098
1103
|
else_t, else_e)
|
1099
1104
|
if synthesized_nil?(compstmt_e)
|
1100
|
-
|
1105
|
+
if keyword_t.nil?
|
1106
|
+
begin_l = body_es.first.src.expression
|
1107
|
+
else
|
1108
|
+
begin_l = loc(keyword_t)
|
1109
|
+
end
|
1101
1110
|
else
|
1102
1111
|
begin_l = compstmt_e.src.expression
|
1103
1112
|
end
|
@@ -1132,7 +1141,8 @@ module Parser
|
|
1132
1141
|
end
|
1133
1142
|
|
1134
1143
|
def loc(token)
|
1135
|
-
|
1144
|
+
# Pass through `nil`s and return nil for tNL.
|
1145
|
+
token[1] if token && token[0]
|
1136
1146
|
end
|
1137
1147
|
|
1138
1148
|
def diagnostic(type, message, location, highlights=[])
|
data/lib/parser/lexer.rl
CHANGED
@@ -240,7 +240,7 @@ class Parser::Lexer
|
|
240
240
|
end
|
241
241
|
|
242
242
|
def range(s = @ts, e = @te)
|
243
|
-
Parser::Source::Range.new(@source_buffer, s, e
|
243
|
+
Parser::Source::Range.new(@source_buffer, s, e)
|
244
244
|
end
|
245
245
|
|
246
246
|
def emit(type, value = tok, s = @ts, e = @te)
|
@@ -993,7 +993,8 @@ class Parser::Lexer
|
|
993
993
|
fnext expr_endfn; fbreak; };
|
994
994
|
|
995
995
|
constant
|
996
|
-
=> { emit(:tCONSTANT)
|
996
|
+
=> { emit(:tCONSTANT)
|
997
|
+
fnext expr_endfn; fbreak; };
|
997
998
|
|
998
999
|
bareword [?=!]?
|
999
1000
|
=> { emit(:tIDENTIFIER)
|
@@ -20,9 +20,9 @@ module Parser
|
|
20
20
|
from, to = range.begin.column, range.end.column
|
21
21
|
|
22
22
|
line = range.source_line
|
23
|
-
line[from
|
23
|
+
line[from...to] = "\e[4m#{line[from...to]}\e[0m"
|
24
24
|
|
25
|
-
tail_len = to - from
|
25
|
+
tail_len = to - from - 1
|
26
26
|
tail = "~" * (tail_len >= 0 ? tail_len : 0)
|
27
27
|
decoration = "#{" " * from}\e[1;31m^#{tail}\e[0m #{token} ".
|
28
28
|
ljust(70) + info
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Parser
|
2
|
+
class Rewriter < Parser::AST::Processor
|
3
|
+
def rewrite(source_buffer, ast)
|
4
|
+
@source_rewriter = Source::Rewriter.new(source_buffer)
|
5
|
+
|
6
|
+
process(ast)
|
7
|
+
|
8
|
+
@source_rewriter.process
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def assignment?(node)
|
14
|
+
[:lvasgn, :ivasgn, :gvasgn,
|
15
|
+
:cvasgn, :cvdecl, :cdecl].include?(node.type)
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove(range)
|
19
|
+
@source_rewriter.remove(range)
|
20
|
+
end
|
21
|
+
|
22
|
+
def insert_before(range, content)
|
23
|
+
@source_rewriter.insert_before(range, content)
|
24
|
+
end
|
25
|
+
|
26
|
+
def insert_after(range, content)
|
27
|
+
@source_rewriter.insert_after(range, content)
|
28
|
+
end
|
29
|
+
|
30
|
+
def replace(range, content)
|
31
|
+
@source_rewriter.replace(range, content)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/parser/runner.rb
CHANGED
@@ -123,12 +123,11 @@ module Parser
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
puts "Using #{@parser_class} to parse #{input_size} files."
|
130
|
-
end
|
126
|
+
def input_size
|
127
|
+
@files.size + @fragments.size
|
128
|
+
end
|
131
129
|
|
130
|
+
def process_all_input
|
132
131
|
parsing_time =
|
133
132
|
Benchmark.measure do
|
134
133
|
process_fragments
|
@@ -14,37 +14,79 @@ module Parser
|
|
14
14
|
elsif node.src.expression.nil?
|
15
15
|
puts "\e[31m[location info present but empty]\e[0m"
|
16
16
|
else
|
17
|
-
|
18
|
-
|
19
|
-
hilight_line
|
20
|
-
|
21
|
-
print_line = lambda do
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
source_line_no = nil
|
18
|
+
source_line = ""
|
19
|
+
hilight_line = ""
|
20
|
+
|
21
|
+
print_line = lambda do
|
22
|
+
unless hilight_line.empty?
|
23
|
+
puts hilight_line.
|
24
|
+
gsub(/[a-z_]+/) { |m| "\e[1;33m#{m}\e[0m" }.
|
25
|
+
gsub(/[~.]+/) { |m| "\e[1;35m#{m}\e[0m" }
|
26
|
+
hilight_line = ""
|
27
|
+
end
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
col_range = range.begin.column...end_col
|
30
|
+
print_source = lambda do |range|
|
31
|
+
source_line = range.source_line
|
32
|
+
puts "\e[32m#{source_line}\e[0m"
|
33
|
+
source_line
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
+
node.src.to_hash.
|
37
|
+
sort_by do |name, range|
|
38
|
+
[(range ? range.line : 0),
|
39
|
+
(name == :expression ? 1 : 0)]
|
40
|
+
end.
|
41
|
+
each do |name, range|
|
42
|
+
next if range.nil?
|
43
|
+
|
44
|
+
if source_line_no != range.line
|
45
|
+
print_line.call()
|
46
|
+
source_line = print_source.call(range)
|
47
|
+
source_line_no = range.line
|
48
|
+
end
|
49
|
+
|
50
|
+
beg_col = range.begin.column
|
51
|
+
|
52
|
+
if beg_col + range.length > source_line.length
|
53
|
+
multiline = true
|
54
|
+
range_length = source_line.length - beg_col + 3
|
55
|
+
else
|
56
|
+
multiline = false
|
57
|
+
range_length = range.length
|
58
|
+
end
|
59
|
+
|
60
|
+
length = range_length + 1 + name.length
|
61
|
+
end_col = beg_col + length
|
62
|
+
|
63
|
+
if beg_col > 0
|
64
|
+
col_range = (beg_col - 1)...end_col
|
65
|
+
else
|
66
|
+
col_range = beg_col...end_col
|
67
|
+
end
|
68
|
+
|
69
|
+
if hilight_line.length < end_col
|
70
|
+
hilight_line = hilight_line.ljust(end_col)
|
71
|
+
end
|
72
|
+
|
73
|
+
if hilight_line[col_range] =~ /^\s*$/
|
74
|
+
if multiline
|
75
|
+
tail = ('~' * (source_line.length - beg_col)) + '...'
|
76
|
+
else
|
77
|
+
tail = '~' * range_length
|
78
|
+
end
|
79
|
+
|
80
|
+
tail = ' ' + tail if beg_col > 0
|
81
|
+
|
82
|
+
hilight_line[col_range] = tail + " #{name}"
|
83
|
+
else
|
84
|
+
print_line.call
|
85
|
+
redo
|
86
|
+
end
|
36
87
|
end
|
37
88
|
|
38
|
-
|
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?
|
89
|
+
print_line.call
|
48
90
|
end
|
49
91
|
|
50
92
|
super
|
@@ -73,6 +115,14 @@ module Parser
|
|
73
115
|
end
|
74
116
|
end
|
75
117
|
|
118
|
+
def process_all_input
|
119
|
+
super
|
120
|
+
|
121
|
+
if input_size > 1
|
122
|
+
puts "Using #{@parser_class} to parse #{input_size} files."
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
76
126
|
def process(buffer)
|
77
127
|
ast = @parser.parse(buffer)
|
78
128
|
|
@@ -1,13 +1,93 @@
|
|
1
1
|
require 'parser/runner'
|
2
|
+
require 'tempfile'
|
2
3
|
|
3
4
|
module Parser
|
4
5
|
|
5
6
|
class Runner::RubyRewrite < Runner
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
|
10
|
+
@rewriters = []
|
11
|
+
end
|
12
|
+
|
6
13
|
private
|
7
14
|
|
8
15
|
def runner_name
|
9
16
|
'ruby-rewrite'
|
10
17
|
end
|
18
|
+
|
19
|
+
def setup_option_parsing
|
20
|
+
super
|
21
|
+
|
22
|
+
@slop.on 'l=', 'load=', 'Load a rewriter' do |file|
|
23
|
+
load_and_discover(file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_and_discover(file)
|
28
|
+
load file
|
29
|
+
|
30
|
+
const_name = file.
|
31
|
+
sub(/\.rb$/, '').
|
32
|
+
gsub(/(^|_)([a-z])/) do |m|
|
33
|
+
"#{$2.upcase}"
|
34
|
+
end
|
35
|
+
|
36
|
+
@rewriters << Object.const_get(const_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def process(initial_buffer)
|
40
|
+
buffer = initial_buffer
|
41
|
+
|
42
|
+
@rewriters.each do |rewriter_class|
|
43
|
+
@parser.reset
|
44
|
+
ast = @parser.parse(buffer)
|
45
|
+
|
46
|
+
rewriter = rewriter_class.new
|
47
|
+
new_source = rewriter.rewrite(buffer, ast)
|
48
|
+
|
49
|
+
new_buffer = Source::Buffer.new(initial_buffer.name +
|
50
|
+
'|after ' + rewriter_class.name)
|
51
|
+
new_buffer.source = new_source
|
52
|
+
|
53
|
+
@parser.reset
|
54
|
+
new_ast = @parser.parse(new_buffer)
|
55
|
+
|
56
|
+
unless ast == new_ast
|
57
|
+
$stderr.puts "ASTs do not match:"
|
58
|
+
|
59
|
+
old = Tempfile.new('old')
|
60
|
+
old.write ast.inspect + "\n"; old.flush
|
61
|
+
|
62
|
+
new = Tempfile.new('new')
|
63
|
+
new.write new_ast.inspect + "\n"; new.flush
|
64
|
+
|
65
|
+
IO.popen("diff -u #{old.path} #{new.path}") do |io|
|
66
|
+
diff = io.read.
|
67
|
+
sub(/^---.*/, "--- #{buffer.name}").
|
68
|
+
sub(/^\+\+\+.*/, "+++ #{new_buffer.name}")
|
69
|
+
|
70
|
+
$stderr.write diff
|
71
|
+
end
|
72
|
+
|
73
|
+
exit 1
|
74
|
+
end
|
75
|
+
|
76
|
+
buffer = new_buffer
|
77
|
+
end
|
78
|
+
|
79
|
+
if File.exist?(buffer.name)
|
80
|
+
File.open(buffer.name, 'w') do |file|
|
81
|
+
file.write buffer.source
|
82
|
+
end
|
83
|
+
else
|
84
|
+
if input_size > 1
|
85
|
+
puts "Rewritten content of #{buffer.name}:"
|
86
|
+
end
|
87
|
+
|
88
|
+
puts buffer.source
|
89
|
+
end
|
90
|
+
end
|
11
91
|
end
|
12
92
|
|
13
93
|
end
|
data/lib/parser/source/map.rb
CHANGED
data/lib/parser/source/range.rb
CHANGED
@@ -21,7 +21,7 @@ module Parser
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def size
|
24
|
-
@end_pos - @begin_pos
|
24
|
+
@end_pos - @begin_pos
|
25
25
|
end
|
26
26
|
|
27
27
|
alias length size
|
@@ -39,13 +39,25 @@ module Parser
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def column_range
|
42
|
-
self.begin.column
|
42
|
+
self.begin.column...self.end.column
|
43
43
|
end
|
44
44
|
|
45
45
|
def source_line
|
46
46
|
@source_buffer.source_line(line)
|
47
47
|
end
|
48
48
|
|
49
|
+
def to_source
|
50
|
+
source_line[column_range]
|
51
|
+
end
|
52
|
+
|
53
|
+
def is?(*what)
|
54
|
+
what.include?(to_source)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_a
|
58
|
+
(@begin_pos...@end_pos).to_a
|
59
|
+
end
|
60
|
+
|
49
61
|
def to_s
|
50
62
|
line, column = @source_buffer.decompose_position(@begin_pos)
|
51
63
|
|
@@ -55,11 +67,18 @@ module Parser
|
|
55
67
|
def join(other)
|
56
68
|
Range.new(@source_buffer,
|
57
69
|
[@begin_pos, other.begin_pos].min,
|
58
|
-
[@end_pos,
|
70
|
+
[@end_pos, other.end_pos].max)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(other)
|
74
|
+
other.is_a?(Range) &&
|
75
|
+
@source_buffer == other.source_buffer &&
|
76
|
+
@begin_pos == other.begin_pos &&
|
77
|
+
@end_pos == other.end_pos
|
59
78
|
end
|
60
79
|
|
61
80
|
def inspect
|
62
|
-
"#<Source::Range #{@source_buffer.name} #{@begin_pos}
|
81
|
+
"#<Source::Range #{@source_buffer.name} #{@begin_pos}...#{@end_pos}>"
|
63
82
|
end
|
64
83
|
end
|
65
84
|
|