sexp2ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +24 -0
- data/README.md +32 -0
- data/Rakefile +92 -0
- data/lib/sexp2ruby/core_extensions/regexp.rb +26 -0
- data/lib/sexp2ruby/errors.rb +7 -0
- data/lib/sexp2ruby/processor.rb +1159 -0
- data/lib/sexp2ruby/version.rb +3 -0
- data/lib/sexp2ruby.rb +4 -0
- data/sexp2ruby.gemspec +32 -0
- data/spec/lib/processor_spec.rb +183 -0
- data/spec/spec_helper.rb +10 -0
- data/test/test_ruby2ruby.rb +496 -0
- metadata +137 -0
@@ -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
|