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 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