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
|
require 'mustermann/ast/translator'
require 'mustermann/ast/compiler'
module Mustermann
module AST
# Looks at an AST, remembers the important bits of information to do an
# ultra fast expansion.
#
# @!visibility private
class Expander < Translator
raises ExpandError
translate Array do |*args|
inject(t.pattern) do |pattern, element|
t.add_to(pattern, t(element, *args))
end
end
translate :capture do |options = {}|
t.for_capture(node, options)
end
translate :named_splat, :splat do
t.pattern + t.for_capture(node)
end
translate :expression do
t(payload, allow_reserved: operator.allow_reserved)
end
translate :root, :group do
t(payload)
end
translate :char do
t.pattern(t.escape(payload, also_escape: /[\/\?#\&\=%]/).gsub(?%, "%%"))
end
translate :separator do
t.pattern(payload.gsub(?%, "%%"))
end
translate :with_look_ahead do
t.add_to(t(head), t(payload))
end
translate :optional do
nested = t(payload)
nested += t.pattern unless nested.any? { |n| n.first.empty? }
nested
end
translate :union do
payload.map { |e| t(e) }.inject(:+)
end
# helper method for captures
# @!visibility private
def for_capture(node, options = {})
name = node.name.to_sym
pattern('%s', name, name => /(?!#{pattern_for(node, options)})./)
end
# maps sorted key list to sprintf patterns and filters
# @!visibility private
def mappings
@mappings ||= {}
end
# all the known keys
# @!visibility private
def keys
@keys ||= []
end
# add a tree for expansion
# @!visibility private
def add(ast)
translate(ast).each do |keys, pattern, filter|
self.keys.concat(keys).uniq!
mappings[keys.sort] ||= [keys, pattern, filter]
end
end
# helper method for getting a capture's pattern.
# @!visibility private
def pattern_for(node, options = {})
Compiler.new.decorator_for(node).pattern(options)
end
# @see Mustermann::Pattern#expand
# @!visibility private
def expand(values)
values = values.each_with_object({}){ |(key, value), new_hash|
new_hash[value.instance_of?(Array) ? [key] * value.length : key] = value }
keys, pattern, filters = mappings.fetch(values.keys.flatten.sort) { error_for(values) }
filters.each { |key, filter| values[key] &&= escape(values[key], also_escape: filter) }
pattern % (values[keys] || values.values_at(*keys))
end
# @see Mustermann::Pattern#expandable?
# @!visibility private
def expandable?(values)
values = values.keys if values.respond_to? :keys
values = values.sort if values.respond_to? :sort
mappings.include? values
end
# @see Mustermann::Expander#with_rest
# @!visibility private
def expandable_keys(keys)
mappings.keys.select { |k| (k - keys).empty? }.max_by(&:size) || keys
end
# helper method for raising an error for unexpandable values
# @!visibility private
def error_for(values)
expansions = mappings.keys.map(&:inspect).join(" or ")
raise error_class, "cannot expand with keys %p, possible expansions: %s" % [values.keys.sort, expansions]
end
# @see Mustermann::AST::Translator#expand
# @!visibility private
def escape(string, *args)
# URI::Parser is pretty slow, let's not send every string to it, even if it's unnecessary
string =~ /\A\w*\Z/ ? string : super
end
# Turns a sprintf pattern into our secret internal data structure.
# @!visibility private
def pattern(string = "", *keys)
filters = keys.last.is_a?(Hash) ? keys.pop : {}
[[keys, string, filters]]
end
# Creates the product of two of our secret internal data structures.
# @!visibility private
def add_to(list, result)
list << [[], ""] if list.empty?
list.inject([]) { |l, (k1, p1, f1)| l + result.map { |k2, p2, f2| [k1+k2, p1+p2, f1.merge(f2)] } }
end
end
end
end
|