Class: Sexp2Ruby::Processor

Inherits:
SexpProcessor
  • Object
show all
Defined in:
lib/sexp2ruby/processor.rb

Overview

Generate ruby code from a sexp.

Constant Summary collapse

LF =
"\n"
ASSIGN_NODES =

Nodes that represent assignment and probably need () around them.

TODO: this should be replaced with full precedence support :/

[
 :dasgn,
 :flip2,
 :flip3,
 :lasgn,
 :masgn,
 :attrasgn,
 :op_asgn1,
 :op_asgn2,
 :op_asgn_and,
 :op_asgn_or,
 :return,
 :if, # HACK
 :rescue,
]
HASH_SYNTAXES =
[:ruby18, :ruby19]
RUBY_19_HASH_KEY =
/\A[a-z][_a-zA-Z0-9]+\Z/
CONSTRUCTOR_OPTIONS =
[
  :hash_syntax,
  :no_paren_methods
]
NODES =
[
  :alias,
  :and,
  :arglist,
  :args,
  :array,
  :attrasgn,
  :back_ref,
  :begin,
  :block,
  :block_pass,
  :break,
  :call,
  :case,
  :cdecl,
  :class,
  :colon2,
  :colon3,
  :const,
  :cvar,
  :cvasgn,
  :cvdecl,
  :defined,
  :defn,
  :defs,
  :dot2,
  :dot3,
  :dregx,
  :dregx_once,
  :dstr,
  :dsym,
  :dxstr,
  :ensure,
  :evstr,
  :false,
  :flip2,
  :flip3,
  :for,
  :gasgn,
  :gvar,
  :hash,
  :iasgn,
  :if,
  :iter,
  :ivar,
  :kwsplat,
  :lasgn,
  :lit,
  :lvar,
  :masgn,
  :match,
  :match2,
  :match3,
  :module,
  :next,
  :nil,
  :not,
  :nth_ref,
  :op_asgn1,
  :op_asgn2,
  :op_asgn_and,
  :op_asgn_or,
  :or,
  :postexe,
  :redo,
  :resbody,
  :rescue,
  :retry,
  :return,
  :sclass,
  :self,
  :splat,
  :str,
  :super,
  :svalue,
  :to_ary,
  :true,
  :undef,
  :until,
  :valias,
  :when,
  :while,
  :xstr,
  :yield,
  :zsuper
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(option = {}) ⇒ Processor

Options:

  • :hash_syntax - either :ruby18 or :ruby19. Default is :ruby19.

  • :no_paren_methods - an array of symbols, these methods will omit argument parentheses. Default is [].



132
133
134
135
136
137
138
139
140
141
142
# File 'lib/sexp2ruby/processor.rb', line 132

def initialize(option = {})
  super()
  check_option_keys(option)
  @hash_syntax = extract_option(HASH_SYNTAXES, option[:hash_syntax], :ruby19)
  @no_paren_methods = option[:no_paren_methods] || []
  @indent_lvl = "  "
  self.auto_shift_type = true
  self.strict = true
  self.expected = String
  @calls = []
end

Instance Attribute Details

#hash_syntaxObject (readonly)

Returns the value of attribute hash_syntax.



124
125
126
# File 'lib/sexp2ruby/processor.rb', line 124

def hash_syntax
  @hash_syntax
end

#indent_lvlObject (readonly)

Returns the value of attribute indent_lvl.



124
125
126
# File 'lib/sexp2ruby/processor.rb', line 124

def indent_lvl
  @indent_lvl
end

#no_paren_methodsObject (readonly)

Returns the value of attribute no_paren_methods.



124
125
126
# File 'lib/sexp2ruby/processor.rb', line 124

def no_paren_methods
  @no_paren_methods
end

Instance Method Details

#call_popObject

State




162
163
164
# File 'lib/sexp2ruby/processor.rb', line 162

def call_pop
  @calls.pop
end

#call_push(name) ⇒ Object



166
167
168
# File 'lib/sexp2ruby/processor.rb', line 166

def call_push(name)
  @calls.push(name)
end

#check_option_keys(option) ⇒ Object

Utility Methods




224
225
226
227
228
229
# File 'lib/sexp2ruby/processor.rb', line 224

def check_option_keys(option)
  diff = option.keys - CONSTRUCTOR_OPTIONS
  unless diff.empty?
    raise InvalidOption, "Invalid option(s): #{diff}"
  end
end

#cond_loop(exp, name) ⇒ Object

Generate a post-or-pre conditional loop.



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/sexp2ruby/processor.rb', line 232

def cond_loop(exp, name)
  cond = process(exp.shift)
  body = process(exp.shift)
  head_controlled = exp.shift

  body = indent(body).chomp if body

  code = []
  if head_controlled
    code << "#{name} #{cond} do"
    code << body if body
    code << "end"
  else
    code << "begin"
    code << body if body
    code << "end #{name} #{cond}"
  end
  code.join(LF)
end

#dthing_escape(type, lit) ⇒ Object

Escape something interpolated.



253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/sexp2ruby/processor.rb', line 253

def dthing_escape type, lit
  lit = lit.gsub(/\n/, '\n')
  case type
  when :dregx then
    lit.gsub(/(\A|[^\\])\//, '\1\/')
  when :dstr, :dsym then
    lit.gsub(/"/, '\"')
  when :dxstr then
    lit.gsub(/`/, '\`')
  else
    raise "unsupported type #{type.inspect}"
  end
end

#extract_option(array, value, default) ⇒ Object

Check that value is in array of valid option values, or raise InvalidOption. If value is nil, return default.



269
270
271
272
273
274
275
276
277
# File 'lib/sexp2ruby/processor.rb', line 269

def extract_option(array, value, default)
  if value.nil?
    default
  elsif array.include?(value)
    value
  else
    raise InvalidOption, "Invalid option value: #{value}"
  end
end

#finish(exp) ⇒ Object

Process all the remaining stuff in exp and return the results sans-nils.



281
282
283
284
285
286
287
# File 'lib/sexp2ruby/processor.rb', line 281

def finish exp # REFACTOR: work this out of the rest of the processors
  body = []
  until exp.empty? do
    body << process(exp.shift)
  end
  body.compact
end

#indent(s) ⇒ Object

Indent all lines of s to the current indent level.



299
300
301
# File 'lib/sexp2ruby/processor.rb', line 299

def indent(s)
  s.to_s.split(/\n/).map{|line| @indent_lvl + line}.join(LF)
end

#parenthesize(exp) ⇒ Object

Wrap appropriate expressions in matching parens.



304
305
306
307
308
309
310
311
# File 'lib/sexp2ruby/processor.rb', line 304

def parenthesize exp
  case self.context[1]
  when nil, :defn, :defs, :class, :sclass, :if, :iter, :resbody, :when, :while then
    exp
  else
    "(#{exp})"
  end
end

#rewrite_attrasgn(exp) ⇒ Object

Rewriters




173
174
175
176
177
178
179
180
# File 'lib/sexp2ruby/processor.rb', line 173

def rewrite_attrasgn exp
  if context.first(2) == [:array, :masgn]
    exp[0] = :call
    exp[2] = exp[2].to_s.sub(/=$/, '').to_sym
  end

  exp
end

#rewrite_ensure(exp) ⇒ Object



182
183
184
185
# File 'lib/sexp2ruby/processor.rb', line 182

def rewrite_ensure exp
  exp = s(:begin, exp) unless context.first == :begin
  exp
end

#rewrite_resbody(exp) ⇒ Object



187
188
189
190
191
192
# File 'lib/sexp2ruby/processor.rb', line 187

def rewrite_resbody exp
  raise "no exception list in #{exp.inspect}" unless exp.size > 2 && exp[1]
  raise exp[1].inspect if exp[1][0] != :array
  # for now, do nothing, just check and freak if we see an errant structure
  exp
end

#rewrite_rescue(exp) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/sexp2ruby/processor.rb', line 194

def rewrite_rescue exp
  complex = false
  complex ||= exp.size > 3
  complex ||= exp.resbody.block
  complex ||= exp.resbody.size > 3
  complex ||= exp.find_nodes(:resbody).any? { |n| n[1] != s(:array) }
  complex ||= exp.find_nodes(:resbody).any? { |n| n.last.nil? }
  complex ||= exp.find_nodes(:resbody).any? { |n| n[2] and n[2].node_type == :block }

  handled = context.first == :ensure

  exp = s(:begin, exp) if complex unless handled

  exp
end

#rewrite_svalue(exp) ⇒ Object



210
211
212
213
214
215
216
217
218
219
# File 'lib/sexp2ruby/processor.rb', line 210

def rewrite_svalue(exp)
  case exp.last.first
  when :array
    s(:svalue, *exp[1][1..-1])
  when :splat
    exp
  else
    raise "huh: #{exp.inspect}"
  end
end

#ruby19_hash_key?(exp) ⇒ Boolean

Given exp representing the left side of a hash pair, return true if it is compatible with the ruby 1.9 hash syntax. For example, the symbol :foo is compatible, but the literal 7 is not. Note that strings are not considered “compatible”. If we converted string keys to symbol keys, we wouldn’t be faithfully representing the input.

Returns:

  • (Boolean)


294
295
296
# File 'lib/sexp2ruby/processor.rb', line 294

def ruby19_hash_key?(exp)
  exp.sexp_type == :lit && exp.length == 2 && RUBY_19_HASH_KEY === exp[1].to_s
end

#splat(sym) ⇒ Object

Return a splatted symbol for sym.



314
315
316
# File 'lib/sexp2ruby/processor.rb', line 314

def splat(sym)
  :"*#{sym}"
end

#util_dthing(type, exp) ⇒ Object

Generate something interpolated.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/sexp2ruby/processor.rb', line 319

def util_dthing(type, exp)
  s = []

  # first item in sexp is a string literal
  s << dthing_escape(type, exp.shift)

  until exp.empty?
    pt = exp.shift
    case pt
    when Sexp then
      case pt.first
      when :str then
        s << dthing_escape(type, pt.last)
      when :evstr then
        s << '#{' << process(pt) << '}' # do not use interpolation here
      else
        raise "unknown type: #{pt.inspect}"
      end
    else
      raise "unhandled value in d-thing: #{pt.inspect}"
    end
  end

  s.join
end

#util_module_or_class(exp, is_class = false) ⇒ Object

Utility method to generate ether a module or class.



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/sexp2ruby/processor.rb', line 346

def util_module_or_class(exp, is_class=false)
  result = []

  name = exp.shift
  name = process name if Sexp === name

  result << name

  if is_class
    superk = process(exp.shift)
    result << " < #{superk}" if superk
  end

  result << LF

  body = []
  begin
    code = process(exp.shift) unless exp.empty?
    body << code.chomp unless code.nil? or code.chomp.empty?
  end until exp.empty?

  body = body.empty? ? "" : indent(body.join("\n\n")) + LF
  result << body
  result << "end"

  result.join
end