sexp2ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1159 @@
1
+ require 'sexp_processor'
2
+
3
+ module Sexp2Ruby
4
+
5
+ # Generate ruby code from a sexp.
6
+ class Processor < SexpProcessor
7
+
8
+ # cutoff for one-liners
9
+ LINE_LENGTH = 78
10
+
11
+ # binary operation messages
12
+ BINARY = [:<=>, :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%, :<<, :>>, :**, :'!=']
13
+
14
+ ##
15
+ # Nodes that represent assignment and probably need () around them.
16
+ #
17
+ # TODO: this should be replaced with full precedence support :/
18
+
19
+ ASSIGN_NODES = [
20
+ :dasgn,
21
+ :flip2,
22
+ :flip3,
23
+ :lasgn,
24
+ :masgn,
25
+ :attrasgn,
26
+ :op_asgn1,
27
+ :op_asgn2,
28
+ :op_asgn_and,
29
+ :op_asgn_or,
30
+ :return,
31
+ :if, # HACK
32
+ :rescue,
33
+ ]
34
+
35
+ ##
36
+ # Some sexp types are OK without parens when appearing as hash values.
37
+ # This list can include `:call`s because they're always printed with parens
38
+ # around their arguments. For example:
39
+ #
40
+ # { :foo => (bar("baz")) } # The outer parens are unnecessary
41
+ # { :foo => bar("baz") } # This is the normal code style
42
+
43
+ HASH_VAL_NO_PAREN = [
44
+ :call,
45
+ :false,
46
+ :lit,
47
+ :lvar,
48
+ :nil,
49
+ :str,
50
+ :true
51
+ ]
52
+
53
+ HASH_SYNTAXES = [:ruby18, :ruby19]
54
+ RUBY_19_HASH_KEY = /\A[a-z][_a-zA-Z0-9]+\Z/
55
+
56
+ CONSTRUCTOR_OPTIONS = [:hash_syntax]
57
+
58
+ attr_reader :hash_syntax
59
+
60
+ ##
61
+ # Options:
62
+ #
63
+ # - `:hash_syntax` - either `:ruby18` or `:ruby19`
64
+
65
+ def initialize(option = {})
66
+ super()
67
+ check_option_keys(option)
68
+ @hash_syntax = extract_option(HASH_SYNTAXES, option[:hash_syntax], :ruby18)
69
+ @indent = " "
70
+ self.auto_shift_type = true
71
+ self.strict = true
72
+ self.expected = String
73
+
74
+ @calls = []
75
+
76
+ # self.debug[:defn] = /zsuper/
77
+ end
78
+
79
+ ############################################################
80
+ # Processors
81
+
82
+ def process_alias(exp) # :nodoc:
83
+ parenthesize "alias #{process(exp.shift)} #{process(exp.shift)}"
84
+ end
85
+
86
+ def process_and(exp) # :nodoc:
87
+ parenthesize "#{process exp.shift} and #{process exp.shift}"
88
+ end
89
+
90
+ def process_arglist(exp) # custom made node # :nodoc:
91
+ code = []
92
+ until exp.empty? do
93
+ arg = exp.shift
94
+ to_wrap = arg.first == :rescue
95
+ arg_code = process arg
96
+ code << (to_wrap ? "(#{arg_code})" : arg_code)
97
+ end
98
+ code.join ', '
99
+ end
100
+
101
+ def process_args(exp) # :nodoc:
102
+ args = []
103
+
104
+ until exp.empty? do
105
+ arg = exp.shift
106
+ case arg
107
+ when Symbol then
108
+ args << arg
109
+ when Sexp then
110
+ case arg.first
111
+ when :lasgn then
112
+ args << process(arg)
113
+ when :masgn then
114
+ args << process(arg)
115
+ when :kwarg then
116
+ _, k, v = arg
117
+ args << "#{k}: #{process v}"
118
+ else
119
+ raise "unknown arg type #{arg.first.inspect}"
120
+ end
121
+ else
122
+ raise "unknown arg type #{arg.inspect}"
123
+ end
124
+ end
125
+
126
+ "(#{args.join ', '})"
127
+ end
128
+
129
+ def process_array(exp) # :nodoc:
130
+ "[#{process_arglist(exp)}]"
131
+ end
132
+
133
+ def process_attrasgn(exp) # :nodoc:
134
+ receiver = process exp.shift
135
+ name = exp.shift
136
+ rhs = exp.pop
137
+ args = s(:array, *exp)
138
+ exp.clear
139
+
140
+ case name
141
+ when :[]= then
142
+ args = process args
143
+ "#{receiver}#{args} = #{process rhs}"
144
+ else
145
+ raise "dunno what to do: #{args.inspect}" unless args.size == 1 # s(:array)
146
+ name = name.to_s.sub(/=$/, '')
147
+ if rhs && rhs != s(:arglist) then
148
+ "#{receiver}.#{name} = #{process(rhs)}"
149
+ else
150
+ raise "dunno what to do: #{rhs.inspect}"
151
+ end
152
+ end
153
+ end
154
+
155
+ def process_back_ref(exp) # :nodoc:
156
+ "$#{exp.shift}"
157
+ end
158
+
159
+ # TODO: figure out how to do rescue and ensure ENTIRELY w/o begin
160
+ def process_begin(exp) # :nodoc:
161
+ code = []
162
+ code << "begin"
163
+ until exp.empty?
164
+ src = process(exp.shift)
165
+ src = indent(src) unless src =~ /(^|\n)(rescue|ensure)/ # ensure no level 0 rescues
166
+ code << src
167
+ end
168
+ code << "end"
169
+ return code.join("\n")
170
+ end
171
+
172
+ def process_block(exp) # :nodoc:
173
+ result = []
174
+
175
+ exp << nil if exp.empty?
176
+ until exp.empty? do
177
+ code = exp.shift
178
+ if code.nil? or code.first == :nil then
179
+ result << "# do nothing\n"
180
+ else
181
+ result << process(code)
182
+ end
183
+ end
184
+
185
+ result = parenthesize result.join "\n"
186
+ result += "\n" unless result.start_with? "("
187
+
188
+ return result
189
+ end
190
+
191
+ def process_block_pass exp # :nodoc:
192
+ raise "huh?: #{exp.inspect}" if exp.size > 1
193
+
194
+ "&#{process exp.shift}"
195
+ end
196
+
197
+ def process_break(exp) # :nodoc:
198
+ val = exp.empty? ? nil : process(exp.shift)
199
+ # HACK "break" + (val ? " #{val}" : "")
200
+ if val then
201
+ "break #{val}"
202
+ else
203
+ "break"
204
+ end
205
+ end
206
+
207
+ def process_call(exp) # :nodoc:
208
+ receiver_node_type = exp.first.nil? ? nil : exp.first.first
209
+ receiver = process exp.shift
210
+ receiver = "(#{receiver})" if ASSIGN_NODES.include? receiver_node_type
211
+
212
+ name = exp.shift
213
+ args = []
214
+
215
+ # this allows us to do both old and new sexp forms:
216
+ exp.push(*exp.pop[1..-1]) if exp.size == 1 && exp.first.first == :arglist
217
+
218
+ @calls.push name
219
+
220
+ in_context :arglist do
221
+ until exp.empty? do
222
+ arg_type = exp.first.sexp_type
223
+ is_empty_hash = (exp.first == s(:hash))
224
+ arg = process exp.shift
225
+
226
+ next if arg.empty?
227
+
228
+ strip_hash = (arg_type == :hash and
229
+ not BINARY.include? name and
230
+ not is_empty_hash and
231
+ (exp.empty? or exp.first.sexp_type == :splat))
232
+ wrap_arg = ASSIGN_NODES.include? arg_type
233
+
234
+ arg = arg[2..-3] if strip_hash
235
+ arg = "(#{arg})" if wrap_arg
236
+
237
+ args << arg
238
+ end
239
+ end
240
+
241
+ case name
242
+ when *BINARY then
243
+ "(#{receiver} #{name} #{args.join(', ')})"
244
+ when :[] then
245
+ receiver ||= "self"
246
+ "#{receiver}[#{args.join(', ')}]"
247
+ when :[]= then
248
+ receiver ||= "self"
249
+ rhs = args.pop
250
+ "#{receiver}[#{args.join(', ')}] = #{rhs}"
251
+ when :"!" then
252
+ "(not #{receiver})"
253
+ when :"-@" then
254
+ "-#{receiver}"
255
+ when :"+@" then
256
+ "+#{receiver}"
257
+ else
258
+ args = nil if args.empty?
259
+ args = "(#{args.join(', ')})" if args
260
+ receiver = "#{receiver}." if receiver
261
+
262
+ "#{receiver}#{name}#{args}"
263
+ end
264
+ ensure
265
+ @calls.pop
266
+ end
267
+
268
+ def process_case(exp) # :nodoc:
269
+ result = []
270
+ expr = process exp.shift
271
+ if expr then
272
+ result << "case #{expr}"
273
+ else
274
+ result << "case"
275
+ end
276
+ until exp.empty?
277
+ pt = exp.shift
278
+ if pt and pt.first == :when
279
+ result << "#{process(pt)}"
280
+ else
281
+ code = indent(process(pt))
282
+ code = indent("# do nothing") if code =~ /^\s*$/
283
+ result << "else\n#{code}"
284
+ end
285
+ end
286
+ result << "end"
287
+ result.join("\n")
288
+ end
289
+
290
+ def process_cdecl(exp) # :nodoc:
291
+ lhs = exp.shift
292
+ lhs = process lhs if Sexp === lhs
293
+ unless exp.empty? then
294
+ rhs = process(exp.shift)
295
+ "#{lhs} = #{rhs}"
296
+ else
297
+ lhs.to_s
298
+ end
299
+ end
300
+
301
+ def process_class(exp) # :nodoc:
302
+ "#{exp.comments}class #{util_module_or_class(exp, true)}"
303
+ end
304
+
305
+ def process_colon2(exp) # :nodoc:
306
+ "#{process(exp.shift)}::#{exp.shift}"
307
+ end
308
+
309
+ def process_colon3(exp) # :nodoc:
310
+ "::#{exp.shift}"
311
+ end
312
+
313
+ def process_const(exp) # :nodoc:
314
+ exp.shift.to_s
315
+ end
316
+
317
+ def process_cvar(exp) # :nodoc:
318
+ "#{exp.shift}"
319
+ end
320
+
321
+ def process_cvasgn(exp) # :nodoc:
322
+ "#{exp.shift} = #{process(exp.shift)}"
323
+ end
324
+
325
+ def process_cvdecl(exp) # :nodoc:
326
+ "#{exp.shift} = #{process(exp.shift)}"
327
+ end
328
+
329
+ def process_defined(exp) # :nodoc:
330
+ "defined? #{process(exp.shift)}"
331
+ end
332
+
333
+ def process_defn(exp) # :nodoc:
334
+ type1 = exp[1].first
335
+ type2 = exp[2].first rescue nil
336
+ expect = [:ivar, :iasgn, :attrset]
337
+
338
+ # s(name, args, ivar|iasgn|attrset)
339
+ if exp.size == 3 and type1 == :args and expect.include? type2 then
340
+ name = exp.first # don't shift in case we pass through
341
+ case type2
342
+ when :ivar then
343
+ ivar_name = exp.ivar.last
344
+
345
+ meth_name = ivar_name.to_s[1..-1].to_sym
346
+ expected = s(meth_name, s(:args), s(:ivar, ivar_name))
347
+
348
+ if exp == expected then
349
+ exp.clear
350
+ return "attr_reader #{name.inspect}"
351
+ end
352
+ when :attrset then
353
+ # TODO: deprecate? this is a PT relic
354
+ exp.clear
355
+ return "attr_writer :#{name.to_s[0..-2]}"
356
+ when :iasgn then
357
+ ivar_name = exp.iasgn[1]
358
+ meth_name = "#{ivar_name.to_s[1..-1]}=".to_sym
359
+ arg_name = exp.args.last
360
+ expected = s(meth_name, s(:args, arg_name),
361
+ s(:iasgn, ivar_name, s(:lvar, arg_name)))
362
+
363
+ if exp == expected then
364
+ exp.clear
365
+ return "attr_writer :#{name.to_s[0..-2]}"
366
+ end
367
+ else
368
+ raise "Unknown defn type: #{exp.inspect}"
369
+ end
370
+ end
371
+
372
+ comm = exp.comments
373
+ name = exp.shift
374
+ args = process exp.shift
375
+ args = "" if args == "()"
376
+
377
+ exp.shift if exp == s(s(:nil)) # empty it out of a default nil expression
378
+
379
+ # REFACTOR: use process_block but get it happier wrt parenthesize
380
+ body = []
381
+ until exp.empty? do
382
+ body << process(exp.shift)
383
+ end
384
+
385
+ body << "# do nothing" if body.empty?
386
+ body = body.join("\n")
387
+ body = body.lines.to_a[1..-2].join("\n") if
388
+ body =~ /^\Abegin/ && body =~ /^end\z/
389
+ body = indent(body) unless body =~ /(^|\n)rescue/
390
+
391
+ return "#{comm}def #{name}#{args}\n#{body}\nend".gsub(/\n\s*\n+/, "\n")
392
+ end
393
+
394
+ def process_defs(exp) # :nodoc:
395
+ lhs = exp.shift
396
+ var = [:self, :cvar, :dvar, :ivar, :gvar, :lvar].include? lhs.first
397
+ name = exp.shift
398
+
399
+ lhs = process(lhs)
400
+ lhs = "(#{lhs})" unless var
401
+
402
+ exp.unshift "#{lhs}.#{name}"
403
+ process_defn(exp)
404
+ end
405
+
406
+ def process_dot2(exp) # :nodoc:
407
+ "(#{process exp.shift}..#{process exp.shift})"
408
+ end
409
+
410
+ def process_dot3(exp) # :nodoc:
411
+ "(#{process exp.shift}...#{process exp.shift})"
412
+ end
413
+
414
+ def process_dregx(exp) # :nodoc:
415
+ options = re_opt exp.pop if Fixnum === exp.last
416
+ "/" << util_dthing(:dregx, exp) << "/#{options}"
417
+ end
418
+
419
+ def process_dregx_once(exp) # :nodoc:
420
+ process_dregx(exp) + "o"
421
+ end
422
+
423
+ def process_dstr(exp) # :nodoc:
424
+ "\"#{util_dthing(:dstr, exp)}\""
425
+ end
426
+
427
+ def process_dsym(exp) # :nodoc:
428
+ ":\"#{util_dthing(:dsym, exp)}\""
429
+ end
430
+
431
+ def process_dxstr(exp) # :nodoc:
432
+ "`#{util_dthing(:dxstr, exp)}`"
433
+ end
434
+
435
+ def process_ensure(exp) # :nodoc:
436
+ body = process exp.shift
437
+ ens = exp.shift
438
+ ens = nil if ens == s(:nil)
439
+ ens = process(ens) || "# do nothing"
440
+ ens = "begin\n#{ens}\nend\n" if ens =~ /(^|\n)rescue/
441
+
442
+ body.sub!(/\n\s*end\z/, '')
443
+ body = indent(body) unless body =~ /(^|\n)rescue/
444
+
445
+ return "#{body}\nensure\n#{indent ens}"
446
+ end
447
+
448
+ def process_evstr(exp) # :nodoc:
449
+ exp.empty? ? '' : process(exp.shift)
450
+ end
451
+
452
+ def process_false(exp) # :nodoc:
453
+ "false"
454
+ end
455
+
456
+ def process_flip2(exp) # :nodoc:
457
+ "#{process(exp.shift)}..#{process(exp.shift)}"
458
+ end
459
+
460
+ def process_flip3(exp) # :nodoc:
461
+ "#{process(exp.shift)}...#{process(exp.shift)}"
462
+ end
463
+
464
+ def process_for(exp) # :nodoc:
465
+ recv = process exp.shift
466
+ iter = process exp.shift
467
+ body = exp.empty? ? nil : process(exp.shift)
468
+
469
+ result = ["for #{iter} in #{recv} do"]
470
+ result << indent(body ? body : "# do nothing")
471
+ result << "end"
472
+
473
+ result.join("\n")
474
+ end
475
+
476
+ def process_gasgn(exp) # :nodoc:
477
+ process_iasgn(exp)
478
+ end
479
+
480
+ def process_gvar(exp) # :nodoc:
481
+ return exp.shift.to_s
482
+ end
483
+
484
+ def process_hash(exp) # :nodoc:
485
+ result = []
486
+
487
+ until exp.empty?
488
+ s = exp.shift
489
+ t = s.sexp_type
490
+ ruby19_key = ruby19_hash_key?(s)
491
+ lhs = process s
492
+
493
+ case t
494
+ when :kwsplat then
495
+ result << lhs
496
+ else
497
+ rhs = exp.shift
498
+ t = rhs.first
499
+ rhs = process rhs
500
+ rhs = "(#{rhs})" unless HASH_VAL_NO_PAREN.include? t
501
+
502
+ if hash_syntax == :ruby19 && ruby19_key
503
+ lhs.gsub!(/\A:/, "")
504
+ result << "#{lhs}: #{rhs}"
505
+ else
506
+ result << "#{lhs} => #{rhs}"
507
+ end
508
+ end
509
+ end
510
+
511
+ return result.empty? ? "{}" : "{ #{result.join(', ')} }"
512
+ end
513
+
514
+ def process_iasgn(exp) # :nodoc:
515
+ lhs = exp.shift
516
+ if exp.empty? then # part of an masgn
517
+ lhs.to_s
518
+ else
519
+ "#{lhs} = #{process exp.shift}"
520
+ end
521
+ end
522
+
523
+ def process_if(exp) # :nodoc:
524
+ expand = ASSIGN_NODES.include? exp.first.first
525
+ c = process exp.shift
526
+ t = process exp.shift
527
+ f = process exp.shift
528
+
529
+ c = "(#{c.chomp})" if c =~ /\n/
530
+
531
+ if t then
532
+ unless expand then
533
+ if f then
534
+ r = "#{c} ? (#{t}) : (#{f})"
535
+ r = nil if r =~ /return/ # HACK - need contextual awareness or something
536
+ else
537
+ r = "#{t} if #{c}"
538
+ end
539
+ return r if r and (@indent+r).size < LINE_LENGTH and r !~ /\n/
540
+ end
541
+
542
+ r = "if #{c} then\n#{indent(t)}\n"
543
+ r << "else\n#{indent(f)}\n" if f
544
+ r << "end"
545
+
546
+ r
547
+ elsif f
548
+ unless expand then
549
+ r = "#{f} unless #{c}"
550
+ return r if (@indent+r).size < LINE_LENGTH and r !~ /\n/
551
+ end
552
+ "unless #{c} then\n#{indent(f)}\nend"
553
+ else
554
+ # empty if statement, just do it in case of side effects from condition
555
+ "if #{c} then\n#{indent '# do nothing'}\nend"
556
+ end
557
+ end
558
+
559
+ def process_iter(exp) # :nodoc:
560
+ iter = process exp.shift
561
+ args = exp.shift
562
+ body = exp.empty? ? nil : process(exp.shift)
563
+
564
+ args = case args
565
+ when 0 then
566
+ " ||"
567
+ else
568
+ a = process(args)[1..-2]
569
+ a = " |#{a}|" unless a.empty?
570
+ a
571
+ end
572
+
573
+ b, e = if iter == "END" then
574
+ [ "{", "}" ]
575
+ else
576
+ [ "do", "end" ]
577
+ end
578
+
579
+ iter.sub!(/\(\)$/, '')
580
+
581
+ # REFACTOR: ugh
582
+ result = []
583
+ result << "#{iter} {"
584
+ result << args
585
+ if body then
586
+ result << " #{body.strip} "
587
+ else
588
+ result << ' '
589
+ end
590
+ result << "}"
591
+ result = result.join
592
+ return result if result !~ /\n/ and result.size < LINE_LENGTH
593
+
594
+ result = []
595
+ result << "#{iter} #{b}"
596
+ result << args
597
+ result << "\n"
598
+ if body then
599
+ result << indent(body.strip)
600
+ result << "\n"
601
+ end
602
+ result << e
603
+ result.join
604
+ end
605
+
606
+ def process_ivar(exp) # :nodoc:
607
+ exp.shift.to_s
608
+ end
609
+
610
+ def process_kwsplat(exp)
611
+ "**#{process exp.shift}"
612
+ end
613
+
614
+ def process_lasgn(exp) # :nodoc:
615
+ s = "#{exp.shift}"
616
+ s += " = #{process exp.shift}" unless exp.empty?
617
+ s
618
+ end
619
+
620
+ def process_lit(exp) # :nodoc:
621
+ obj = exp.shift
622
+ case obj
623
+ when Range then
624
+ "(#{obj.inspect})"
625
+ else
626
+ obj.inspect
627
+ end
628
+ end
629
+
630
+ def process_lvar(exp) # :nodoc:
631
+ exp.shift.to_s
632
+ end
633
+
634
+ def process_masgn(exp) # :nodoc:
635
+ # s(:masgn, s(:array, s(:lasgn, :var), ...), s(:to_ary, <val>, ...))
636
+ # s(:iter, <call>, s(:args, s(:masgn, :a, :b)), <body>)
637
+
638
+ case exp.first
639
+ when Sexp then
640
+ lhs = exp.shift
641
+ rhs = exp.empty? ? nil : exp.shift
642
+
643
+ case lhs.first
644
+ when :array then
645
+ lhs.shift # node type
646
+ lhs = lhs.map do |l|
647
+ case l.first
648
+ when :masgn then
649
+ "(#{process(l)})"
650
+ else
651
+ process(l)
652
+ end
653
+ end
654
+ else
655
+ raise "no clue: #{lhs.inspect}"
656
+ end
657
+
658
+ unless rhs.nil? then
659
+ t = rhs.first
660
+ rhs = process rhs
661
+ rhs = rhs[1..-2] if t == :array # FIX: bad? I dunno
662
+ return "#{lhs.join(", ")} = #{rhs}"
663
+ else
664
+ return lhs.join(", ")
665
+ end
666
+ when Symbol then # block arg list w/ masgn
667
+ result = exp.join ", "
668
+ exp.clear
669
+ "(#{result})"
670
+ else
671
+ raise "unknown masgn: #{exp.inspect}"
672
+ end
673
+ end
674
+
675
+ def process_match(exp) # :nodoc:
676
+ "#{process(exp.shift)}"
677
+ end
678
+
679
+ def process_match2(exp) # :nodoc:
680
+ lhs = process(exp.shift)
681
+ rhs = process(exp.shift)
682
+ "#{lhs} =~ #{rhs}"
683
+ end
684
+
685
+ def process_match3(exp) # :nodoc:
686
+ rhs = process(exp.shift)
687
+ left_type = exp.first.sexp_type
688
+ lhs = process(exp.shift)
689
+
690
+ if ASSIGN_NODES.include? left_type then
691
+ "(#{lhs}) =~ #{rhs}"
692
+ else
693
+ "#{lhs} =~ #{rhs}"
694
+ end
695
+ end
696
+
697
+ def process_module(exp) # :nodoc:
698
+ "#{exp.comments}module #{util_module_or_class(exp)}"
699
+ end
700
+
701
+ def process_next(exp) # :nodoc:
702
+ val = exp.empty? ? nil : process(exp.shift)
703
+ if val then
704
+ "next #{val}"
705
+ else
706
+ "next"
707
+ end
708
+ end
709
+
710
+ def process_nil(exp) # :nodoc:
711
+ "nil"
712
+ end
713
+
714
+ def process_not(exp) # :nodoc:
715
+ "(not #{process exp.shift})"
716
+ end
717
+
718
+ def process_nth_ref(exp) # :nodoc:
719
+ "$#{exp.shift}"
720
+ end
721
+
722
+ def process_op_asgn1(exp) # :nodoc:
723
+ # [[:lvar, :b], [:arglist, [:lit, 1]], :"||", [:lit, 10]]
724
+ lhs = process(exp.shift)
725
+ index = process(exp.shift)
726
+ msg = exp.shift
727
+ rhs = process(exp.shift)
728
+
729
+ "#{lhs}[#{index}] #{msg}= #{rhs}"
730
+ end
731
+
732
+ def process_op_asgn2(exp) # :nodoc:
733
+ # [[:lvar, :c], :var=, :"||", [:lit, 20]]
734
+ lhs = process(exp.shift)
735
+ index = exp.shift.to_s[0..-2]
736
+ msg = exp.shift
737
+
738
+ rhs = process(exp.shift)
739
+
740
+ "#{lhs}.#{index} #{msg}= #{rhs}"
741
+ end
742
+
743
+ def process_op_asgn_and(exp) # :nodoc:
744
+ # a &&= 1
745
+ # [[:lvar, :a], [:lasgn, :a, [:lit, 1]]]
746
+ exp.shift
747
+ process(exp.shift).sub(/\=/, '&&=')
748
+ end
749
+
750
+ def process_op_asgn_or(exp) # :nodoc:
751
+ # a ||= 1
752
+ # [[:lvar, :a], [:lasgn, :a, [:lit, 1]]]
753
+ exp.shift
754
+ process(exp.shift).sub(/\=/, '||=')
755
+ end
756
+
757
+ def process_or(exp) # :nodoc:
758
+ "(#{process exp.shift} or #{process exp.shift})"
759
+ end
760
+
761
+ def process_postexe(exp) # :nodoc:
762
+ "END"
763
+ end
764
+
765
+ def process_redo(exp) # :nodoc:
766
+ "redo"
767
+ end
768
+
769
+ def process_resbody exp # :nodoc:
770
+ args = exp.shift
771
+ body = finish(exp)
772
+ body << "# do nothing" if body.empty?
773
+
774
+ name = args.lasgn true
775
+ name ||= args.iasgn true
776
+ args = process(args)[1..-2]
777
+ args = " #{args}" unless args.empty?
778
+ args += " => #{name[1]}" if name
779
+
780
+ "rescue#{args}\n#{indent body.join("\n")}"
781
+ end
782
+
783
+ def process_rescue exp # :nodoc:
784
+ body = process(exp.shift) unless exp.first.first == :resbody
785
+ els = process(exp.pop) unless exp.last.first == :resbody
786
+
787
+ body ||= "# do nothing"
788
+ simple = exp.size == 1 && exp.resbody.size <= 3 &&
789
+ !exp.resbody.block &&
790
+ !exp.resbody.return
791
+
792
+ resbodies = []
793
+ until exp.empty? do
794
+ resbody = exp.shift
795
+ simple &&= resbody[1] == s(:array)
796
+ simple &&= resbody[2] != nil && resbody[2].node_type != :block
797
+ resbodies << process(resbody)
798
+ end
799
+
800
+ if els then
801
+ "#{indent body}\n#{resbodies.join("\n")}\nelse\n#{indent els}"
802
+ elsif simple then
803
+ resbody = resbodies.first.sub(/\n\s*/, ' ')
804
+ "#{body} #{resbody}"
805
+ else
806
+ "#{indent body}\n#{resbodies.join("\n")}"
807
+ end
808
+ end
809
+
810
+ def process_retry(exp) # :nodoc:
811
+ "retry"
812
+ end
813
+
814
+ def process_return(exp) # :nodoc:
815
+ # HACK return "return" + (exp.empty? ? "" : " #{process exp.shift}")
816
+
817
+ if exp.empty? then
818
+ return "return"
819
+ else
820
+ return "return #{process exp.shift}"
821
+ end
822
+ end
823
+
824
+ def process_sclass(exp) # :nodoc:
825
+ "class << #{process(exp.shift)}\n#{indent(process_block(exp))}\nend"
826
+ end
827
+
828
+ def process_self(exp) # :nodoc:
829
+ "self"
830
+ end
831
+
832
+ def process_splat(exp) # :nodoc:
833
+ if exp.empty? then
834
+ "*"
835
+ else
836
+ "*#{process(exp.shift)}"
837
+ end
838
+ end
839
+
840
+ def process_str(exp) # :nodoc:
841
+ return exp.shift.dump
842
+ end
843
+
844
+ def process_super(exp) # :nodoc:
845
+ args = finish exp
846
+
847
+ "super(#{args.join(', ')})"
848
+ end
849
+
850
+ def process_svalue(exp) # :nodoc:
851
+ code = []
852
+ until exp.empty? do
853
+ code << process(exp.shift)
854
+ end
855
+ code.join(", ")
856
+ end
857
+
858
+ def process_to_ary(exp) # :nodoc:
859
+ process(exp.shift)
860
+ end
861
+
862
+ def process_true(exp) # :nodoc:
863
+ "true"
864
+ end
865
+
866
+ def process_undef(exp) # :nodoc:
867
+ "undef #{process(exp.shift)}"
868
+ end
869
+
870
+ def process_until(exp) # :nodoc:
871
+ cond_loop(exp, 'until')
872
+ end
873
+
874
+ def process_valias(exp) # :nodoc:
875
+ "alias #{exp.shift} #{exp.shift}"
876
+ end
877
+
878
+ def process_when(exp) # :nodoc:
879
+ src = []
880
+
881
+ if self.context[1] == :array then # ugh. matz! why not an argscat?!?
882
+ val = process(exp.shift)
883
+ exp.shift # empty body
884
+ return "*#{val}"
885
+ end
886
+
887
+ until exp.empty?
888
+ cond = process(exp.shift).to_s[1..-2]
889
+ code = indent(finish(exp).join("\n"))
890
+ code = indent "# do nothing" if code =~ /\A\s*\Z/
891
+ src << "when #{cond} then\n#{code.chomp}"
892
+ end
893
+
894
+ src.join("\n")
895
+ end
896
+
897
+ def process_while(exp) # :nodoc:
898
+ cond_loop(exp, 'while')
899
+ end
900
+
901
+ def process_xstr(exp) # :nodoc:
902
+ "`#{process_str(exp)[1..-2]}`"
903
+ end
904
+
905
+ def process_yield(exp) # :nodoc:
906
+ args = []
907
+ until exp.empty? do
908
+ args << process(exp.shift)
909
+ end
910
+
911
+ unless args.empty? then
912
+ "yield(#{args.join(', ')})"
913
+ else
914
+ "yield"
915
+ end
916
+ end
917
+
918
+ def process_zsuper(exp) # :nodoc:
919
+ "super"
920
+ end
921
+
922
+ ############################################################
923
+ # Rewriters:
924
+
925
+ def rewrite_attrasgn exp # :nodoc:
926
+ if context.first(2) == [:array, :masgn] then
927
+ exp[0] = :call
928
+ exp[2] = exp[2].to_s.sub(/=$/, '').to_sym
929
+ end
930
+
931
+ exp
932
+ end
933
+
934
+ def rewrite_ensure exp # :nodoc:
935
+ exp = s(:begin, exp) unless context.first == :begin
936
+ exp
937
+ end
938
+
939
+ def rewrite_resbody exp # :nodoc:
940
+ raise "no exception list in #{exp.inspect}" unless exp.size > 2 && exp[1]
941
+ raise exp[1].inspect if exp[1][0] != :array
942
+ # for now, do nothing, just check and freak if we see an errant structure
943
+ exp
944
+ end
945
+
946
+ def rewrite_rescue exp # :nodoc:
947
+ complex = false
948
+ complex ||= exp.size > 3
949
+ complex ||= exp.resbody.block
950
+ complex ||= exp.resbody.size > 3
951
+ complex ||= exp.find_nodes(:resbody).any? { |n| n[1] != s(:array) }
952
+ complex ||= exp.find_nodes(:resbody).any? { |n| n.last.nil? }
953
+ complex ||= exp.find_nodes(:resbody).any? { |n| n[2] and n[2].node_type == :block }
954
+
955
+ handled = context.first == :ensure
956
+
957
+ exp = s(:begin, exp) if complex unless handled
958
+
959
+ exp
960
+ end
961
+
962
+ def rewrite_svalue(exp) # :nodoc:
963
+ case exp.last.first
964
+ when :array
965
+ s(:svalue, *exp[1][1..-1])
966
+ when :splat
967
+ exp
968
+ else
969
+ raise "huh: #{exp.inspect}"
970
+ end
971
+ end
972
+
973
+ ############################################################
974
+ # Utility Methods:
975
+
976
+ def check_option_keys(option)
977
+ diff = option.keys - CONSTRUCTOR_OPTIONS
978
+ unless diff.empty?
979
+ raise InvalidOption, "Invalid option(s): #{diff}"
980
+ end
981
+ end
982
+
983
+ ##
984
+ # Generate a post-or-pre conditional loop.
985
+
986
+ def cond_loop(exp, name)
987
+ cond = process(exp.shift)
988
+ body = process(exp.shift)
989
+ head_controlled = exp.shift
990
+
991
+ body = indent(body).chomp if body
992
+
993
+ code = []
994
+ if head_controlled then
995
+ code << "#{name} #{cond} do"
996
+ code << body if body
997
+ code << "end"
998
+ else
999
+ code << "begin"
1000
+ code << body if body
1001
+ code << "end #{name} #{cond}"
1002
+ end
1003
+ code.join("\n")
1004
+ end
1005
+
1006
+ ##
1007
+ # Utility method to escape something interpolated.
1008
+
1009
+ def dthing_escape type, lit
1010
+ lit = lit.gsub(/\n/, '\n')
1011
+ case type
1012
+ when :dregx then
1013
+ lit.gsub(/(\A|[^\\])\//, '\1\/')
1014
+ when :dstr, :dsym then
1015
+ lit.gsub(/"/, '\"')
1016
+ when :dxstr then
1017
+ lit.gsub(/`/, '\`')
1018
+ else
1019
+ raise "unsupported type #{type.inspect}"
1020
+ end
1021
+ end
1022
+
1023
+ ##
1024
+ # Check that `value` is in `array` of valid option values,
1025
+ # or raise InvalidOption. If `value` is nil, return `default`.
1026
+
1027
+ def extract_option(array, value, default)
1028
+ if value.nil?
1029
+ default
1030
+ elsif array.include?(value)
1031
+ value
1032
+ else
1033
+ raise InvalidOption, "Invalid option value: #{value}"
1034
+ end
1035
+ end
1036
+
1037
+ ##
1038
+ # Process all the remaining stuff in +exp+ and return the results
1039
+ # sans-nils.
1040
+
1041
+ def finish exp # REFACTOR: work this out of the rest of the processors
1042
+ body = []
1043
+ until exp.empty? do
1044
+ body << process(exp.shift)
1045
+ end
1046
+ body.compact
1047
+ end
1048
+
1049
+ ##
1050
+ # Given `exp` representing the left side of a hash pair, return true
1051
+ # if it is compatible with the ruby 1.9 hash syntax. For example,
1052
+ # the symbol `:foo` is compatible, but the literal `7` is not. Note
1053
+ # that strings are not considered "compatible". If we converted string
1054
+ # keys to symbol keys, we wouldn't be faithfully representing the input.
1055
+
1056
+ def ruby19_hash_key?(exp)
1057
+ exp.sexp_type == :lit && exp.length == 2 && RUBY_19_HASH_KEY === exp[1].to_s
1058
+ end
1059
+
1060
+ ##
1061
+ # Indent all lines of +s+ to the current indent level.
1062
+
1063
+ def indent(s)
1064
+ s.to_s.split(/\n/).map{|line| @indent + line}.join("\n")
1065
+ end
1066
+
1067
+ ##
1068
+ # Wrap appropriate expressions in matching parens.
1069
+
1070
+ def parenthesize exp
1071
+ case self.context[1]
1072
+ when nil, :defn, :defs, :class, :sclass, :if, :iter, :resbody, :when, :while then
1073
+ exp
1074
+ else
1075
+ "(#{exp})"
1076
+ end
1077
+ end
1078
+
1079
+ ##
1080
+ # Return the appropriate regexp flags for a given numeric code.
1081
+
1082
+ def re_opt options
1083
+ bits = (0..8).map { |n| options[n] * 2**n }
1084
+ bits.delete 0
1085
+ bits.map { |n| Regexp::CODES[n] }.join
1086
+ end
1087
+
1088
+ ##
1089
+ # Return a splatted symbol for +sym+.
1090
+
1091
+ def splat(sym)
1092
+ :"*#{sym}"
1093
+ end
1094
+
1095
+ ##
1096
+ # Utility method to generate something interpolated.
1097
+
1098
+ def util_dthing(type, exp)
1099
+ s = []
1100
+
1101
+ # first item in sexp is a string literal
1102
+ s << dthing_escape(type, exp.shift)
1103
+
1104
+ until exp.empty?
1105
+ pt = exp.shift
1106
+ case pt
1107
+ when Sexp then
1108
+ case pt.first
1109
+ when :str then
1110
+ s << dthing_escape(type, pt.last)
1111
+ when :evstr then
1112
+ s << '#{' << process(pt) << '}' # do not use interpolation here
1113
+ else
1114
+ raise "unknown type: #{pt.inspect}"
1115
+ end
1116
+ else
1117
+ raise "unhandled value in d-thing: #{pt.inspect}"
1118
+ end
1119
+ end
1120
+
1121
+ s.join
1122
+ end
1123
+
1124
+ ##
1125
+ # Utility method to generate ether a module or class.
1126
+
1127
+ def util_module_or_class(exp, is_class=false)
1128
+ result = []
1129
+
1130
+ name = exp.shift
1131
+ name = process name if Sexp === name
1132
+
1133
+ result << name
1134
+
1135
+ if is_class then
1136
+ superk = process(exp.shift)
1137
+ result << " < #{superk}" if superk
1138
+ end
1139
+
1140
+ result << "\n"
1141
+
1142
+ body = []
1143
+ begin
1144
+ code = process(exp.shift) unless exp.empty?
1145
+ body << code.chomp unless code.nil? or code.chomp.empty?
1146
+ end until exp.empty?
1147
+
1148
+ unless body.empty? then
1149
+ body = indent(body.join("\n\n")) + "\n"
1150
+ else
1151
+ body = ""
1152
+ end
1153
+ result << body
1154
+ result << "end"
1155
+
1156
+ result.join
1157
+ end
1158
+ end
1159
+ end