Class: RedQuilt::Arena
- Inherits:
-
Object
- Object
- RedQuilt::Arena
- 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 =
-1
Instance Attribute Summary collapse
-
#source ⇒ String
readonly
Returns the value of attribute source.
Instance Method Summary collapse
-
#add_node(type, source_start: NO_NODE, source_len: 0, int1: 0, int2: 0, int3: 0, str1: nil, str2: nil) ⇒ Object
Structure mutators.
- #append_child(parent_id, child_id) ⇒ Integer
-
#check_integrity!(root_id) ⇒ self
Verifies the structural invariants of the tree rooted at root_id.
-
#child_ids(id) ⇒ Enumerator[Integer, void]
Returns an Enumerator yielding each child id.
-
#code_block_info(id) ⇒ String?
CODE_BLOCK: the fence info string (e.g. 'ruby', 'vtt audio="x"').
-
#detach(child_id) ⇒ Integer
Removes child_id from its current parent.
-
#each_child(id) {|child_id| ... } ⇒ self
Traversal.
-
#footnote_label(id) ⇒ String?
FOOTNOTE_REFERENCE / FOOTNOTE_DEFINITION: the author-written label.
-
#footnote_number(id) ⇒ Integer
FOOTNOTE_REFERENCE: the assigned footnote number.
-
#footnote_occurrence(id) ⇒ Integer
FOOTNOTE_REFERENCE: which occurrence of a repeated reference this is (1-based), used to give each backref a unique anchor.
-
#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.
- #heading_content_len(id) ⇒ Integer
-
#heading_content_start(id) ⇒ Integer
HEADING: byte range of the heading text alone, excluding the
#marker and any closing hashes. -
#heading_level(id) ⇒ Integer
Semantic payload accessors (per-NodeType column conventions).
-
#initialize(source) ⇒ Arena
constructor
A new instance of Arena.
-
#insert_before(parent_id, ref_id, new_id) ⇒ Integer
Inserts new_id immediately before ref_id in parent_id's child list.
- #int1(id) ⇒ Integer
- #int2(id) ⇒ Integer
- #int3(id) ⇒ Integer
-
#link_destination(id) ⇒ String?
LINK / IMAGE: destination URL.
-
#link_title(id) ⇒ String?
LINK / IMAGE: optional title attribute (nil/empty when absent).
-
#list_delimiter(id) ⇒ String?
LIST: the item delimiter as authored (e.g. "-", "1.").
-
#list_ordered?(id) ⇒ Boolean
LIST: ordered (
1.) vs bullet (-). -
#list_start(id) ⇒ Integer
LIST: the start number of an ordered list (1 unless overridden).
-
#list_tight?(id) ⇒ Boolean
LIST: tight (no blank lines between items) vs loose.
- #raw_first_child_id(id) ⇒ Integer
- #raw_last_child_id(id) ⇒ Integer
- #raw_next_sibling_id(id) ⇒ Integer
-
#raw_parent_id(id) ⇒ Integer
Raw id accessors (may return NO_NODE).
- #raw_prev_sibling_id(id) ⇒ Integer
-
#reparent(new_parent_id, first_id, last_id) ⇒ void
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.
-
#resolve_footnote_definition(id, number, total_references) ⇒ void
FOOTNOTE_DEFINITION: records the resolved number and total reference count onto the node (read back via #footnote_number / #footnote_total_references).
-
#source_end(id) ⇒ Integer
Byte offset one past the node's source span (start + len).
- #source_len(id) ⇒ Integer
-
#source_span(id) ⇒ SourceSpan?
Semantic accessors.
- #source_start(id) ⇒ Integer
- #str1(id) ⇒ String?
- #str2(id) ⇒ String?
-
#table_cell_header?(id) ⇒ Boolean
TABLE_CELL: header cell (
) vs data cell ( ). - #table_row_header?(id) ⇒ Boolean
TABLE_ROW: header row (rendered in ) vs body row.
- #text(id) ⇒ String?
Returns the node's textual content.
- #type(id) ⇒ Integer
Column accessors.
- #type_name(id) ⇒ Symbol
- #update_int3(id, value) ⇒ void
- #update_span(id, start_byte, end_byte) ⇒ void
- #update_str1(id, value) ⇒ void
Constructor Details
#initialize(source) ⇒ Arena
Returns a new instance of Arena.
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
#source ⇒ String (readonly)
Returns the value of attribute source.
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
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
nand its first_child / last_childfc/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.
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.
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"').
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.
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
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.
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.
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.
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.)
332 333 334
# File 'lib/red_quilt/arena.rb', line 332 def footnote_total_references(id) @int2[id] end
#heading_content_len(id) ⇒ 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.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)
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.
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
214 215 216
# File 'lib/red_quilt/arena.rb', line 214 def int1(id) @int1[id] end
#int2(id) ⇒ Integer
218 219 220
# File 'lib/red_quilt/arena.rb', line 218 def int2(id) @int2[id] end
#int3(id) ⇒ Integer
222 223 224
# File 'lib/red_quilt/arena.rb', line 222 def int3(id) @int3[id] end
#link_destination(id) ⇒ String?
LINK / IMAGE: destination URL.
303 304 305
# File 'lib/red_quilt/arena.rb', line 303 def link_destination(id) @str1[id] end
#link_title(id) ⇒ String?
LINK / IMAGE: optional title attribute (nil/empty when absent).
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.").
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 (-).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).
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.
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
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
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
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)
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
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.
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).
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).
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
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
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
201 202 203
# File 'lib/red_quilt/arena.rb', line 201 def source_start(id) @source_start[id] end
#str1(id) ⇒ String?
226 227 228
# File 'lib/red_quilt/arena.rb', line 226 def str1(id) @str1[id] end
#str2(id) ⇒ String?
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 ( ). 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.
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.
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
116 117 118
# File 'sig/red_quilt.rbs', line 116 def type(id) @type[id] end
#type_name(id) ⇒ 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.
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.
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.
399 400 401
# File 'lib/red_quilt/arena.rb', line 399 def update_str1(id, value) @str1[id] = value end
- #table_row_header?(id) ⇒ Boolean