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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fea7f55530b85f44e316648564958c278fbaa263
4
- data.tar.gz: 311a216bc1414b18713a9de80866a848354f1c24
3
+ metadata.gz: 8603dec71087cb78e504f3ed0175962f075dde01
4
+ data.tar.gz: 518d432e89bce2f3a03f9cbabc1e5917040e2c2c
5
5
  SHA512:
6
- metadata.gz: 8057a5681f5d217d455a618a2e8706ed5a6333976b57019005c2355339e40d5932753eb7ae3b8acc1dc52e48e6529d15b0f1a7f62f00d8d1b4b5513fd9449fee
7
- data.tar.gz: 2f3d4d028c517bcd42f6498251d4fd264da703e60d9d9ef910b4a91138dc6896920ccfcc4210fcb22b8722e1a6cc1a33cbafb8cb6dee0239f23057ae21724c7b
6
+ metadata.gz: 10f802d02d6a4e4a80deb6e427fc68d123e86b51e00bdda09e06cbb30e4c33c795b11de47e58e5e370e0117bceca458c9660e416c0489e4601e680bf1c296f2b
7
+ data.tar.gz: 94dbc707d086703b9060687dceb6c7d806ad01c17619e4be7fd379bdbfb4dbe56212ac289443c513a4ba0c097f469390284e93dbd7708faf7b75dca4daa45158
@@ -1,6 +1,12 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ v2.3.0.pre.5 (2015-12-16)
5
+ -------------------------
6
+
7
+ API modifications:
8
+ * Source::Diagnostic: output ^^^^^ instead of ^~~~~ (like clang). (whitequark)
9
+
4
10
  v2.3.0.pre.4 (2015-11-26)
5
11
  -------------------------
6
12
 
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in parser.gemspec
4
4
  gemspec
5
+
6
+ # Workaround for bug in Bundler on JRuby
7
+ # See https://github.com/bundler/bundler/issues/4157
8
+ gem 'ast', '>= 1.1', '< 3.0'
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 Bison parser.'
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 Bison parser in release mode.'
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
@@ -82,23 +82,79 @@ module Parser
82
82
  # @return [Array<String>]
83
83
  #
84
84
  def render
85
- source_line = @location.source_line
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 |hilight|
89
- range = hilight.column_range
90
- highlight_line[range] = '~' * hilight.size
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
- range = @location.column_range
94
- highlight_line[range] = '^' * @location.size
124
+ if range_start
125
+ highlight_line += '...'
126
+ end
95
127
 
96
- [
97
- "#{@location.to_s}: #{@level}: #{message}",
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
@@ -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
@@ -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
@@ -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 (clobber_action = clobbered?(action.range))
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 => clobber_action },
174
- clobber_action.range)
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
- else
179
- clobber(action.range)
204
+ end
205
+ end
180
206
 
181
- active_queue << action
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
- self
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 clobber(range)
188
- self.active_clobber = active_clobber | (2 ** range.size - 1) << range.begin_pos
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 clobbered?(range)
192
- if active_clobber & ((2 ** range.size - 1) << range.begin_pos) != 0
193
- active_queue.find do |action|
194
- action.range.to_a & range.to_a
195
- end
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?
@@ -1,3 +1,3 @@
1
1
  module Parser
2
- VERSION = '2.3.0.pre.4'
2
+ VERSION = '2.3.0.pre.5'
3
3
  end
@@ -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+)\.rb$}
36
+ source_file.filename =~ %r{/lib/parser/(lexer|ruby\d+|macruby|rubymotion)\.rb$}
37
37
  end
38
38
  end
39
39
  end
@@ -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
@@ -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
@@ -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
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-26 00:00:00.000000000 Z
11
+ date: 2015-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast