Class: Markbridge::AST::Element

Inherits:
Node
  • Object
show all
Defined in:
lib/markbridge/ast/element.rb

Overview

Base class for all AST elements that can contain children. Elements form the structural nodes of the AST tree, while Text nodes are leaves.

Examples:

Creating an element with children

element = AST::Bold.new
element << AST::Text.new("hello")
element << AST::Text.new(" world")
element.children.size # => 1 (consecutive text nodes are merged)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeElement

Returns a new instance of Element.



17
18
19
# File 'lib/markbridge/ast/element.rb', line 17

def initialize
  @children = []
end

Instance Attribute Details

#childrenArray<Node> (readonly)

Returns the child nodes of this element.

Returns:

  • (Array<Node>)

    the child nodes of this element



15
16
17
# File 'lib/markbridge/ast/element.rb', line 15

def children
  @children
end

Instance Method Details

#<<(child) ⇒ Element

Add a child node to this element. Consecutive Text nodes are automatically merged for optimization.

Examples:

Adding children

element << AST::Text.new("hello")
element << AST::Bold.new

Parameters:

  • child (Node)

    the node to add as a child

Returns:

  • (Element)

    self for method chaining

Raises:

  • (TypeError)

    if child is not a Node instance



31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/markbridge/ast/element.rb', line 31

def <<(child)
  unless child.is_a?(Node)
    actual = child.nil? ? "nil" : child.class
    raise TypeError, "<< on #{self.class} expected a #{Node}, got #{actual}"
  end

  if child.instance_of?(Text) && children.last.instance_of?(Text)
    @children.last.merge(child)
  else
    @children << child
  end

  self
end

#descendants(klass = nil) ⇒ Array<Node>

Array of descendant nodes, optionally filtered by class.

document.descendants                    # every descendant
document.descendants(AST::Url)          # every Url descendant

Parameters:

  • klass (Class, nil) (defaults to: nil)

    when given, only descendants that is_a?(klass) are returned

Returns:



79
80
81
82
83
84
# File 'lib/markbridge/ast/element.rb', line 79

def descendants(klass = nil)
  result = each_descendant.to_a
  return result if klass.nil?

  result.select { |node| node.is_a?(klass) }
end

#each_descendant {|node| ... } ⇒ Enumerator, Element

Depth-first pre-order traversal yielding every descendant node. Returns an Enumerator when called without a block so it composes through Enumerable:

document.each_descendant.select { |n| n.is_a?(AST::Url) }

Iteration semantics: each Element snapshots its own children array at the moment iteration enters it, so replacing a child via #replace_child mid-walk is safe — descent uses the pre-replacement reference. Adding or removing siblings on an Element you are currently descending into is not guaranteed to be visible to the current walk.

Yield Parameters:

  • node (Node)

    each descendant in document order

Returns:

  • (Enumerator, Element)

    Enumerator without a block, self otherwise



61
62
63
64
65
66
67
68
69
# File 'lib/markbridge/ast/element.rb', line 61

def each_descendant(&block)
  return enum_for(:each_descendant) unless block_given?

  @children.dup.each do |child|
    yield child
    child.each_descendant(&block) if child.is_a?(Element)
  end
  self
end

#replace_child(old_child, new_child) ⇒ Element

Replace a direct child of this Element with a different Node. Preserves the child’s index — useful for AST-mutation passes that need to swap one Element type for another in place (e.g. wrapping trailing paragraphs in a Details block).

Parameters:

  • old_child (Node)

    the child to remove (matched by equal? via Array#index)

  • new_child (Node)

    the replacement

Returns:

Raises:

  • (ArgumentError)

    when old_child is not currently a child of this Element

  • (TypeError)

    when new_child is not a Node



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/markbridge/ast/element.rb', line 96

def replace_child(old_child, new_child)
  index = @children.index(old_child)
  raise ArgumentError, "child not found in #{self.class}" if index.nil?

  unless new_child.is_a?(Node)
    actual = new_child.nil? ? "nil" : new_child.class
    raise TypeError, "replace_child on #{self.class} expected a #{Node}, got #{actual}"
  end

  @children[index] = new_child
  self
end