Class: RedQuilt::BlockParser

Inherits:
Object
  • Object
show all
Defined in:
lib/red_quilt/block_parser.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(arena, footnotes: nil) ⇒ BlockParser

Returns a new instance of BlockParser.



5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/red_quilt/block_parser.rb', line 5

def initialize(arena, footnotes: nil)
  @arena = arena
  @lines = build_lines(arena.source)
  @references = {}
  @footnotes = footnotes
  @diagnostics = []
  # Cached collaborator parsers — created once and reused for every
  # block of the corresponding type (including nested ones) so the
  # dispatch path stays allocation-free.
  @list_parser = List::Parser.new(self)
  @blockquote_parser = Blockquote::Parser.new(self)
  @footnote_parser = FootnoteDefinition::Parser.new(self)
end

Instance Attribute Details

#arenaObject (readonly)

Returns the value of attribute arena.



19
20
21
# File 'lib/red_quilt/block_parser.rb', line 19

def arena
  @arena
end

#diagnosticsObject (readonly)

Returns the value of attribute diagnostics.



19
20
21
# File 'lib/red_quilt/block_parser.rb', line 19

def diagnostics
  @diagnostics
end

#referencesObject (readonly)

Returns the value of attribute references.



19
20
21
# File 'lib/red_quilt/block_parser.rb', line 19

def references
  @references
end

Instance Method Details

#lazy_break?(lines, index) ⇒ Boolean

A line at less-than-N indent breaks lazy continuation when it would itself start a new block (heading, thematic break, fenced/indented code, html block, blockquote, list item, table). Same predicate as paragraph_interrupt? minus the “index > 0” guard.

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/red_quilt/block_parser.rb', line 100

def lazy_break?(lines, index)
  line = lines[index]
  return true if atx_heading(line.content)
  return true if thematic_break?(line.content)
  return true if fenced_code_start(line.content)
  # HTML type 7 doesn't break lazy continuation either.
  if (type = html_block_type(line.content)) && type != 7
    return true
  end
  return true if Blockquote.match?(line.content)
  if (li = List.match(line.content)) && List.interrupts_paragraph?(li)
    return true
  end
  return true if table_start?(lines, index)

  false
end

#paragraph_eligible_line?(content) ⇒ Boolean

Returns:

  • (Boolean)


130
131
132
133
134
135
136
137
138
139
140
# File 'lib/red_quilt/block_parser.rb', line 130

def paragraph_eligible_line?(content)
  return false if indented_code_line?(content)
  return false if fenced_code_start(content)
  return false if atx_heading(content)
  return false if thematic_break?(content)
  return false if html_block_start?(content)
  return false if List.match(content)
  return false if Blockquote.match?(content)

  true
end

#parseObject



21
22
23
24
25
26
# File 'lib/red_quilt/block_parser.rb', line 21

def parse
  @root_id = @arena.add_node(NodeType::DOCUMENT, source_start: 0, source_len: @arena.source.bytesize)
  parse_lines(@root_id, @lines, transformed: false)
  @footnote_parser.move_section_to_end(@root_id) if @footnotes
  @root_id
end

#parse_lines(parent_id, lines, transformed:) ⇒ Object

parse_lines returns true if it encountered a blank line BETWEEN two block-level constructs at this scope. parse_list uses that to decide an item’s looseness — the spec says an item is loose when it “directly contains two block-level elements with a blank line between them”, and ref-defs / fence openers that don’t emit an arena child still count as block-level elements.

‘seen_block` guards against treating the empty marker line of a list item (e.g. `-` alone) as a blank “between” anything: the blank only counts after at least one real block has been emitted.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/red_quilt/block_parser.rb', line 38

def parse_lines(parent_id, lines, transformed:)
  saw_blank = false
  seen_block = false
  blank_then_block = false
  index = 0
  while index < lines.length
    line = lines[index]
    if line.blank
      saw_blank = true if seen_block
      index += 1
      next
    end

    blank_then_block = true if saw_blank
    saw_blank = false
    seen_block = true

    content = line.content
    if paragraph_only_line?(content)
      # Fast path: nothing in this line can possibly start a
      # different block, so skip the eight predicate checks below.
      index = parse_paragraph(parent_id, lines, index, transformed)
      next
    end

    if (fence = fenced_code_start(content))
      index = parse_fenced_code(parent_id, lines, index, fence)
    elsif (heading = atx_heading(content))
      append_heading(parent_id, line, heading, transformed)
      index += 1
    elsif thematic_break?(content)
      @arena.append_child(parent_id, @arena.add_node(NodeType::THEMATIC_BREAK, source_start: line.start_byte, source_len: span_len(line)))
      index += 1
    elsif @footnotes && (footnote = FootnoteDefinition.match(content))
      index = @footnote_parser.parse(lines, index, footnote, @footnotes, @root_id)
    elsif (reference = ReferenceDefinition.consume(lines, index))
      store_reference(reference[:reference], reference[:source_span])
      index += reference[:consumed]
    elsif table_start?(lines, index)
      index = parse_table(parent_id, lines, index)
    elsif html_block_start?(content)
      index = parse_html_block(parent_id, lines, index)
    elsif Blockquote.match?(content)
      index = @blockquote_parser.parse(parent_id, lines, index)
    elsif List.match(content)
      index = @list_parser.parse(parent_id, lines, index)
    elsif indented_code_line?(content)
      index = parse_indented_code(parent_id, lines, index)
    else
      index = parse_paragraph(parent_id, lines, index, transformed)
    end
  end
  blank_then_block
end

#thematic_break?(text) ⇒ Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/red_quilt/block_parser.rb', line 126

def thematic_break?(text)
  THEMATIC_BREAK_RE.match?(text)
end