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
|
require 'mustermann/ast/node'
require 'mustermann/error'
require 'delegate'
module Mustermann
module AST
# Implements translator pattern
#
# @abstract
# @!visibility private
class Translator
# Encapsulates a single node translation
# @!visibility private
class NodeTranslator < DelegateClass(Node)
# @param [Array<Symbol, Class>] types list of types to register for.
# @!visibility private
def self.register(*types)
types.each do |type|
type = Node.constant_name(type) if type.is_a? Symbol
translator.dispatch_table[type.to_s] = self
end
end
# @param node [Mustermann::AST::Node, Object]
# @param translator [Mustermann::AST::Translator]
#
# @!visibility private
def initialize(node, translator)
@translator = translator
super(node)
end
# @!visibility private
attr_reader :translator
# shorthand for translating a nested object
# @!visibility private
def t(*args, &block)
return translator unless args.any?
translator.translate(*args, &block)
end
# @!visibility private
alias_method :node, :__getobj__
end
# maps types to translations
# @!visibility private
def self.dispatch_table
@dispatch_table ||= {}
end
# some magic sauce so {NodeTranslator}s know whom to talk to for {#register}
# @!visibility private
def self.inherited(subclass)
node_translator = Class.new(NodeTranslator)
node_translator.define_singleton_method(:translator) { subclass }
subclass.const_set(:NodeTranslator, node_translator)
super
end
# DSL-ish method for specifying the exception class to use.
# @!visibility private
def self.raises(error)
define_method(:error_class) { error }
end
# DSL method for defining single method translations.
# @!visibility private
def self.translate(*types, &block)
Class.new(const_get(:NodeTranslator)) do
register(*types)
define_method(:translate, &block)
end
end
# Enables quick creation of a translator object.
#
# @example
# require 'mustermann'
# require 'mustermann/ast/translator'
#
# translator = Mustermann::AST::Translator.create do
# translate(:node) { [type, *t(payload)].flatten.compact }
# translate(Array) { map { |e| t(e) } }
# translate(Object) { }
# end
#
# ast = Mustermann.new('/:name').to_ast
# translator.translate(ast) # => [:root, :separator, :capture]
#
# @!visibility private
def self.create(&block)
Class.new(self, &block).new
end
raises Mustermann::Error
# @param [Mustermann::AST::Node, Object] node to translate
# @return decorator encapsulating translation
#
# @!visibility private
def decorator_for(node)
factory = node.class.ancestors.inject(nil) { |d,a| d || self.class.dispatch_table[a.name] }
raise error_class, "#{self.class}: Cannot translate #{node.class}" unless factory
factory.new(node, self)
end
# Start the translation dance for a (sub)tree.
# @!visibility private
def translate(node, *args, &block)
result = decorator_for(node).translate(*args, &block)
result = result.node while result.is_a? NodeTranslator
result
end
# @return [String] escaped character
# @!visibility private
def escape(char, options = {})
parser = options[:parser] || URI::DEFAULT_PARSER
escape = options[:escape] || parser.regexp[:UNSAFE]
also_escape = options[:also_escape]
escape = Regexp.union(also_escape, escape) if also_escape
char =~ escape ? parser.escape(char, Regexp.union(*escape)) : char
end
end
end
end
|