~ubuntu-branches/ubuntu/quantal/ruby1.9.1/quantal

« back to all changes in this revision

Viewing changes to lib/rdoc/markup/parser.rb

  • Committer: Bazaar Package Importer
  • Author(s): Lucas Nussbaum
  • Date: 2011-09-24 19:16:17 UTC
  • mfrom: (1.1.8 upstream) (13.1.7 experimental)
  • Revision ID: james.westby@ubuntu.com-20110924191617-o1qz4rcmqjot8zuy
Tags: 1.9.3~rc1-1
* New upstream release: 1.9.3 RC1.
  + Includes load.c fixes. Closes: #639959.
* Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
52
52
  attr_reader :tokens
53
53
 
54
54
  ##
55
 
  # Parsers +str+ into a Document
 
55
  # Parses +str+ into a Document
56
56
 
57
57
  def self.parse str
58
58
    parser = new
59
 
    #parser.debug = true
60
59
    parser.tokenize str
61
 
    RDoc::Markup::Document.new(*parser.parse)
 
60
    doc = RDoc::Markup::Document.new
 
61
    parser.parse doc
62
62
  end
63
63
 
64
64
  ##
86
86
  # Builds a Heading of +level+
87
87
 
88
88
  def build_heading level
89
 
    heading = RDoc::Markup::Heading.new level, text
90
 
    skip :NEWLINE
91
 
 
92
 
    heading
 
89
    type, text, = get
 
90
 
 
91
    text = case type
 
92
           when :TEXT then
 
93
             skip :NEWLINE
 
94
             text
 
95
           else
 
96
             unget
 
97
             ''
 
98
           end
 
99
 
 
100
    RDoc::Markup::Heading.new level, text
93
101
  end
94
102
 
95
103
  ##
105
113
 
106
114
      case type
107
115
      when :BULLET, :LABEL, :LALPHA, :NOTE, :NUMBER, :UALPHA then
108
 
        list_type = type
109
 
 
110
 
        if column < margin then
111
 
          unget
112
 
          break
113
 
        end
114
 
 
115
 
        if list.type and list.type != list_type then
116
 
          unget
117
 
          break
118
 
        end
119
 
 
120
 
        list.type = list_type
 
116
 
 
117
        if column < margin || (list.type && list.type != type) then
 
118
          unget
 
119
          break
 
120
        end
 
121
 
 
122
        list.type = type
 
123
        peek_type, _, column, = peek_token
121
124
 
122
125
        case type
123
126
        when :NOTE, :LABEL then
124
 
          _, indent, = get # SPACE
125
 
          if :NEWLINE == peek_token.first then
126
 
            get
127
 
            peek_type, new_indent, peek_column, = peek_token
128
 
            indent = new_indent if
129
 
              peek_type == :INDENT and peek_column >= column
130
 
            unget
 
127
          if peek_type == :NEWLINE then
 
128
            # description not on the same line as LABEL/NOTE
 
129
            # skip the trailing newline & any blank lines below
 
130
            while peek_type == :NEWLINE
 
131
              get
 
132
              peek_type, _, column, = peek_token
 
133
            end
 
134
 
 
135
            # we may be:
 
136
            #   - at end of stream
 
137
            #   - at a column < margin:
 
138
            #         [text]
 
139
            #       blah blah blah
 
140
            #   - at the same column, but with a different type of list item
 
141
            #       [text]
 
142
            #       * blah blah
 
143
            #   - at the same column, with the same type of list item
 
144
            #       [one]
 
145
            #       [two]
 
146
            # In all cases, we have an empty description.
 
147
            # In the last case only, we continue.
 
148
            if peek_type.nil? || column < margin then
 
149
              empty = 1
 
150
            elsif column == margin then
 
151
              case peek_type
 
152
              when type
 
153
                empty = 2 # continue
 
154
              when *LIST_TOKENS
 
155
                empty = 1
 
156
              else
 
157
                empty = 0
 
158
              end
 
159
            else
 
160
              empty = 0
 
161
            end
 
162
 
 
163
            if empty > 0 then
 
164
              item = RDoc::Markup::ListItem.new(data)
 
165
              item << RDoc::Markup::BlankLine.new
 
166
              list << item
 
167
              break if empty == 1
 
168
              next
 
169
            end
131
170
          end
132
171
        else
133
172
          data = nil
134
 
          _, indent, = get
135
173
        end
136
174
 
137
 
        list_item = build_list_item(margin + indent, data)
 
175
        list_item = RDoc::Markup::ListItem.new data
 
176
        parse list_item, column
 
177
        list << list_item
138
178
 
139
 
        list << list_item if list_item
140
179
      else
141
180
        unget
142
181
        break
151
190
  end
152
191
 
153
192
  ##
154
 
  # Builds a ListItem that is flush to +indent+ with type +item_type+
155
 
 
156
 
  def build_list_item indent, item_type = nil
157
 
    p :list_item_start => [indent, item_type] if @debug
158
 
 
159
 
    list_item = RDoc::Markup::ListItem.new item_type
160
 
 
161
 
    until @tokens.empty? do
162
 
      type, data, column = get
163
 
 
164
 
      if column < indent and
165
 
         not type == :NEWLINE and
166
 
         (type != :INDENT or data < indent) then
167
 
        unget
168
 
        break
169
 
      end
170
 
 
171
 
      case type
172
 
      when :INDENT then
173
 
        unget
174
 
        list_item.push(*parse(indent))
175
 
      when :TEXT then
176
 
        unget
177
 
        list_item << build_paragraph(indent)
178
 
      when :HEADER then
179
 
        list_item << build_heading(data)
180
 
      when :NEWLINE then
181
 
        list_item << RDoc::Markup::BlankLine.new
182
 
      when *LIST_TOKENS then
183
 
        unget
184
 
        list_item << build_list(column)
185
 
      else
186
 
        raise ParseError, "Unhandled token #{@current_token.inspect}"
187
 
      end
188
 
    end
189
 
 
190
 
    p :list_item_end => [indent, item_type] if @debug
191
 
 
192
 
    return nil if list_item.empty?
193
 
 
194
 
    list_item.parts.shift if
195
 
      RDoc::Markup::BlankLine === list_item.parts.first and
196
 
      list_item.length > 1
197
 
 
198
 
    list_item
199
 
  end
200
 
 
201
 
  ##
202
193
  # Builds a Paragraph that is flush to +margin+
203
194
 
204
195
  def build_paragraph margin
209
200
    until @tokens.empty? do
210
201
      type, data, column, = get
211
202
 
212
 
      case type
213
 
      when :INDENT then
214
 
        next if data == margin and peek_token[0] == :TEXT
215
 
 
216
 
        unget
217
 
        break
218
 
      when :TEXT then
219
 
        if column != margin then
220
 
          unget
221
 
          break
222
 
        end
223
 
 
 
203
      if type == :TEXT && column == margin then
224
204
        paragraph << data
225
205
        skip :NEWLINE
226
206
      else
235
215
  end
236
216
 
237
217
  ##
238
 
  # Builds a Verbatim that is flush to +margin+
 
218
  # Builds a Verbatim that is indented from +margin+.
 
219
  #
 
220
  # The verbatim block is shifted left (the least indented lines start in
 
221
  # column 0).  Each part of the verbatim is one line of text, always
 
222
  # terminated by a newline.  Blank lines always consist of a single newline
 
223
  # character, and there is never a single newline at the end of the verbatim.
239
224
 
240
225
  def build_verbatim margin
241
226
    p :verbatim_begin => margin if @debug
242
227
    verbatim = RDoc::Markup::Verbatim.new
243
228
 
 
229
    min_indent = nil
 
230
    generate_leading_spaces = true
 
231
    line = ''
 
232
 
244
233
    until @tokens.empty? do
245
234
      type, data, column, = get
246
235
 
 
236
      if type == :NEWLINE then
 
237
        line << data
 
238
        verbatim << line
 
239
        line = ''
 
240
        generate_leading_spaces = true
 
241
        next
 
242
      end
 
243
 
 
244
      if column <= margin
 
245
        unget
 
246
        break
 
247
      end
 
248
 
 
249
      if generate_leading_spaces then
 
250
        indent = column - margin
 
251
        line << ' ' * indent
 
252
        min_indent = indent if min_indent.nil? || indent < min_indent
 
253
        generate_leading_spaces = false
 
254
      end
 
255
 
247
256
      case type
248
 
      when :INDENT then
249
 
        if margin >= data then
250
 
          unget
251
 
          break
252
 
        end
253
 
 
254
 
        indent = data - margin
255
 
 
256
 
        verbatim << ' ' * indent
257
257
      when :HEADER then
258
 
        verbatim << '=' * data
259
 
 
 
258
        line << '=' * data
260
259
        _, _, peek_column, = peek_token
261
260
        peek_column ||= column + data
262
 
        verbatim << ' ' * (peek_column - column - data)
 
261
        indent = peek_column - column - data
 
262
        line << ' ' * indent
263
263
      when :RULE then
264
264
        width = 2 + data
265
 
        verbatim << '-' * width
266
 
 
 
265
        line << '-' * width
267
266
        _, _, peek_column, = peek_token
268
 
        peek_column ||= column + data + 2
269
 
        verbatim << ' ' * (peek_column - column - width)
 
267
        peek_column ||= column + width
 
268
        indent = peek_column - column - width
 
269
        line << ' ' * indent
270
270
      when :TEXT then
271
 
        verbatim << data
272
 
      when *LIST_TOKENS then
273
 
        if column <= margin then
274
 
          unget
275
 
          break
276
 
        end
277
 
 
 
271
        line << data
 
272
      else # *LIST_TOKENS
278
273
        list_marker = case type
279
 
                      when :BULLET                   then '*'
280
 
                      when :LABEL                    then "[#{data}]"
281
 
                      when :LALPHA, :NUMBER, :UALPHA then "#{data}."
282
 
                      when :NOTE                     then "#{data}::"
 
274
                      when :BULLET then data
 
275
                      when :LABEL  then "[#{data}]"
 
276
                      when :NOTE   then "#{data}::"
 
277
                      else # :LALPHA, :NUMBER, :UALPHA
 
278
                        "#{data}."
283
279
                      end
284
 
 
285
 
        verbatim << list_marker
286
 
 
287
 
        _, data, = get
288
 
 
289
 
        verbatim << ' ' * (data - list_marker.length)
290
 
      when :NEWLINE then
291
 
        verbatim << data
292
 
        break unless [:INDENT, :NEWLINE].include? peek_token[0]
293
 
      else
294
 
        unget
295
 
        break
 
280
        line << list_marker
 
281
        peek_type, _, peek_column = peek_token
 
282
        unless peek_type == :NEWLINE then
 
283
          peek_column ||= column + list_marker.length
 
284
          indent = peek_column - column - list_marker.length
 
285
          line << ' ' * indent
 
286
        end
296
287
      end
 
288
 
297
289
    end
298
290
 
 
291
    verbatim << line << "\n" unless line.empty?
 
292
    verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0
299
293
    verbatim.normalize
300
294
 
301
295
    p :verbatim_end => margin if @debug
313
307
  end
314
308
 
315
309
  ##
316
 
  # Parses the tokens into a Document
 
310
  # Parses the tokens into an array of RDoc::Markup::XXX objects,
 
311
  # and appends them to the passed +parent+ RDoc::Markup::YYY object.
 
312
  #
 
313
  # Exits at the end of the token stream, or when it encounters a token
 
314
  # in a column less than +indent+ (unless it is a NEWLINE).
 
315
  #
 
316
  # Returns +parent+.
317
317
 
318
 
  def parse indent = 0
 
318
  def parse parent, indent = 0
319
319
    p :parse_start => indent if @debug
320
320
 
321
 
    document = []
322
 
 
323
321
    until @tokens.empty? do
324
322
      type, data, column, = get
325
323
 
326
 
      if type != :INDENT and column < indent then
327
 
        unget
328
 
        break
329
 
      end
330
 
 
331
 
      case type
332
 
      when :HEADER then
333
 
        document << build_heading(data)
334
 
      when :INDENT then
335
 
        if indent > data then
336
 
          unget
337
 
          break
338
 
        elsif indent == data then
339
 
          next
340
 
        end
341
 
 
342
 
        unget
343
 
        document << build_verbatim(indent)
344
 
      when :NEWLINE then
345
 
        document << RDoc::Markup::BlankLine.new
 
324
      if type == :NEWLINE then
 
325
        # trailing newlines are skipped below, so this is a blank line
 
326
        parent << RDoc::Markup::BlankLine.new
346
327
        skip :NEWLINE, false
 
328
        next
 
329
      end
 
330
 
 
331
      # indentation change: break or verbatim
 
332
      if column < indent then
 
333
        unget
 
334
        break
 
335
      elsif column > indent then
 
336
        unget
 
337
        parent << build_verbatim(indent)
 
338
        next
 
339
      end
 
340
 
 
341
      # indentation is the same
 
342
      case type
 
343
      when :HEADER then
 
344
        parent << build_heading(data)
347
345
      when :RULE then
348
 
        document << RDoc::Markup::Rule.new(data)
 
346
        parent << RDoc::Markup::Rule.new(data)
349
347
        skip :NEWLINE
350
348
      when :TEXT then
351
349
        unget
352
 
        document << build_paragraph(indent)
353
 
 
354
 
        # we're done with this paragraph (indent mismatch)
355
 
        break if peek_token[0] == :TEXT
 
350
        parent << build_paragraph(indent)
356
351
      when *LIST_TOKENS then
357
352
        unget
358
 
 
359
 
        list = build_list(indent)
360
 
 
361
 
        document << list if list
362
 
 
363
 
        # we're done with this list (indent mismatch)
364
 
        break if LIST_TOKENS.include? peek_token.first and indent > 0
 
353
        parent << build_list(indent)
365
354
      else
366
355
        type, data, column, line = @current_token
367
 
        raise ParseError,
368
 
              "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
 
356
        raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
369
357
      end
370
358
    end
371
359
 
372
360
    p :parse_end => indent if @debug
373
361
 
374
 
    document
 
362
    parent
 
363
 
375
364
  end
376
365
 
377
366
  ##
384
373
  end
385
374
 
386
375
  ##
387
 
  # Skips a token of +token_type+, optionally raising an error.
 
376
  # Skips the next token if its type is +token_type+.
 
377
  #
 
378
  # Optionally raises an error if the next token is not of the expected type.
388
379
 
389
380
  def skip token_type, error = true
390
 
    type, data, = get
391
 
 
 
381
    type, = get
392
382
    return unless type # end of stream
393
 
 
394
383
    return @current_token if token_type == type
395
 
 
396
384
    unget
397
 
 
398
 
    raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if
399
 
      error
400
 
  end
401
 
 
402
 
  ##
403
 
  # Consumes tokens until NEWLINE and turns them back into text
404
 
 
405
 
  def text
406
 
    text = ''
407
 
 
408
 
    loop do
409
 
      type, data, = get
410
 
 
411
 
      text << case type
412
 
              when :BULLET then
413
 
                _, space, = get # SPACE
414
 
                "*#{' ' * (space - 1)}"
415
 
              when :LABEL then
416
 
                _, space, = get # SPACE
417
 
                "[#{data}]#{' ' * (space - data.length - 2)}"
418
 
              when :LALPHA, :NUMBER, :UALPHA then
419
 
                _, space, = get # SPACE
420
 
                "#{data}.#{' ' * (space - 2)}"
421
 
              when :NOTE then
422
 
                _, space = get # SPACE
423
 
                "#{data}::#{' ' * (space - data.length - 2)}"
424
 
              when :TEXT then
425
 
                data
426
 
              when :NEWLINE then
427
 
                unget
428
 
                break
429
 
              when nil then
430
 
                break
431
 
              else
432
 
                raise ParseError, "unhandled token #{@current_token.inspect}"
433
 
              end
434
 
    end
435
 
 
436
 
    text
437
 
  end
438
 
 
439
 
  ##
440
 
  # Calculates the column and line of the current token based on +offset+.
441
 
 
442
 
  def token_pos offset
443
 
    [offset - @line_pos, @line]
 
385
    raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error
444
386
  end
445
387
 
446
388
  ##
455
397
    until s.eos? do
456
398
      pos = s.pos
457
399
 
 
400
      # leading spaces will be reflected by the column of the next token
 
401
      # the only thing we loose are trailing spaces at the end of the file
 
402
      next if s.scan(/ +/)
 
403
 
 
404
      # note: after BULLET, LABEL, etc.,
 
405
      # indent will be the column of the next non-newline token
 
406
 
458
407
      @tokens << case
 
408
                 # [CR]LF => :NEWLINE
459
409
                 when s.scan(/\r?\n/) then
460
410
                   token = [:NEWLINE, s.matched, *token_pos(pos)]
461
411
                   @line_pos = s.pos
462
412
                   @line += 1
463
413
                   token
464
 
                 when s.scan(/ +/) then
465
 
                   [:INDENT, s.matched_size, *token_pos(pos)]
466
 
                 when s.scan(/(=+)\s*/) then
 
414
                 # === text => :HEADER then :TEXT
 
415
                 when s.scan(/(=+)(\s*)/) then
467
416
                   level = s[1].length
468
 
                   level = 6 if level > 6
469
 
                   @tokens << [:HEADER, level, *token_pos(pos)]
 
417
                   header = [:HEADER, level, *token_pos(pos)]
470
418
 
471
 
                   pos = s.pos
472
 
                   s.scan(/.*/)
473
 
                   [:TEXT, s.matched, *token_pos(pos)]
474
 
                 when s.scan(/^(-{3,}) *$/) then
 
419
                   if s[2] =~ /^\r?\n/ then
 
420
                     s.pos -= s[2].length
 
421
                     header
 
422
                   else
 
423
                     pos = s.pos
 
424
                     s.scan(/.*/)
 
425
                     @tokens << header
 
426
                     [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)]
 
427
                   end
 
428
                 # --- (at least 3) and nothing else on the line => :RULE
 
429
                 when s.scan(/(-{3,}) *$/) then
475
430
                   [:RULE, s[1].length - 2, *token_pos(pos)]
476
 
                 when s.scan(/([*-])\s+/) then
477
 
                   @tokens << [:BULLET, :BULLET, *token_pos(pos)]
478
 
                   [:SPACE, s.matched_size, *token_pos(pos)]
479
 
                 when s.scan(/([a-z]|\d+)\.[ \t]+\S/i) then
 
431
                 # * or - followed by white space and text => :BULLET
 
432
                 when s.scan(/([*-]) +(\S)/) then
 
433
                   s.pos -= s[2].bytesize # unget \S
 
434
                   [:BULLET, s[1], *token_pos(pos)]
 
435
                 # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER
 
436
                 when s.scan(/([a-z]|\d+)\. +(\S)/i) then
 
437
                   # FIXME if tab(s), the column will be wrong
 
438
                   # either support tabs everywhere by first expanding them to
 
439
                   # spaces, or assume that they will have been replaced
 
440
                   # before (and provide a check for that at least in debug
 
441
                   # mode)
480
442
                   list_label = s[1]
481
 
                   width      = s.matched_size - 1
482
 
 
483
 
                   s.pos -= 1 # unget \S
484
 
 
485
 
                   list_type = case list_label
486
 
                               when /[a-z]/ then :LALPHA
487
 
                               when /[A-Z]/ then :UALPHA
488
 
                               when /\d/    then :NUMBER
489
 
                               else
490
 
                                 raise ParseError, "BUG token #{list_label}"
491
 
                               end
492
 
 
493
 
                   @tokens << [list_type, list_label, *token_pos(pos)]
494
 
                   [:SPACE, width, *token_pos(pos)]
 
443
                   s.pos -= s[2].bytesize # unget \S
 
444
                   list_type =
 
445
                     case list_label
 
446
                     when /[a-z]/ then :LALPHA
 
447
                     when /[A-Z]/ then :UALPHA
 
448
                     when /\d/    then :NUMBER
 
449
                     else
 
450
                       raise ParseError, "BUG token #{list_label}"
 
451
                     end
 
452
                   [list_type, list_label, *token_pos(pos)]
 
453
                 # [text] followed by spaces or end of line => :LABEL
495
454
                 when s.scan(/\[(.*?)\]( +|$)/) then
496
 
                   @tokens << [:LABEL, s[1], *token_pos(pos)]
497
 
                   [:SPACE, s.matched_size, *token_pos(pos)]
 
455
                   [:LABEL, s[1], *token_pos(pos)]
 
456
                 # text:: followed by spaces or end of line => :NOTE
498
457
                 when s.scan(/(.*?)::( +|$)/) then
499
 
                   @tokens << [:NOTE, s[1], *token_pos(pos)]
500
 
                   [:SPACE, s.matched_size, *token_pos(pos)]
 
458
                   [:NOTE, s[1], *token_pos(pos)]
 
459
                 # anything else: :TEXT
501
460
                 else s.scan(/.*/)
502
 
                   [:TEXT, s.matched, *token_pos(pos)]
 
461
                   [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)]
503
462
                 end
504
463
    end
505
464
 
507
466
  end
508
467
 
509
468
  ##
510
 
  # Returns the current token or +token+ to the token stream
511
 
 
512
 
  def unget token = @current_token
 
469
  # Calculates the column and line of the current token based on +offset+.
 
470
 
 
471
  def token_pos offset
 
472
    [offset - @line_pos, @line]
 
473
  end
 
474
 
 
475
  ##
 
476
  # Returns the current token to the token stream
 
477
 
 
478
  def unget
 
479
    token = @current_token
513
480
    p :unget => token if @debug
514
481
    raise Error, 'too many #ungets' if token == @tokens.first
515
482
    @tokens.unshift token if token
524
491
require 'rdoc/markup/list_item'
525
492
require 'rdoc/markup/raw'
526
493
require 'rdoc/markup/paragraph'
 
494
require 'rdoc/markup/indented_paragraph'
527
495
require 'rdoc/markup/rule'
528
496
require 'rdoc/markup/verbatim'
529
497