Class: Moxml::XPath::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/moxml/xpath/compiler.rb

Overview

Compiler for transforming XPath AST into executable Ruby code.

This class takes an XPath AST (produced by Parser) and compiles it into a Ruby Proc that can be executed against XML documents. The compilation process:

  1. Traverse the XPath AST

  2. Generate Ruby::Node AST representing Ruby code

  3. Use Ruby::Generator to convert to Ruby source string

  4. Evaluate source in Context to get a Proc

Examples:

ast = Parser.parse("//book")
proc = Compiler.compile_with_cache(ast)
result = proc.call(document)

Constant Summary collapse

CONTEXT =

Shared context for compiled Procs

Context.new
CACHE =

Expression cache

Cache.new
STAR =

Wildcard for node names/namespace prefixes

"*"
RETURN_NODESET =

Node types that require a NodeSet to push nodes into

%i[path absolute_path relative_path axis
predicate].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(namespaces: nil) ⇒ Compiler

Initialize compiler

Parameters:

  • namespaces (Hash, nil) (defaults to: nil)

    Optional namespace prefix mappings



49
50
51
52
53
54
# File 'lib/moxml/xpath/compiler.rb', line 49

def initialize(namespaces: nil)
  @namespaces = namespaces
  @literal_id = 0
  @predicate_nodesets = []
  @predicate_indexes = []
end

Class Method Details

.compile_with_cache(ast, namespaces: nil) ⇒ Proc

Compiles and caches an AST

Parameters:

  • ast (AST::Node)

    XPath AST to compile

  • namespaces (Hash, nil) (defaults to: nil)

    Optional namespace prefix mappings

Returns:

  • (Proc)

    Compiled Proc that accepts a document



41
42
43
44
# File 'lib/moxml/xpath/compiler.rb', line 41

def self.compile_with_cache(ast, namespaces: nil)
  cache_key = namespaces ? [ast, namespaces] : ast
  CACHE.get_or_set(cache_key) { new(namespaces: namespaces).compile(ast) }
end

Instance Method Details

#compile(ast) ⇒ Proc

Compiles an XPath AST into a Ruby Proc

Parameters:

Returns:

  • (Proc)

    Executable Proc



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
92
93
94
95
96
97
98
99
100
101
# File 'lib/moxml/xpath/compiler.rb', line 60

def compile(ast)
  document = literal(:node)
  matched = matched_literal
  context_var = context_literal

  ruby_ast = if return_nodeset?(ast)
               process(ast, document) { |node| matched.push(node) }
             else
               process(ast, document)
             end

  proc_ast = literal(:lambda).add_block(document) do
    # Get context from document
    context_assign = context_var.assign(document.context)

    if return_nodeset?(ast)
      # Create NodeSet using send node: Moxml::NodeSet.new([], context)
      nodeset_class = const_ref("Moxml", "NodeSet")
      empty_array = Ruby::Node.new(:array, [])
      nodeset_new = Ruby::Node.new(:send,
                                   [nodeset_class, "new", empty_array,
                                    context_var])

      body = matched.assign(nodeset_new)
        .followed_by(ruby_ast)
        .followed_by(matched)
    else
      body = ruby_ast
    end

    context_assign.followed_by(body)
  end

  generator = Ruby::Generator.new
  source = generator.process(proc_ast)

  CONTEXT.evaluate(source)
ensure
  @literal_id = 0
  @predicate_nodesets.clear
  @predicate_indexes.clear
end

#on_binary_op(ast, input, &block) ⇒ Object

Dispatcher for generic binary operator nodes



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/moxml/xpath/compiler.rb', line 114

def on_binary_op(ast, input, &block)
  operator = ast.value # :eq, :lt, :add, :plus, :star, etc.

  # Map token names to handler method names
  method_name = case operator
                when :plus then :add
                when :minus then :sub
                when :star then :mul
                else operator # eq, lt, gt, div, mod, etc.
                end

  send(:"on_#{method_name}", ast, input, &block)
end

#on_unary_op(ast, input, &block) ⇒ Object

Dispatcher for generic unary operator nodes



129
130
131
132
# File 'lib/moxml/xpath/compiler.rb', line 129

def on_unary_op(ast, input, &block)
  operator = ast.value # :minus
  send(:"on_#{operator}", ast, input, &block)
end

#on_union(ast, input, &block) ⇒ Object

Dispatcher for union nodes (parser creates :union, compiler uses :pipe)



135
136
137
# File 'lib/moxml/xpath/compiler.rb', line 135

def on_union(ast, input, &block)
  on_pipe(ast, input, &block)
end

#process(ast, input) {|Ruby::Node| ... } ⇒ Ruby::Node

Process a single XPath AST node

Parameters:

Yields:

  • (Ruby::Node)

    Yields matched nodes if block given

Returns:



109
110
111
# File 'lib/moxml/xpath/compiler.rb', line 109

def process(ast, input, &block)
  send(:"on_#{ast.type}", ast, input, &block)
end