parser 1.2.0 → 1.3.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 +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
|
|