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

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



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

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



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

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



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
# File 'lib/moxml/xpath/compiler.rb', line 61

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)
end

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

Dispatcher for generic binary operator nodes



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/moxml/xpath/compiler.rb', line 111

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



126
127
128
129
# File 'lib/moxml/xpath/compiler.rb', line 126

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)



132
133
134
# File 'lib/moxml/xpath/compiler.rb', line 132

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:



106
107
108
# File 'lib/moxml/xpath/compiler.rb', line 106

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