1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 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 168 169 170 171 172 173 174 175 176 177
|
require 'mustermann/ast/translator'
module Mustermann
module AST
# Takes a tree, turns it into an even better tree.
# @!visibility private
class Transformer < Translator
# Transforms a tree.
# @note might mutate handed in tree instead of creating a new one
# @param [Mustermann::AST::Node] tree to be transformed
# @return [Mustermann::AST::Node] transformed tree
# @!visibility private
def self.transform(tree)
new.translate(tree)
end
# recursive descent
translate(:node) do
node.payload = t(payload)
node
end
# ignore unknown objects on the tree
translate(Object) { node }
# turn a group containing or nodes into a union
# @!visibility private
class GroupTransformer < NodeTranslator
register :group
# @!visibility private
def translate
return union if payload.any? { |e| e.is_a? :or }
self.payload = t(payload)
self
end
# @!visibility private
def union
groups = split_payload.map { |g| group(g) }
Node[:union].new(groups, start: node.start, stop: node.stop)
end
# @!visibility private
def group(elements)
return t(elements.first) if elements.size == 1
start, stop = elements.first.start, elements.last.stop if elements.any?
Node[:group].new(t(elements), start: start, stop: stop)
end
# @!visibility private
def split_payload
groups = [[]]
payload.each { |e| e.is_a?(:or) ? groups << [] : groups.last << e }
groups.map!
end
end
# inject a union node right inside the root node if it contains or nodes
# @!visibility private
class RootTransformer < GroupTransformer
register :root
# @!visibility private
def union
self.payload = [super]
self
end
end
# URI expression transformations depending on operator
# @!visibility private
class ExpressionTransform < NodeTranslator
register :expression
# @!visibility private
Operator ||= Struct.new(:separator, :allow_reserved, :prefix, :parametric)
# Operators available for expressions.
# @!visibility private
OPERATORS ||= {
nil => Operator.new(?,, false, false, false), ?+ => Operator.new(?,, true, false, false),
?# => Operator.new(?,, true, ?#, false), ?. => Operator.new(?., false, ?., false),
?/ => Operator.new(?/, false, ?/, false), ?; => Operator.new(?;, false, ?;, true),
?? => Operator.new(?&, false, ??, true), ?& => Operator.new(?&, false, ?&, true)
}
# Sets operator and inserts separators in between variables.
# @!visibility private
def translate
self.operator = OPERATORS.fetch(operator) { raise CompileError, "#{operator} operator not supported" }
separator = Node[:separator].new(operator.separator)
prefix = Node[:separator].new(operator.prefix)
self.payload = Array(payload.inject { |list, element| Array(list) << t(separator.dup) << t(element) })
payload.unshift(prefix) if operator.prefix
self
end
end
# Inserts with_look_ahead nodes wherever appropriate
# @!visibility private
class ArrayTransform < NodeTranslator
register Array
# the new array
# @!visibility private
def payload
@payload ||= []
end
# buffer for potential look ahead
# @!visibility private
def lookahead_buffer
@lookahead_buffer ||= []
end
# transform the array
# @!visibility private
def translate
each { |e| track t(e) }
payload.concat create_lookahead(lookahead_buffer, true)
end
# handle a single element from the array
# @!visibility private
def track(element)
return list_for(element) << element if lookahead_buffer.empty?
return lookahead_buffer << element if lookahead? element
lookahead = lookahead_buffer.dup
lookahead = create_lookahead(lookahead, false) if element.is_a? Node[:separator]
lookahead_buffer.clear
payload.concat(lookahead) << element
end
# turn look ahead buffer into look ahead node
# @!visibility private
def create_lookahead(elements, *args)
return elements unless elements.size > 1
[Node[:with_look_ahead].new(elements, *args, start: elements.first.start, stop: elements.last.stop)]
end
# can the given element be used in a look-ahead?
# @!visibility private
def lookahead?(element, in_lookahead = false)
case element
when Node[:char] then in_lookahead
when Node[:group] then lookahead_payload?(element.payload, in_lookahead)
when Node[:optional] then lookahead?(element.payload, true) or expect_lookahead?(element.payload)
end
end
# does the list of elements look look-ahead-ish to you?
# @!visibility private
def lookahead_payload?(payload, in_lookahead)
return unless payload[0..-2].all? { |e| lookahead?(e, in_lookahead) }
expect_lookahead?(payload.last) or lookahead?(payload.last, in_lookahead)
end
# can the current element deal with a look-ahead?
# @!visibility private
def expect_lookahead?(element)
return element.class == Node[:capture] unless element.is_a? Node[:group]
element.payload.all? { |e| expect_lookahead?(e) }
end
# helper method for deciding where to put an element for now
# @!visibility private
def list_for(element)
expect_lookahead?(element) ? lookahead_buffer : payload
end
end
end
end
end
|