parser 2.3.0.pre.4 → 2.3.0.pre.5
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/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
|