File: translator.rb

package info (click to toggle)
ruby-mustermann19 0.4.3%2Bgit20160621-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 756 kB
  • ctags: 445
  • sloc: ruby: 7,197; makefile: 3
file content (128 lines) | stat: -rw-r--r-- 4,049 bytes parent folder | download
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