File: transformer.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 (177 lines) | stat: -rw-r--r-- 6,041 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
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