sexp2ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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