~ubuntu-branches/ubuntu/utopic/ruby-kpeg/utopic

« back to all changes in this revision

Viewing changes to lib/kpeg/code_generator.rb

  • Committer: Package Import Robot
  • Author(s): Dominique Dumont
  • Date: 2014-04-15 09:01:50 UTC
  • Revision ID: package-import@ubuntu.com-20140415090150-b3pvbpg66n78yp4x
Tags: upstream-1.0.0
ImportĀ upstreamĀ versionĀ 1.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'kpeg/grammar_renderer'
 
2
require 'stringio'
 
3
 
 
4
module KPeg
 
5
  class CodeGenerator
 
6
    def initialize(name, gram, debug=false)
 
7
      @name = name
 
8
      @grammar = gram
 
9
      @debug = debug
 
10
      @saves = 0
 
11
      @output = nil
 
12
      @standalone = false
 
13
    end
 
14
 
 
15
    attr_accessor :standalone
 
16
 
 
17
    def method_name(name)
 
18
      name = name.gsub("-","_hyphen_")
 
19
      "_#{name}"
 
20
    end
 
21
 
 
22
    def save
 
23
      if @saves == 0
 
24
        str = "_save"
 
25
      else
 
26
        str = "_save#{@saves}"
 
27
      end
 
28
 
 
29
      @saves += 1
 
30
      str
 
31
    end
 
32
 
 
33
    def reset_saves
 
34
      @saves = 0
 
35
    end
 
36
 
 
37
    def output_ast(short, code, description)
 
38
      parser = FormatParser.new description
 
39
 
 
40
      # just skip it if it's bad.
 
41
      return unless parser.parse "ast_root"
 
42
 
 
43
      name, attrs = parser.result
 
44
 
 
45
      code << "    class #{name} < Node\n"
 
46
      code << "      def initialize(#{attrs.join(', ')})\n"
 
47
      attrs.each do |at|
 
48
        code << "        @#{at} = #{at}\n"
 
49
      end
 
50
      code << "      end\n"
 
51
      attrs.each do |at|
 
52
        code << "      attr_reader :#{at}\n"
 
53
      end
 
54
      code << "    end\n"
 
55
 
 
56
      [short, name, attrs]
 
57
    end
 
58
 
 
59
    def handle_ast(code)
 
60
      output_node = false
 
61
 
 
62
      root = @grammar.variables["ast-location"] || "AST"
 
63
 
 
64
      methods = []
 
65
 
 
66
      vars = @grammar.variables.keys.sort
 
67
 
 
68
      vars.each do |name|
 
69
        val = @grammar.variables[name]
 
70
 
 
71
        if val.index("ast ") == 0
 
72
          unless output_node
 
73
            code << "\n"
 
74
            code << "  module #{root}\n"
 
75
            code << "    class Node; end\n"
 
76
            output_node = true
 
77
          end
 
78
          if m = output_ast(name, code, val[4..-1])
 
79
            methods << m
 
80
          end
 
81
        end
 
82
      end
 
83
 
 
84
      if output_node
 
85
        code << "  end\n"
 
86
        code << "  module #{root}Construction\n"
 
87
        methods.each do |short, name, attrs|
 
88
          code << "    def #{short}(#{attrs.join(', ')})\n"
 
89
          code << "      #{root}::#{name}.new(#{attrs.join(', ')})\n"
 
90
          code << "    end\n"
 
91
        end
 
92
        code << "  end\n"
 
93
        code << "  include #{root}Construction\n"
 
94
      end
 
95
    end
 
96
 
 
97
    def indentify(code, indent)
 
98
      "#{"  " * indent}#{code}"
 
99
    end
 
100
 
 
101
    # Default indent is 4 spaces (indent=2)
 
102
    def output_op(code, op, indent=2)
 
103
      case op
 
104
      when Dot
 
105
        code << indentify("_tmp = get_byte\n", indent)
 
106
      when LiteralString
 
107
        code << indentify("_tmp = match_string(#{op.string.dump})\n", indent)
 
108
      when LiteralRegexp
 
109
        if op.regexp.respond_to?(:kcode)
 
110
          lang = op.regexp.kcode.to_s[0,1]
 
111
        else
 
112
          # Let default ruby string handling figure it out
 
113
          lang = ""
 
114
        end
 
115
        code << indentify("_tmp = scan(/\\A#{op.regexp}/#{lang})\n", indent)
 
116
      when CharRange
 
117
        ss = save()
 
118
        if op.start.bytesize == 1 and op.fin.bytesize == 1
 
119
          code << indentify("#{ss} = self.pos\n", indent)
 
120
          code << indentify("_tmp = get_byte\n", indent)
 
121
          code << indentify("if _tmp\n", indent)
 
122
 
 
123
          if op.start.respond_to? :getbyte
 
124
            left  = op.start.getbyte 0
 
125
            right = op.fin.getbyte 0
 
126
          else
 
127
            left  = op.start[0]
 
128
            right = op.fin[0]
 
129
          end
 
130
 
 
131
          code << indentify("  unless _tmp >= #{left} and _tmp <= #{right}\n", indent)
 
132
          code << indentify("    self.pos = #{ss}\n", indent)
 
133
          code << indentify("    _tmp = nil\n", indent)
 
134
          code << indentify("  end\n", indent)
 
135
          code << indentify("end\n", indent)
 
136
        else
 
137
          raise "Unsupported char range - #{op.inspect}"
 
138
        end
 
139
      when Choice
 
140
        ss = save()
 
141
        code << "\n"
 
142
        code << indentify("#{ss} = self.pos\n", indent)
 
143
        code << indentify("while true # choice\n", indent)
 
144
        op.ops.each_with_index do |n,idx|
 
145
          output_op code, n, (indent+1)
 
146
 
 
147
          code << indentify("  break if _tmp\n", indent)
 
148
          code << indentify("  self.pos = #{ss}\n", indent)
 
149
          if idx == op.ops.size - 1
 
150
            code << indentify("  break\n", indent)
 
151
          end
 
152
        end
 
153
        code << indentify("end # end choice\n\n", indent)
 
154
      when Multiple
 
155
        ss = save()
 
156
        if op.min == 0 and op.max == 1
 
157
          code << indentify("#{ss} = self.pos\n", indent)
 
158
          output_op code, op.op, indent
 
159
          if op.save_values
 
160
            code << indentify("@result = nil unless _tmp\n", indent)
 
161
          end
 
162
          code << indentify("unless _tmp\n", indent)
 
163
          code << indentify("  _tmp = true\n", indent)
 
164
          code << indentify("  self.pos = #{ss}\n", indent)
 
165
          code << indentify("end\n", indent)
 
166
        elsif op.min == 0 and !op.max
 
167
          if op.save_values
 
168
            code << indentify("_ary = []\n", indent)
 
169
          end
 
170
 
 
171
          code << indentify("while true\n", indent)
 
172
          output_op code, op.op, (indent+1)
 
173
          if op.save_values
 
174
            code << indentify("  _ary << @result if _tmp\n", indent)
 
175
          end
 
176
          code << indentify("  break unless _tmp\n", indent)
 
177
          code << indentify("end\n", indent)
 
178
          code << indentify("_tmp = true\n", indent)
 
179
 
 
180
          if op.save_values
 
181
            code << indentify("@result = _ary\n", indent)
 
182
          end
 
183
 
 
184
        elsif op.min == 1 and !op.max
 
185
          code << indentify("#{ss} = self.pos\n", indent)
 
186
          if op.save_values
 
187
            code << indentify("_ary = []\n", indent)
 
188
          end
 
189
          output_op code, op.op, indent
 
190
          code << indentify("if _tmp\n", indent)
 
191
          if op.save_values
 
192
            code << indentify("  _ary << @result\n", indent)
 
193
          end
 
194
          code << indentify("  while true\n", indent)
 
195
          output_op code, op.op, (indent+2)
 
196
          if op.save_values
 
197
            code << indentify("    _ary << @result if _tmp\n", indent)
 
198
          end
 
199
          code << indentify("    break unless _tmp\n", indent)
 
200
          code << indentify("  end\n", indent)
 
201
          code << indentify("  _tmp = true\n", indent)
 
202
          if op.save_values
 
203
            code << indentify("  @result = _ary\n", indent)
 
204
          end
 
205
          code << indentify("else\n", indent)
 
206
          code << indentify("  self.pos = #{ss}\n", indent)
 
207
          code << indentify("end\n", indent)
 
208
        else
 
209
          code << indentify("#{ss} = self.pos\n", indent)
 
210
          code << indentify("_count = 0\n", indent)
 
211
          code << indentify("while true\n", indent)
 
212
          output_op code, op.op, (indent+1)
 
213
          code << indentify("  if _tmp\n", indent)
 
214
          code << indentify("    _count += 1\n", indent)
 
215
          code << indentify("    break if _count == #{op.max}\n", indent)
 
216
          code << indentify("  else\n", indent)
 
217
          code << indentify("    break\n", indent)
 
218
          code << indentify("  end\n", indent)
 
219
          code << indentify("end\n", indent)
 
220
          code << indentify("if _count >= #{op.min}\n", indent)
 
221
          code << indentify("  _tmp = true\n", indent)
 
222
          code << indentify("else\n", indent)
 
223
          code << indentify("  self.pos = #{ss}\n", indent)
 
224
          code << indentify("  _tmp = nil\n", indent)
 
225
          code << indentify("end\n", indent)
 
226
        end
 
227
 
 
228
      when Sequence
 
229
        ss = save()
 
230
        code << "\n"
 
231
        code << indentify("#{ss} = self.pos\n", indent)
 
232
        code << indentify("while true # sequence\n", indent)
 
233
        op.ops.each_with_index do |n, idx|
 
234
          output_op code, n, (indent+1)
 
235
 
 
236
          if idx == op.ops.size - 1
 
237
            code << indentify("  unless _tmp\n", indent)
 
238
            code << indentify("    self.pos = #{ss}\n", indent)
 
239
            code << indentify("  end\n", indent)
 
240
            code << indentify("  break\n", indent)
 
241
          else
 
242
            code << indentify("  unless _tmp\n", indent)
 
243
            code << indentify("    self.pos = #{ss}\n", indent)
 
244
            code << indentify("    break\n", indent)
 
245
            code << indentify("  end\n", indent)
 
246
          end
 
247
        end
 
248
        code << indentify("end # end sequence\n\n", indent)
 
249
      when AndPredicate
 
250
        ss = save()
 
251
        code << indentify("#{ss} = self.pos\n", indent)
 
252
        if op.op.kind_of? Action
 
253
          code << indentify("_tmp = begin; #{op.op.action}; end\n", indent)
 
254
        else
 
255
          output_op code, op.op, indent
 
256
        end
 
257
        code << indentify("self.pos = #{ss}\n", indent)
 
258
      when NotPredicate
 
259
        ss = save()
 
260
        code << indentify("#{ss} = self.pos\n", indent)
 
261
        if op.op.kind_of? Action
 
262
          code << indentify("_tmp = begin; #{op.op.action}; end\n", indent)
 
263
        else
 
264
          output_op code, op.op, indent
 
265
        end
 
266
        code << indentify("_tmp = _tmp ? nil : true\n", indent)
 
267
        code << indentify("self.pos = #{ss}\n", indent)
 
268
      when RuleReference
 
269
        if op.arguments
 
270
          code << indentify("_tmp = apply_with_args(:#{method_name op.rule_name}, #{op.arguments[1..-2]})\n", indent)
 
271
        else
 
272
          code << indentify("_tmp = apply(:#{method_name op.rule_name})\n", indent)
 
273
        end
 
274
      when InvokeRule
 
275
        if op.arguments
 
276
          code << indentify("_tmp = #{method_name op.rule_name}#{op.arguments}\n", indent)
 
277
        else
 
278
          code << indentify("_tmp = #{method_name op.rule_name}()\n", indent)
 
279
        end
 
280
      when ForeignInvokeRule
 
281
        if op.arguments
 
282
          code << indentify("_tmp = @_grammar_#{op.grammar_name}.external_invoke(self, :#{method_name op.rule_name}, #{op.arguments[1..-2]})\n", indent)
 
283
        else
 
284
          code << indentify("_tmp = @_grammar_#{op.grammar_name}.external_invoke(self, :#{method_name op.rule_name})\n", indent)
 
285
        end
 
286
      when Tag
 
287
        if op.tag_name and !op.tag_name.empty?
 
288
          output_op code, op.op, indent
 
289
          code << indentify("#{op.tag_name} = @result\n", indent)
 
290
        else
 
291
          output_op code, op.op, indent
 
292
        end
 
293
      when Action
 
294
        code << indentify("@result = begin; ", indent)
 
295
        code << op.action << "; end\n"
 
296
        if @debug
 
297
          code << indentify("puts \"   => \" #{op.action.dump} \" => \#{@result.inspect} \\n\"\n", indent)
 
298
        end
 
299
        code << indentify("_tmp = true\n", indent)
 
300
      when Collect
 
301
        code << indentify("_text_start = self.pos\n", indent)
 
302
        output_op code, op.op, indent
 
303
        code << indentify("if _tmp\n", indent)
 
304
        code << indentify("  text = get_text(_text_start)\n", indent)
 
305
        code << indentify("end\n", indent)
 
306
      when Bounds
 
307
        code << indentify("_bounds_start = self.pos\n", indent)
 
308
        output_op code, op.op, indent
 
309
        code << indentify("if _tmp\n", indent)
 
310
        code << indentify("  bounds = [_bounds_start, self.pos]\n", indent)
 
311
        code << indentify("end\n", indent)
 
312
      else
 
313
        raise "Unknown op - #{op.class}"
 
314
      end
 
315
    end
 
316
 
 
317
    def standalone_region(path, marker = "STANDALONE")
 
318
      expanded_path = File.expand_path("../#{path}", __FILE__)
 
319
      cp = File.read(expanded_path)
 
320
 
 
321
      start_marker = "# #{marker} START"
 
322
      end_marker   = /^\s*# #{Regexp.escape marker} END/
 
323
 
 
324
      start = cp.index(start_marker) + start_marker.length + 1 # \n
 
325
      fin   = cp.index(end_marker)
 
326
 
 
327
      unless start and fin
 
328
        abort("#{marker} boundaries in #{path} missing " \
 
329
              "for standalone generation")
 
330
      end
 
331
 
 
332
      cp[start..fin]
 
333
    end
 
334
 
 
335
    def output
 
336
      return @output if @output
 
337
 
 
338
      code = []
 
339
 
 
340
      output_header(code)
 
341
      output_grammar(code)
 
342
      output_footer(code)
 
343
 
 
344
      @output = code.join
 
345
    end
 
346
 
 
347
    ##
 
348
    # Output of class end and footer
 
349
 
 
350
    def output_footer(code)
 
351
      code << "end\n"
 
352
 
 
353
      if footer = @grammar.directives['footer']
 
354
        code << footer.action
 
355
      end
 
356
    end
 
357
 
 
358
    ##
 
359
    # Output of grammar and rules
 
360
 
 
361
    def output_grammar(code)
 
362
      code << "  # :stopdoc:\n"
 
363
      handle_ast(code)
 
364
 
 
365
      fg = @grammar.foreign_grammars
 
366
 
 
367
      if fg.empty?
 
368
        if @standalone
 
369
          code << "  def setup_foreign_grammar; end\n"
 
370
        end
 
371
      else
 
372
        code << "  def setup_foreign_grammar\n"
 
373
        @grammar.foreign_grammars.each do |name, gram|
 
374
          code << "    @_grammar_#{name} = #{gram}.new(nil)\n"
 
375
        end
 
376
        code << "  end\n"
 
377
      end
 
378
 
 
379
      render = GrammarRenderer.new(@grammar)
 
380
 
 
381
      renderings = {}
 
382
 
 
383
      @grammar.rule_order.each do |name|
 
384
        reset_saves
 
385
 
 
386
        rule = @grammar.rules[name]
 
387
        io = StringIO.new
 
388
        render.render_op io, rule.op
 
389
 
 
390
        rend = io.string
 
391
        rend.gsub! "\n", " "
 
392
 
 
393
        renderings[name] = rend
 
394
 
 
395
        code << "\n"
 
396
        code << "  # #{name} = #{rend}\n"
 
397
 
 
398
        if rule.arguments
 
399
          code << "  def #{method_name name}(#{rule.arguments.join(',')})\n"
 
400
        else
 
401
          code << "  def #{method_name name}\n"
 
402
        end
 
403
 
 
404
        if @debug
 
405
          code << "    puts \"START #{name} @ \#{show_pos}\\n\"\n"
 
406
        end
 
407
 
 
408
        output_op code, rule.op
 
409
        if @debug
 
410
          code << "    if _tmp\n"
 
411
          code << "      puts \"   OK #{name} @ \#{show_pos}\\n\"\n"
 
412
          code << "    else\n"
 
413
          code << "      puts \" FAIL #{name} @ \#{show_pos}\\n\"\n"
 
414
          code << "    end\n"
 
415
        end
 
416
 
 
417
        code << "    set_failed_rule :#{method_name name} unless _tmp\n"
 
418
        code << "    return _tmp\n"
 
419
        code << "  end\n"
 
420
      end
 
421
 
 
422
      code << "\n  Rules = {}\n"
 
423
      @grammar.rule_order.each do |name|
 
424
        rend = GrammarRenderer.escape renderings[name], true
 
425
        code << "  Rules[:#{method_name name}] = rule_info(\"#{name}\", \"#{rend}\")\n"
 
426
      end
 
427
 
 
428
      code << "  # :startdoc:\n"
 
429
    end
 
430
 
 
431
    ##
 
432
    # Output up to the user-defined setup actions
 
433
 
 
434
    def output_header(code)
 
435
      if header = @grammar.directives['header']
 
436
        code << header.action.strip
 
437
        code << "\n"
 
438
      end
 
439
 
 
440
      pre_class = @grammar.directives['pre-class']
 
441
 
 
442
      if @standalone
 
443
        if pre_class
 
444
          code << pre_class.action.strip
 
445
          code << "\n"
 
446
        end
 
447
        code << "class #{@name}\n"
 
448
 
 
449
        cp  = standalone_region("compiled_parser.rb")
 
450
        cpi = standalone_region("compiled_parser.rb", "INITIALIZE")
 
451
        pp  = standalone_region("position.rb")
 
452
 
 
453
        cp.gsub!(/^\s*include Position/, pp)
 
454
        code << "  # :stopdoc:\n"
 
455
        code << cpi << "\n" unless @grammar.variables['custom_initialize']
 
456
        code << cp  << "\n"
 
457
        code << "  # :startdoc:\n"
 
458
      else
 
459
        code << "require 'kpeg/compiled_parser'\n\n"
 
460
        if pre_class
 
461
          code << pre_class.action.strip
 
462
          code << "\n"
 
463
        end
 
464
        code << "class #{@name} < KPeg::CompiledParser\n"
 
465
      end
 
466
 
 
467
      @grammar.setup_actions.each do |act|
 
468
        code << "\n#{act.action}\n\n"
 
469
      end
 
470
    end
 
471
 
 
472
    def make(str)
 
473
      m = Module.new
 
474
      m.module_eval output, "(kpeg parser #{@name})"
 
475
 
 
476
      cls = m.const_get(@name)
 
477
      cls.new(str)
 
478
    end
 
479
 
 
480
    def parse(str)
 
481
      make(str).parse
 
482
    end
 
483
  end
 
484
end