File: optimizer.rb

package info (click to toggle)
ruby-parslet 2.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,260 kB
  • sloc: ruby: 6,157; sh: 8; javascript: 3; makefile: 3
file content (270 lines) | stat: -rw-r--r-- 6,621 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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# Example that demonstrates how a simple erb-like parser could be constructed. 

$:.unshift File.dirname(__FILE__) + "/../lib"

require 'parslet'
require 'parslet/atoms/visitor'
require 'parslet/convenience'

class ErbParser < Parslet::Parser
  rule(:ruby) { (str('%>').absent? >> any).repeat.as(:ruby) }
  
  rule(:expression) { (str('=') >> ruby).as(:expression) }
  rule(:comment) { (str('#') >> ruby).as(:comment) }
  rule(:code) { ruby.as(:code) }
  rule(:erb) { expression | comment | code }
  
  rule(:erb_with_tags) { str('<%') >> erb >> str('%>') }
  rule(:text) { (str('<%').absent? >> any).repeat(1) }
  
  rule(:text_with_ruby) { (text.as(:text) | erb_with_tags).repeat.as(:text) }
  root(:text_with_ruby)
end

class Parslet::Source
  def match_excluding str
    slice_str = @str.check_until(Regexp.new(Regexp.escape(str)))
    return @str.rest_size unless slice_str
    return slice_str.size - str.size
  end
end

class AbsentParser < Parslet::Atoms::Base
  def initialize absent
    @absent = absent
  end

  def try(source, context, consume_all)
    excluding_length = source.match_excluding(@absent)

    if excluding_length >= 1
      return succ(source.consume(excluding_length))
    else
      return context.err(self, source, "Failed absence #{@absent.inspect}.")
    end
  end
end

class Parslet::Optimizer
  module DSL
    def >> other
      Match::Sequence.new(self, other)
    end
    def absent?
      Match::Lookahead.new(false, self)
    end
    def repeat(min=0, max=nil)
      Match::Repetition.new(self, min, max)
    end
  end
  module Match
    class Base
      include DSL

      def visit_parser(root)
        false
      end
      def visit_entity(name, block)
        false
      end
      def visit_named(name, atom)
        false
      end
      def visit_repetition(tag, min, max, atom)
        false
      end
      def visit_alternative(alternatives)
        false
      end
      def visit_sequence(sequence)
        false
      end
      def visit_lookahead(positive, atom)
        false
      end
      def visit_re(regexp)
        false
      end
      def visit_str(str)
        false
      end
      def match(other, bindings)
        @bindings = bindings
        other.accept(self)
      end
    end
    class Str < Base
      def initialize(variable)
        @variable = variable
      end
      def visit_str(str)
        if bound_value=@bindings[@variable]
          return bound_value == str
        else
          @bindings[@variable] = str
          return true
        end
      end
    end
    class Lookahead < Base
      def initialize(positive, expression)
        @positive, @expression = positive, expression
      end
      def visit_lookahead(positive, atom)
        positive == @positive && 
          @expression.match(atom, @bindings)
      end
    end
    class Sequence < Base
      def initialize(*parslets)
        @parslets = parslets
      end
      def visit_sequence(sequence)
        sequence.zip(@parslets).all? { |atom, expr| expr.match(atom, @bindings) }
      end
    end
    class Repetition < Base
      def initialize(expression, min, max)
        @min, @max, @expression = min, max, expression
      end
      def visit_repetition(tag, min, max, atom)
        @min == min && @max == max && @expression.match(atom, @bindings)
      end
    end
    class Re < Base
      def initialize(variable)
        @variable = variable
      end
      def visit_re(regexp)
        case @variable
          when Symbol
            p [@variable, regexp]
            fail
        else
          @variable == regexp
        end
      end
    end
  end

  def self.str(var)
    Match::Str.new(var)
  end
  def self.any
    Match::Re.new('.')
  end
  
  class Rule
    def initialize(expression, replacement)
      @expression, @replacement = expression, replacement
    end

    class Context
      def initialize(bindings)
        @bindings = bindings
      end
      def method_missing(sym, *args, &block)
        if args.size == 0 && !block && @bindings.has_key?(sym)
          return @bindings[sym]
        end
        
        super
      end
      def call(callable)
        instance_eval(&callable)
      end
    end

    def match other
      bindings = {}
      if @expression.match(other, bindings)
        return bindings
      end
    end
    def call(bindings)
      context = Context.new(bindings)
      context.call(@replacement)
    end
  end
  def self.rule(expression, &replacement)
    rules << Rule.new(expression, replacement)
  end
  def self.rules
    @rules ||= []
  end
  def rules
    self.class.rules
  end

  class Transform
    def initialize(rules)
      @rules = rules
      @candidates = []
    end

    def default_parser(root)
      root.accept(self)
    end
    def default_entity(name, block)
      Parslet::Atoms::Entity.new(name) { block.call.accept(self) }
    end
    def default_named(name, atom)
      Parslet::Atoms::Named.new(atom.accept(self), name)
    end
    def default_repetition(tag, min, max, atom)
      Parslet::Atoms::Repetition.new(atom.accept(self), min, max, tag)
    end
    def default_alternative(alternatives)
      Parslet::Atoms::Alternative.new(
        *alternatives.map { |atom| atom.accept(self) })
    end
    def default_sequence(sequence)
      Parslet::Atoms::Sequence.new(
        *sequence.map { |atom| atom.accept(self) })
    end
    def default_lookahead(positive, atom)
      Parslet::Atoms::Lookahead.new(atom, positive)
    end
    def default_re(regexp)
      Parslet::Atoms::Re.new(regexp)
    end
    def default_str(str)
      Parslet::Atoms::Str.new(str)
    end

    def method_missing(sym, *args, &block)
      if (md=sym.to_s.match(/visit_([a-z]+)/)) && !block
        # Obtain the default, which is a completely transformed new parser
        default = self.send("default_#{md[1]}", *args)
        # Try transforming this parser again at the current level
        return transform(default)
      end

      super
    end
    def transform(atom)
      # Try to match one of the rules against the newly constructed tree.
      @rules.each do |rule|
        if bindings=rule.match(atom)
          return rule.call(bindings)
        end
      end

      # No match, returning new atom.
      return atom
    end
  end

  def apply(parser)
    parser.accept(Transform.new(rules))
  end
end
class Optimizer < Parslet::Optimizer
  rule((str(:x).absent? >> any).repeat(1)) {
    AbsentParser.new(x) }
end

parser = ErbParser.new
optimized_parser = Optimizer.new.apply(parser)
# p optimized_parser.parse(File.read(ARGV.first))
p parser.parse_with_debug(File.read(ARGV.first))