Class: RedQuilt::Arena

Inherits:
Object
  • Object
show all
Defined in:
lib/red_quilt/arena.rb,
sig/red_quilt.rbs

Overview

Low-level parallel-array storage for the AST. See docs/arena-usage.ja.md.

Defined Under Namespace

Classes: IntegrityError

Constant Summary collapse

NO_NODE =

Returns:

  • (Integer)
-1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ Arena

Returns a new instance of Arena.

Parameters:

  • source (String)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/red_quilt/arena.rb', line 35

def initialize(source)
  @source = source
  @type = []
  @parent = []
  @first_child = []
  @last_child = []
  @next_sibling = []
  @prev_sibling = []
  @source_start = []
  @source_len = []
  @int1 = []
  @int2 = []
  @int3 = []
  @str1 = []
  @str2 = []
end

Instance Attribute Details

#sourceString (readonly)

Returns the value of attribute source.

Returns:

  • (String)


33
34
35
# File 'lib/red_quilt/arena.rb', line 33

def source
  @source
end

Instance Method Details

#add_node(type, source_start: NO_NODE, source_len: 0, int1: 0, int2: 0, int3: 0, str1: nil, str2: nil) ⇒ Object

Structure mutators. The child/insert/detach helpers return the affected node id; update_* / reparent return values are incidental.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/red_quilt/arena.rb', line 54

def add_node(type, source_start: NO_NODE, source_len: 0, int1: 0, int2: 0, int3: 0, str1: nil, str2: nil)
  id = @type.length
  @type[id] = type
  @parent[id] = NO_NODE
  @first_child[id] = NO_NODE
  @last_child[id] = NO_NODE
  @next_sibling[id] = NO_NODE
  @prev_sibling[id] = NO_NODE
  @source_start[id] = source_start
  @source_len[id] = source_len
  @int1[id] = int1
  @int2[id] = int2
  @int3[id] = int3
  @str1[id] = str1
  @str2[id] = str2
  id
end

#append_child(parent_id, child_id) ⇒ Integer

Parameters:

  • parent_id (Integer)
  • child_id (Integer)

Returns:

  • (Integer)


72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/red_quilt/arena.rb', line 72

def append_child(parent_id, child_id)
  @parent[child_id] = parent_id
  if @first_child[parent_id] == NO_NODE
    @first_child[parent_id] = child_id
    @last_child[parent_id] = child_id
  else
    last = @last_child[parent_id]
    @next_sibling[last] = child_id
    @prev_sibling[child_id] = last
    @last_child[parent_id] = child_id
  end
  child_id
end

#check_integrity!(root_id) ⇒ self

Verifies the structural invariants of the tree rooted at root_id. Raises IntegrityError on the first violation, including the offending node id(s) and a description of the broken rule.

Checked invariants:

  • root has parent = NO_NODE
  • for every reachable node n and its first_child / last_child fc / lc:
    * fc and lc are both NO_NODE, or both not NO_NODE
    * walking next_sibling from fc reaches lc and only lc
    * for each child `c`, @parent[c] == n
    * for each child `c`, @prev_sibling[c] equals the previously
    visited sibling (or NO_NODE for the first)
    
  • no node is reached twice (no shared subtrees, no cycles)

Intended for development / debugging. Not called by the production parse / render path.

Parameters:

  • root_id (Integer)

Returns:

  • (self)

Raises:



429
430
431
432
433
434
435
436
437
438
# File 'lib/red_quilt/arena.rb', line 429

def check_integrity!(root_id)
  raise IntegrityError, "root_id #{root_id} has no row" if root_id >= @type.length
  if @parent[root_id] != NO_NODE
    raise IntegrityError, "root #{root_id} has non-NO_NODE parent #{@parent[root_id]}"
  end

  visited = {}
  walk_for_integrity(root_id, NO_NODE, visited)
  self
end

#child_ids(id) ⇒ Enumerator[Integer, void]

Returns an Enumerator yielding each child id. Kept for the external NodeRef API where Enumerator chaining (map, select, ...) is convenient.

Parameters:

  • id (Integer)

Returns:

  • (Enumerator[Integer, void])


389
390
391
392
393
394
395
396
397
# File 'lib/red_quilt/arena.rb', line 389

def child_ids(id)
  Enumerator.new do |yielder|
    child_id = @first_child[id]
    until child_id == NO_NODE
      yielder << child_id
      child_id = @next_sibling[child_id]
    end
  end
end

#code_block_info(id) ⇒ String?

CODE_BLOCK: the fence info string (e.g. 'ruby', 'vtt audio="x"').

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


298
299
300
# File 'lib/red_quilt/arena.rb', line 298

def code_block_info(id)
  @str2[id]
end

#detach(child_id) ⇒ Integer

Removes child_id from its current parent. The node's row stays in the arena (its payload columns are untouched) but parent / siblings are reset to NO_NODE, so the node is no longer reachable through any tree walk. Detached rows are not reused by subsequent #add_node calls.

Parameters:

  • child_id (Integer)

Returns:

  • (Integer)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/red_quilt/arena.rb', line 106

def detach(child_id)
  parent_id = @parent[child_id]
  prev_id = @prev_sibling[child_id]
  next_id = @next_sibling[child_id]

  if prev_id == NO_NODE
    @first_child[parent_id] = next_id
  else
    @next_sibling[prev_id] = next_id
  end

  if next_id == NO_NODE
    @last_child[parent_id] = prev_id
  else
    @prev_sibling[next_id] = prev_id
  end

  @parent[child_id] = NO_NODE
  @prev_sibling[child_id] = NO_NODE
  @next_sibling[child_id] = NO_NODE
  child_id
end

#each_child(id) {|child_id| ... } ⇒ self

Traversal

Parameters:

  • id (Integer)

Yields:

Yield Parameters:

  • child_id (Integer)

Yield Returns:

  • (void)

Returns:

  • (self)


377
378
379
380
381
382
383
384
# File 'lib/red_quilt/arena.rb', line 377

def each_child(id)
  child_id = @first_child[id]
  until child_id == NO_NODE
    yield child_id
    child_id = @next_sibling[child_id]
  end
  self
end

#footnote_label(id) ⇒ String?

FOOTNOTE_REFERENCE / FOOTNOTE_DEFINITION: the author-written label.

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


324
325
326
# File 'lib/red_quilt/arena.rb', line 324

def footnote_label(id)
  @str1[id]
end

#footnote_number(id) ⇒ Integer

FOOTNOTE_REFERENCE: the assigned footnote number.

Parameters:

  • id (Integer)

Returns:

  • (Integer)


313
314
315
# File 'lib/red_quilt/arena.rb', line 313

def footnote_number(id)
  @int1[id]
end

#footnote_occurrence(id) ⇒ Integer

FOOTNOTE_REFERENCE: which occurrence of a repeated reference this is (1-based), used to give each backref a unique anchor.

Parameters:

  • id (Integer)

Returns:

  • (Integer)


319
320
321
# File 'lib/red_quilt/arena.rb', line 319

def footnote_occurrence(id)
  @int2[id]
end

#footnote_total_references(id) ⇒ Integer

FOOTNOTE_DEFINITION: total number of references to this footnote, materialized by FootnotePass so renderers can read it off the node instead of consulting the registry. (FOOTNOTE_REFERENCE reuses int2 for its own occurrence index; see #footnote_occurrence.)

Parameters:

  • id (Integer)

Returns:

  • (Integer)


332
333
334
# File 'lib/red_quilt/arena.rb', line 332

def footnote_total_references(id)
  @int2[id]
end

#heading_content_len(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


263
264
265
# File 'lib/red_quilt/arena.rb', line 263

def heading_content_len(id)
  @int3[id]
end

#heading_content_start(id) ⇒ Integer

HEADING: byte range of the heading text alone, excluding the # marker and any closing hashes. The node's source_span covers the heading as authored (for position reporting), so inline parsing needs this narrower range to avoid lexing the marker as content. Setext headings carry no marker prefix and leave this zeroed, in which case the span is the content range.

Parameters:

  • id (Integer)

Returns:

  • (Integer)


259
260
261
# File 'lib/red_quilt/arena.rb', line 259

def heading_content_start(id)
  @int2[id]
end

#heading_level(id) ⇒ Integer

Semantic payload accessors (per-NodeType column conventions)

Parameters:

  • id (Integer)

Returns:

  • (Integer)


249
250
251
# File 'lib/red_quilt/arena.rb', line 249

def heading_level(id)
  @int1[id]
end

#insert_before(parent_id, ref_id, new_id) ⇒ Integer

Inserts new_id immediately before ref_id in parent_id's child list.

Parameters:

  • parent_id (Integer)
  • ref_id (Integer)
  • new_id (Integer)

Returns:

  • (Integer)


87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/red_quilt/arena.rb', line 87

def insert_before(parent_id, ref_id, new_id)
  @parent[new_id] = parent_id
  prev_ref = @prev_sibling[ref_id]
  @prev_sibling[new_id] = prev_ref
  @next_sibling[new_id] = ref_id
  @prev_sibling[ref_id] = new_id
  if prev_ref == NO_NODE
    @first_child[parent_id] = new_id
  else
    @next_sibling[prev_ref] = new_id
  end
  new_id
end

#int1(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


214
215
216
# File 'lib/red_quilt/arena.rb', line 214

def int1(id)
  @int1[id]
end

#int2(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


218
219
220
# File 'lib/red_quilt/arena.rb', line 218

def int2(id)
  @int2[id]
end

#int3(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


222
223
224
# File 'lib/red_quilt/arena.rb', line 222

def int3(id)
  @int3[id]
end

LINK / IMAGE: destination URL.

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


303
304
305
# File 'lib/red_quilt/arena.rb', line 303

def link_destination(id)
  @str1[id]
end

LINK / IMAGE: optional title attribute (nil/empty when absent).

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


308
309
310
# File 'lib/red_quilt/arena.rb', line 308

def link_title(id)
  @str2[id]
end

#list_delimiter(id) ⇒ String?

LIST: the item delimiter as authored (e.g. "-", "1.").

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


283
284
285
# File 'lib/red_quilt/arena.rb', line 283

def list_delimiter(id)
  @str1[id]
end

#list_ordered?(id) ⇒ Boolean

LIST: ordered (1.) vs bullet (-).

Parameters:

  • id (Integer)

Returns:

  • (Boolean)


268
269
270
# File 'lib/red_quilt/arena.rb', line 268

def list_ordered?(id)
  @int1[id] == 1
end

#list_start(id) ⇒ Integer

LIST: the start number of an ordered list (1 unless overridden).

Parameters:

  • id (Integer)

Returns:

  • (Integer)


273
274
275
# File 'lib/red_quilt/arena.rb', line 273

def list_start(id)
  @int2[id]
end

#list_tight?(id) ⇒ Boolean

LIST: tight (no blank lines between items) vs loose.

Parameters:

  • id (Integer)

Returns:

  • (Boolean)


278
279
280
# File 'lib/red_quilt/arena.rb', line 278

def list_tight?(id)
  @int3[id] == 1
end

#raw_first_child_id(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


185
186
187
# File 'lib/red_quilt/arena.rb', line 185

def raw_first_child_id(id)
  @first_child[id]
end

#raw_last_child_id(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


189
190
191
# File 'lib/red_quilt/arena.rb', line 189

def raw_last_child_id(id)
  @last_child[id]
end

#raw_next_sibling_id(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


193
194
195
# File 'lib/red_quilt/arena.rb', line 193

def raw_next_sibling_id(id)
  @next_sibling[id]
end

#raw_parent_id(id) ⇒ Integer

Raw id accessors (may return NO_NODE)

Parameters:

  • id (Integer)

Returns:

  • (Integer)


181
182
183
# File 'lib/red_quilt/arena.rb', line 181

def raw_parent_id(id)
  @parent[id]
end

#raw_prev_sibling_id(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


197
198
199
# File 'lib/red_quilt/arena.rb', line 197

def raw_prev_sibling_id(id)
  @prev_sibling[id]
end

#reparent(new_parent_id, first_id, last_id) ⇒ void

This method returns an undefined value.

Moves a contiguous sibling range [first_id .. last_id] (both inclusive, walking #next_sibling from first to last) under new_parent_id, replacing any existing children there. The walk assumes the range is well-formed; passing nodes from different parents or a last_id not reachable from first_id is undefined behavior.

Parameters:

  • new_parent_id (Integer)
  • first_id (Integer)
  • last_id (Integer)


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/red_quilt/arena.rb', line 135

def reparent(new_parent_id, first_id, last_id)
  return if first_id == NO_NODE || last_id == NO_NODE

  original_parent = @parent[first_id]
  prev_of_first = @prev_sibling[first_id]
  next_of_last = @next_sibling[last_id]

  if prev_of_first == NO_NODE
    @first_child[original_parent] = next_of_last
  else
    @next_sibling[prev_of_first] = next_of_last
  end

  if next_of_last == NO_NODE
    @last_child[original_parent] = prev_of_first
  else
    @prev_sibling[next_of_last] = prev_of_first
  end

  @prev_sibling[first_id] = NO_NODE
  @next_sibling[last_id] = NO_NODE

  id = first_id
  loop do
    @parent[id] = new_parent_id
    break if id == last_id

    id = @next_sibling[id]
  end

  @first_child[new_parent_id] = first_id
  @last_child[new_parent_id] = last_id
end

#resolve_footnote_definition(id, number, total_references) ⇒ void

This method returns an undefined value.

FOOTNOTE_DEFINITION: records the resolved number and total reference count onto the node (read back via #footnote_number / #footnote_total_references).

Parameters:

  • id (Integer)
  • number (Integer)
  • total_references (Integer)


339
340
341
342
# File 'lib/red_quilt/arena.rb', line 339

def resolve_footnote_definition(id, number, total_references)
  @int1[id] = number
  @int2[id] = total_references
end

#source_end(id) ⇒ Integer

Byte offset one past the node's source span (start + len).

Parameters:

  • id (Integer)

Returns:

  • (Integer)


210
211
212
# File 'lib/red_quilt/arena.rb', line 210

def source_end(id)
  @source_start[id] + @source_len[id]
end

#source_len(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


205
206
207
# File 'lib/red_quilt/arena.rb', line 205

def source_len(id)
  @source_len[id]
end

#source_span(id) ⇒ SourceSpan?

Semantic accessors

Parameters:

  • id (Integer)

Returns:



346
347
348
349
350
351
# File 'lib/red_quilt/arena.rb', line 346

def source_span(id)
  start_byte = @source_start[id]
  return nil if start_byte.nil? || start_byte.negative?

  SourceSpan.new(start_byte, start_byte + @source_len[id])
end

#source_start(id) ⇒ Integer

Parameters:

  • id (Integer)

Returns:

  • (Integer)


201
202
203
# File 'lib/red_quilt/arena.rb', line 201

def source_start(id)
  @source_start[id]
end

#str1(id) ⇒ String?

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


226
227
228
# File 'lib/red_quilt/arena.rb', line 226

def str1(id)
  @str1[id]
end

#str2(id) ⇒ String?

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


230
231
232
# File 'lib/red_quilt/arena.rb', line 230

def str2(id)
  @str2[id]
end

#table_cell_header?(id) ⇒ Boolean

TABLE_CELL: header cell () vs data cell ().

Parameters:

  • id (Integer)

Returns:

  • (Boolean)


293
294
295
# File 'lib/red_quilt/arena.rb', line 293

def table_cell_header?(id)
  @int1[id] == 1
end

#table_row_header?(id) ⇒ Boolean

TABLE_ROW: header row (rendered in ) vs body row.

Parameters:

  • id (Integer)

Returns:

  • (Boolean)


288
289
290
# File 'lib/red_quilt/arena.rb', line 288

def table_row_header?(id)
  @int1[id] == 1
end

#text(id) ⇒ String?

Returns the node's textual content. Prefers str1 (the literal form, e.g. an entity decoded to its character, or a reassembled blockquote line). Falls back to a byteslice of @source when only a span is recorded. Returns nil if neither is available.

Parameters:

  • id (Integer)

Returns:

  • (String, nil)


357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/red_quilt/arena.rb', line 357

def text(id)
  literal = @str1[id]
  return literal unless literal.nil?

  start_byte = @source_start[id]
  return nil if start_byte.nil? || start_byte.negative?

  # An ATX heading's span deliberately covers its `#` marker so that
  # positions point at the heading as authored. Text is the content
  # alone, so slice the narrower content range instead.
  if @type[id] == NodeType::HEADING && @int2[id].positive?
    return @source.byteslice(@int2[id], @int3[id])
  end

  @source.byteslice(start_byte, @source_len[id])
end

#type(id) ⇒ Integer

Column accessors

Parameters:

  • id (Integer)

Returns:

  • (Integer)


116
117
118
# File 'sig/red_quilt.rbs', line 116

def type(id)
  @type[id]
end

#type_name(id) ⇒ Symbol

Parameters:

  • id (Integer)

Returns:

  • (Symbol)


173
174
175
# File 'lib/red_quilt/arena.rb', line 173

def type_name(id)
  NodeType.name_for(@type[id])
end

#update_int3(id, value) ⇒ void

This method returns an undefined value.

Parameters:

  • id (Integer)
  • value (Integer)


403
404
405
# File 'lib/red_quilt/arena.rb', line 403

def update_int3(id, value)
  @int3[id] = value
end

#update_span(id, start_byte, end_byte) ⇒ void

This method returns an undefined value.

Parameters:

  • id (Integer)
  • start_byte (Integer)
  • end_byte (Integer)


407
408
409
410
# File 'lib/red_quilt/arena.rb', line 407

def update_span(id, start_byte, end_byte)
  @source_start[id] = start_byte
  @source_len[id] = end_byte - start_byte
end

#update_str1(id, value) ⇒ void

This method returns an undefined value.

Parameters:

  • id (Integer)
  • value (String, nil)


399
400
401
# File 'lib/red_quilt/arena.rb', line 399

def update_str1(id, value)
  @str1[id] = value
end