parser 2.3.0.pre.4 → 2.3.0.pre.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile +4 -0
- data/Rakefile +3 -3
- data/lib/parser/diagnostic.rb +68 -12
- data/lib/parser/lexer.rl +1 -1
- data/lib/parser/source/buffer.rb +20 -0
- data/lib/parser/source/range.rb +25 -0
- data/lib/parser/source/rewriter.rb +66 -14
- data/lib/parser/version.rb +1 -1
- data/test/helper.rb +2 -2
- data/test/test_diagnostic.rb +25 -2
- data/test/test_source_buffer.rb +16 -0
- data/test/test_source_range.rb +28 -0
- data/test/test_source_rewriter.rb +36 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8603dec71087cb78e504f3ed0175962f075dde01
|
4
|
+
data.tar.gz: 518d432e89bce2f3a03f9cbabc1e5917040e2c2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10f802d02d6a4e4a80deb6e427fc68d123e86b51e00bdda09e06cbb30e4c33c795b11de47e58e5e370e0117bceca458c9660e416c0489e4601e680bf1c296f2b
|
7
|
+
data.tar.gz: 94dbc707d086703b9060687dceb6c7d806ad01c17619e4be7fd379bdbfb4dbe56212ac289443c513a4ba0c097f469390284e93dbd7708faf7b75dca4daa45158
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -30,12 +30,12 @@ GENERATED_FILES = %w(lib/parser/lexer.rb
|
|
30
30
|
|
31
31
|
CLEAN.include(GENERATED_FILES)
|
32
32
|
|
33
|
-
desc 'Generate the Ragel lexer and
|
33
|
+
desc 'Generate the Ragel lexer and Racc parser.'
|
34
34
|
task :generate => GENERATED_FILES do
|
35
35
|
Rake::Task[:ragel_check].invoke
|
36
36
|
GENERATED_FILES.each do |filename|
|
37
37
|
content = File.read(filename)
|
38
|
-
content = "# -*- encoding:utf-8; warn-indent:false -*-\n" + content
|
38
|
+
content = "# -*- encoding:utf-8; warn-indent:false; frozen_string_literal: true -*-\n" + content
|
39
39
|
|
40
40
|
File.open(filename, 'w') do |io|
|
41
41
|
io.write content
|
@@ -45,7 +45,7 @@ end
|
|
45
45
|
|
46
46
|
task :regenerate => [:clean, :generate]
|
47
47
|
|
48
|
-
desc 'Generate the Ragel lexer and
|
48
|
+
desc 'Generate the Ragel lexer and Racc parser in release mode.'
|
49
49
|
task :generate_release => [:clean_env, :regenerate]
|
50
50
|
|
51
51
|
task :clean_env do
|
data/lib/parser/diagnostic.rb
CHANGED
@@ -82,23 +82,79 @@ module Parser
|
|
82
82
|
# @return [Array<String>]
|
83
83
|
#
|
84
84
|
def render
|
85
|
-
|
85
|
+
if @location.line != @location.last_line
|
86
|
+
# multi-line diagnostic
|
87
|
+
first_line = first_line_only(@location)
|
88
|
+
last_line = last_line_only(@location)
|
89
|
+
buffer = @location.source_buffer
|
90
|
+
|
91
|
+
last_lineno, last_column = buffer.decompose_position(@location.end_pos)
|
92
|
+
["#{@location}-#{last_lineno}:#{last_column}: #{@level}: #{message}"] +
|
93
|
+
render_line(first_line, true, false) +
|
94
|
+
render_line(last_line, false, true)
|
95
|
+
else
|
96
|
+
["#{@location}: #{@level}: #{message}"] + render_line(@location)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
##
|
103
|
+
# Renders one source line in clang diagnostic style, with highlights.
|
104
|
+
#
|
105
|
+
# @return [Array<String>]
|
106
|
+
#
|
107
|
+
def render_line(range, range_start=false, range_end=false)
|
108
|
+
source_line = range.source_line
|
86
109
|
highlight_line = ' ' * source_line.length
|
87
110
|
|
88
|
-
@highlights.each do |
|
89
|
-
|
90
|
-
|
111
|
+
@highlights.each do |highlight|
|
112
|
+
line_range = range.source_buffer.line_range(range.line)
|
113
|
+
if highlight = highlight.intersect(line_range)
|
114
|
+
highlight_line[highlight.column_range] = '~' * highlight.size
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if !range_end && range.size >= 1
|
119
|
+
highlight_line[range.column_range] = '^' + '~' * (range.size - 1)
|
120
|
+
else
|
121
|
+
highlight_line[range.column_range] = '~' * range.size
|
91
122
|
end
|
92
123
|
|
93
|
-
|
94
|
-
|
124
|
+
if range_start
|
125
|
+
highlight_line += '...'
|
126
|
+
end
|
95
127
|
|
96
|
-
[
|
97
|
-
"#{
|
98
|
-
source_line,
|
99
|
-
highlight_line,
|
100
|
-
]
|
128
|
+
[source_line, highlight_line].
|
129
|
+
map { |line| "#{range.source_buffer.name}:#{range.line}: #{line}" }
|
101
130
|
end
|
102
|
-
end
|
103
131
|
|
132
|
+
##
|
133
|
+
# If necessary, shrink a `Range` so as to include only the first line.
|
134
|
+
#
|
135
|
+
# @return [Parser::Source::Range]
|
136
|
+
#
|
137
|
+
def first_line_only(range)
|
138
|
+
if range.line != range.last_line
|
139
|
+
range.resize(range.source =~ /\n/)
|
140
|
+
else
|
141
|
+
range
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# If necessary, shrink a `Range` so as to include only the last line.
|
147
|
+
#
|
148
|
+
# @return [Parser::Source::Range]
|
149
|
+
#
|
150
|
+
def last_line_only(range)
|
151
|
+
if range.line != range.last_line
|
152
|
+
Source::Range.new(range.source_buffer,
|
153
|
+
range.begin_pos + (range.source =~ /[^\n]*\Z/),
|
154
|
+
range.end_pos)
|
155
|
+
else
|
156
|
+
range
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
104
160
|
end
|
data/lib/parser/lexer.rl
CHANGED
@@ -190,7 +190,7 @@ class Parser::Lexer
|
|
190
190
|
|
191
191
|
# This is a workaround for 1.9.2, which (without force_encoding)
|
192
192
|
# would convert the result to UTF-8 (source encoding of lexer.rl).
|
193
|
-
@source += "\0".force_encoding(@encoding)
|
193
|
+
@source += "\0".dup.force_encoding(@encoding)
|
194
194
|
else
|
195
195
|
@source += "\0"
|
196
196
|
end
|
data/lib/parser/source/buffer.rb
CHANGED
@@ -210,6 +210,26 @@ module Parser
|
|
210
210
|
@lines.fetch(lineno - @first_line).dup
|
211
211
|
end
|
212
212
|
|
213
|
+
##
|
214
|
+
# Extract line `lineno` as a new `Range`, taking `first_line` into account.
|
215
|
+
#
|
216
|
+
# @param [Integer] lineno
|
217
|
+
# @return [Range]
|
218
|
+
# @raise [IndexError] if `lineno` is out of bounds
|
219
|
+
#
|
220
|
+
def line_range(lineno)
|
221
|
+
index = lineno - @first_line + 1
|
222
|
+
if index <= 0 || index > line_begins.size
|
223
|
+
raise IndexError, 'Parser::Source::Buffer: range for line ' \
|
224
|
+
"#{lineno} requested, valid line numbers are #{@first_line}.." \
|
225
|
+
"#{@first_line + line_begins.size - 1}"
|
226
|
+
elsif index == line_begins.size
|
227
|
+
Range.new(self, line_begins[-index][1], @source.size)
|
228
|
+
else
|
229
|
+
Range.new(self, line_begins[-index][1], line_begins[-index - 1][1] - 1)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
213
233
|
private
|
214
234
|
|
215
235
|
def line_begins
|
data/lib/parser/source/range.rb
CHANGED
@@ -31,6 +31,10 @@ module Parser
|
|
31
31
|
# @param [Integer] end_pos
|
32
32
|
#
|
33
33
|
def initialize(source_buffer, begin_pos, end_pos)
|
34
|
+
if end_pos < begin_pos
|
35
|
+
raise ArgumentError, 'Parser::Source::Range: end_pos must not be less than begin_pos'
|
36
|
+
end
|
37
|
+
|
34
38
|
@source_buffer = source_buffer
|
35
39
|
@begin_pos, @end_pos = begin_pos, end_pos
|
36
40
|
|
@@ -184,6 +188,27 @@ module Parser
|
|
184
188
|
[@end_pos, other.end_pos].max)
|
185
189
|
end
|
186
190
|
|
191
|
+
##
|
192
|
+
# @param [Range] other
|
193
|
+
# @return [Range] overlapping region of this range and `other`, or `nil`
|
194
|
+
# if they do not overlap
|
195
|
+
#
|
196
|
+
def intersect(other)
|
197
|
+
unless disjoint?(other)
|
198
|
+
Range.new(@source_buffer,
|
199
|
+
[@begin_pos, other.begin_pos].max,
|
200
|
+
[@end_pos, other.end_pos].min)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# @param [Range] other
|
206
|
+
# @return [Boolean] `true` if this range and `other` do not overlap
|
207
|
+
#
|
208
|
+
def disjoint?(other)
|
209
|
+
@begin_pos >= other.end_pos || other.begin_pos >= @end_pos
|
210
|
+
end
|
211
|
+
|
187
212
|
##
|
188
213
|
# Compares ranges.
|
189
214
|
# @return [Boolean]
|
@@ -159,7 +159,33 @@ module Parser
|
|
159
159
|
private
|
160
160
|
|
161
161
|
def append(action)
|
162
|
-
if (
|
162
|
+
if (clobber_actions = clobbered?(action.range))
|
163
|
+
handle_clobber(action, clobber_actions)
|
164
|
+
else
|
165
|
+
clobber(action.range)
|
166
|
+
active_queue << action
|
167
|
+
end
|
168
|
+
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
def clobber(range)
|
173
|
+
self.active_clobber = active_clobber | (2 ** range.size - 1) << range.begin_pos
|
174
|
+
end
|
175
|
+
|
176
|
+
def clobbered?(range)
|
177
|
+
if active_clobber & ((2 ** range.size - 1) << range.begin_pos) != 0
|
178
|
+
active_queue.select do |action|
|
179
|
+
action.range.end_pos > range.begin_pos &&
|
180
|
+
range.end_pos > action.range.begin_pos
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def handle_clobber(action, existing)
|
186
|
+
if can_merge?(action, existing)
|
187
|
+
merge_actions!(action, existing)
|
188
|
+
else
|
163
189
|
# cannot replace 3 characters with "foobar"
|
164
190
|
diagnostic = Diagnostic.new(:error,
|
165
191
|
:invalid_action,
|
@@ -170,30 +196,56 @@ module Parser
|
|
170
196
|
# clobbered by: remove 3 characters
|
171
197
|
diagnostic = Diagnostic.new(:note,
|
172
198
|
:clobbered,
|
173
|
-
{ :action =>
|
174
|
-
|
199
|
+
{ :action => existing[0] },
|
200
|
+
existing[0].range)
|
175
201
|
@diagnostics.process(diagnostic)
|
176
202
|
|
177
203
|
raise ClobberingError, "Parser::Source::Rewriter detected clobbering"
|
178
|
-
|
179
|
-
|
204
|
+
end
|
205
|
+
end
|
180
206
|
|
181
|
-
|
207
|
+
def can_merge?(action, existing)
|
208
|
+
existing.all? do |other|
|
209
|
+
overlap = action.range.intersect(other.range)
|
210
|
+
action_offset = overlap.begin_pos - action.range.begin_pos
|
211
|
+
other_offset = overlap.begin_pos - other.range.begin_pos
|
212
|
+
|
213
|
+
replacement1 = action.replacement[action_offset, overlap.size] || ''
|
214
|
+
replacement2 = other.replacement[other_offset, overlap.size] || ''
|
215
|
+
replacement1 == replacement2
|
182
216
|
end
|
217
|
+
end
|
183
218
|
|
184
|
-
|
219
|
+
def merge_actions!(action, existing)
|
220
|
+
actions = existing.push(action).sort_by { |a| a.range.begin_pos }
|
221
|
+
merged_begin = actions.map { |act| act.range.begin_pos }.min
|
222
|
+
merged_end = actions.map { |act| act.range.end_pos }.max
|
223
|
+
range = Source::Range.new(@source_buffer,
|
224
|
+
merged_begin,
|
225
|
+
merged_end)
|
226
|
+
clobber(range)
|
227
|
+
|
228
|
+
replacement = merge_replacements(actions)
|
229
|
+
replace_actions(actions, Rewriter::Action.new(range, replacement))
|
185
230
|
end
|
186
231
|
|
187
|
-
def
|
188
|
-
|
232
|
+
def replace_actions(old, updated)
|
233
|
+
old.each { |act| active_queue.delete(act) }
|
234
|
+
active_queue << updated
|
189
235
|
end
|
190
236
|
|
191
|
-
def
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
237
|
+
def merge_replacements(actions)
|
238
|
+
# `actions` must be sorted by beginning position
|
239
|
+
begin_pos = actions.first.range.begin_pos
|
240
|
+
result = ''
|
241
|
+
|
242
|
+
actions.each do |act|
|
243
|
+
offset = result.size - act.range.begin_pos + begin_pos
|
244
|
+
next if offset < 0 || offset >= act.replacement.size
|
245
|
+
result << act.replacement[offset..-1]
|
196
246
|
end
|
247
|
+
|
248
|
+
result
|
197
249
|
end
|
198
250
|
|
199
251
|
def in_transaction?
|
data/lib/parser/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -8,7 +8,7 @@ if ENV.include?('COVERAGE') && SimpleCov.usable?
|
|
8
8
|
if defined?(TracePoint)
|
9
9
|
require_relative 'racc_coverage_helper'
|
10
10
|
|
11
|
-
RaccCoverage.start(%w(ruby18.y ruby19.y ruby20.y ruby21.y),
|
11
|
+
RaccCoverage.start(%w(ruby18.y ruby19.y ruby20.y ruby21.y ruby22.y ruby23.y),
|
12
12
|
File.expand_path('../../lib/parser', __FILE__))
|
13
13
|
|
14
14
|
# Report results faster.
|
@@ -33,7 +33,7 @@ if ENV.include?('COVERAGE') && SimpleCov.usable?
|
|
33
33
|
|
34
34
|
# Exclude generated files.
|
35
35
|
add_filter do |source_file|
|
36
|
-
source_file.filename =~ %r{/lib/parser/(lexer|ruby\d
|
36
|
+
source_file.filename =~ %r{/lib/parser/(lexer|ruby\d+|macruby|rubymotion)\.rb$}
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/test/test_diagnostic.rb
CHANGED
@@ -42,8 +42,31 @@ class TestDiagnostic < Minitest::Test
|
|
42
42
|
location, highlights)
|
43
43
|
assert_equal([
|
44
44
|
"(string):1:27: error: unexpected `+'",
|
45
|
-
'if (this is some bad code + bugs)',
|
46
|
-
'
|
45
|
+
'(string):1: if (this is some bad code + bugs)',
|
46
|
+
'(string):1: ~~~~ ^ ~~~~ '
|
47
|
+
], diag.render)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_multiline_render
|
51
|
+
@buffer = Parser::Source::Buffer.new('(string)')
|
52
|
+
@buffer.source = "abc abc abc\ndef def def\nghi ghi ghi\n"
|
53
|
+
|
54
|
+
location = Parser::Source::Range.new(@buffer, 4, 27)
|
55
|
+
|
56
|
+
highlights = [
|
57
|
+
Parser::Source::Range.new(@buffer, 0, 3),
|
58
|
+
Parser::Source::Range.new(@buffer, 28, 31)
|
59
|
+
]
|
60
|
+
|
61
|
+
diag = Parser::Diagnostic.new(:error, :unexpected_token, { :token => 'ghi' },
|
62
|
+
location, highlights)
|
63
|
+
|
64
|
+
assert_equal([
|
65
|
+
"(string):1:5-3:3: error: unexpected token ghi",
|
66
|
+
'(string):1: abc abc abc',
|
67
|
+
'(string):1: ~~~ ^~~~~~~...',
|
68
|
+
'(string):3: ghi ghi ghi',
|
69
|
+
'(string):3: ~~~ ~~~ '
|
47
70
|
], diag.render)
|
48
71
|
end
|
49
72
|
end
|
data/test/test_source_buffer.rb
CHANGED
@@ -100,4 +100,20 @@ class TestSourceBuffer < Minitest::Test
|
|
100
100
|
assert_equal '1', @buffer.source_line(5)
|
101
101
|
assert_equal 'foo', @buffer.source_line(6)
|
102
102
|
end
|
103
|
+
|
104
|
+
def test_line_range
|
105
|
+
@buffer = Parser::Source::Buffer.new('(string)', 5)
|
106
|
+
@buffer.source = "abc\ndef\nghi\n"
|
107
|
+
|
108
|
+
assert_raises IndexError do
|
109
|
+
@buffer.line_range(4)
|
110
|
+
end
|
111
|
+
assert_equal 'abc', @buffer.line_range(5).source
|
112
|
+
assert_equal 'def', @buffer.line_range(6).source
|
113
|
+
assert_equal 'ghi', @buffer.line_range(7).source
|
114
|
+
assert_equal '', @buffer.line_range(8).source
|
115
|
+
assert_raises IndexError do
|
116
|
+
@buffer.line_range(9)
|
117
|
+
end
|
118
|
+
end
|
103
119
|
end
|
data/test/test_source_range.rb
CHANGED
@@ -18,6 +18,12 @@ class TestSourceRange < Minitest::Test
|
|
18
18
|
assert_equal 2, sr.size
|
19
19
|
end
|
20
20
|
|
21
|
+
def test_bad_size
|
22
|
+
assert_raises ArgumentError do
|
23
|
+
Parser::Source::Range.new(@buf, 2, 1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
21
27
|
def test_join
|
22
28
|
sr1 = Parser::Source::Range.new(@buf, 1, 2)
|
23
29
|
sr2 = Parser::Source::Range.new(@buf, 5, 8)
|
@@ -27,6 +33,28 @@ class TestSourceRange < Minitest::Test
|
|
27
33
|
assert_equal 8, sr.end_pos
|
28
34
|
end
|
29
35
|
|
36
|
+
def test_intersect
|
37
|
+
sr1 = Parser::Source::Range.new(@buf, 1, 3)
|
38
|
+
sr2 = Parser::Source::Range.new(@buf, 2, 6)
|
39
|
+
sr3 = Parser::Source::Range.new(@buf, 5, 8)
|
40
|
+
|
41
|
+
assert_equal 2, sr1.intersect(sr2).begin_pos
|
42
|
+
assert_equal 3, sr1.intersect(sr2).end_pos
|
43
|
+
assert_equal 5, sr2.intersect(sr3).begin_pos
|
44
|
+
assert_equal 6, sr2.intersect(sr3).end_pos
|
45
|
+
assert sr1.intersect(sr3) == nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_disjoint
|
49
|
+
sr1 = Parser::Source::Range.new(@buf, 1, 3)
|
50
|
+
sr2 = Parser::Source::Range.new(@buf, 2, 6)
|
51
|
+
sr3 = Parser::Source::Range.new(@buf, 5, 8)
|
52
|
+
|
53
|
+
assert sr1.disjoint?(sr3)
|
54
|
+
assert !sr1.disjoint?(sr2)
|
55
|
+
assert !sr2.disjoint?(sr3)
|
56
|
+
end
|
57
|
+
|
30
58
|
def test_line
|
31
59
|
sr = Parser::Source::Range.new(@buf, 7, 8)
|
32
60
|
assert_equal 2, sr.line
|
@@ -111,6 +111,42 @@ class TestSourceRewriter < Minitest::Test
|
|
111
111
|
assert rescued
|
112
112
|
end
|
113
113
|
|
114
|
+
def test_overlapping_delete
|
115
|
+
assert_equal 'faz',
|
116
|
+
@rewriter.
|
117
|
+
remove(range(1, 4)).
|
118
|
+
remove(range(6, 3)).
|
119
|
+
remove(range(4, 3)).
|
120
|
+
process
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_overlapping_replace
|
124
|
+
assert_equal 'flippin flyin flapjackz',
|
125
|
+
@rewriter.
|
126
|
+
replace(range(1, 4), 'lippin f').
|
127
|
+
replace(range(4, 4), 'pin flyin flap').
|
128
|
+
replace(range(7, 3), ' flyin flapjack').
|
129
|
+
process
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_subsuming_delete
|
133
|
+
assert_equal 'foo',
|
134
|
+
@rewriter.
|
135
|
+
remove(range(6, 3)).
|
136
|
+
remove(range(7, 2)).
|
137
|
+
remove(range(3, 8)).
|
138
|
+
process
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_subsuming_replace
|
142
|
+
assert_equal 'freebie',
|
143
|
+
@rewriter.
|
144
|
+
replace(range(3, 3), 'ebi').
|
145
|
+
replace(range(1, 10), 'reebie').
|
146
|
+
replace(range(5, 2), 'ie').
|
147
|
+
process
|
148
|
+
end
|
149
|
+
|
114
150
|
def test_transaction_returns_self
|
115
151
|
assert_equal @rewriter, @rewriter.transaction {}
|
116
152
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.0.pre.
|
4
|
+
version: 2.3.0.pre.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- whitequark
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ast
|