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.
- 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
|