class Sexp2Ruby::Processor
Generate ruby code from a sexp.
Constants
- ASSIGN_NODES
Nodes that represent assignment and probably need () around them.
TODO: this should be replaced with full precedence support :/
- CONSTRUCTOR_OPTIONS
- HASH_SYNTAXES
- LF
- NODES
- RUBY_19_HASH_KEY
Attributes
Public Class Methods
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 `[]`.
# 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
Public Instance Methods
State
# File lib/sexp2ruby/processor.rb, line 162 def call_pop @calls.pop end
# File lib/sexp2ruby/processor.rb, line 166 def call_push(name) @calls.push(name) end
Utility Methods
# 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
Generate a post-or-pre conditional loop.
# 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
Escape something interpolated.
# 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
Check that `value` is in `array` of valid option values, or raise InvalidOption
. If `value` is nil, return `default`.
# 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
Process all the remaining stuff in exp
and return the results sans-nils.
# 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 all lines of s
to the current indent level.
# File lib/sexp2ruby/processor.rb, line 299 def indent(s) s.to_s.split(/\n/).map{|line| @indent_lvl + line}.join(LF) end
Wrap appropriate expressions in matching parens.
# 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
Rewriters
# 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
# File lib/sexp2ruby/processor.rb, line 182 def rewrite_ensure exp exp = s(:begin, exp) unless context.first == :begin exp end
# 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
# 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
# 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
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.
# 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
Return a splatted symbol for sym
.
# File lib/sexp2ruby/processor.rb, line 314 def splat(sym) :"*#{sym}" end
Generate something interpolated.
# 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
Utility method to generate ether a module or class.
# 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