class Goling::Reduction

Attributes

args[RW]
from[RW]
inline[RW]
lang[RW]
line[RW]
location[RW]
named_args[RW]
reduction_id[RW]
regexp[RW]
returns[RW]
rule_args[RW]
sexp[RW]

Public Class Methods

new(params) click to toggle source
# File lib/goling/reduction.rb, line 24
def initialize params
  @returns  = params[:returns]
  @lang     = params[:lang]
  @inline   = params[:inline]
  @location = params[:location]
  @line     = params[:line]
  @regexp   = params[:regexp]
  @rule_args= @regexp.split(')').map{ |sub| sub.split('(')[1] }.select{ |v| v }
  @args     = params[:args]
  @sexp     = params[:sexp]
  @reduction_id = @@reductions.size
  determine_arguments
  @@reductions << self
end
parse(str) click to toggle source
# File lib/goling/reduction.rb, line 65
def self.parse str
  if /^{(?<return>[^:]*):(?<rid>[0-9]+)}$/ =~ str
    @@reductions[rid.to_i]
  elsif /^(?<return>[^:]*):(?<rid>[0-9]+)$/ =~ str
    @@reductions[rid.to_i]
  else
    raise "hell #{str}"
  end
end

Public Instance Methods

compile_with_return_to_var(return_variable, replace = {}) click to toggle source

Compile self

  • return_variable - The return variable. Can either be a symbol representing the variable name or nil to skip variable assigning.

  • replace - A list of variables in need of a new unique name or replacement with inlined code

# File lib/goling/reduction.rb, line 80
def compile_with_return_to_var(return_variable, replace = {})
  s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
  args = @named_args.keys
  # args[] now has the symbolized argument names of the code block

  args_code = []
  s.args.each_with_index do |arg,i|
    if /^{(?<ret>[^:]*):(?<n>[0-9]+)}$/ =~ arg
      # got a argument that referes to a reduction
      # pseudo allocate a return variable name and compile the reduction
      red = Reduction::parse(arg)
      if red.lang != lang && red.lang == :js && lang == :ruby
        # paste javascript code into a ruby variable
        code = red.compile_with_return_to_var(nil,replace)
        clone = Marshal.load(Marshal.dump(code)) # code is not cleanly duplicated
        code = Sexp.new(:iter,Sexp.new(:call, nil, :lambda, Sexp.new(:arglist)), nil,
          Sexp.new(:block,
            *clone
          )
        )
        code = Ruby2Js.new.process(code)
        code = [Sexp.new(:lasgn, args[i], Sexp.new(:lit, code))]
        args_code += code
      else
        raise "trying to reference #{red.lang} code in #{lang} code" if red.lang != lang
        if red.inline
          code = red.compile_with_return_to_var(nil,replace)
          replace[args[i]] = Replacement.new(:sexp => Sexp.new(:block,*code), :inline => true)
        else
          code = red.compile_with_return_to_var("#{ret}_#{n}".to_sym,replace)
          args_code += code
          replace[args[i]] = Replacement.new(:sexp => "#{ret}_#{n}".to_sym)
        end
      end
    elsif /^[0-9]+$/ =~ arg
      # got a number argument, stuff it in a integer variable
      args_code << Sexp.new(:lasgn, args[i], Sexp.new(:lit, arg.to_i))
    else
      raise "hell"
    end
  end

  if return_variable
    if s.sexp[3][0] == :block
      code = Sexp.new(:lasgn, return_variable,
        Sexp.new(:block,
          *(s.sexp[3][1..-1].map{ |s| s.dup })
        )
      )
    else
      code = Sexp.new(:lasgn, return_variable, s.sexp[3].dup)
    end
  else
    code = s.sexp[3].dup
  end

  replace.each do |k,v|
    replace_variable_references(code,v,k)
  end

  return *args_code + [code]
end
determine_arguments() click to toggle source
# File lib/goling/reduction.rb, line 39
def determine_arguments
  s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
  raise "what is this?" unless s.sexp[0] == :iter && s.sexp[1][0] == :call && s.sexp[1][1] == nil && s.sexp[1][2] == :proc && s.sexp[1][3][0] == :arglist

  block_args = s.sexp[2]
  if block_args
    if block_args[0]==:lasgn
      # single argument
      args = [block_args[1]]
    elsif block_args[0]==:masgn
      # multiple arguments
      args = block_args[1]
      raise "unsupported argument type #{args}" unless args[0]==:array
      args = args[1..-1].map{ |arg|
        raise "unsupported argument type #{arg}" unless arg[0]==:lasgn
        arg[1]
      }
    else
      raise "unsupported argument type #{args}"
    end
  end
  # args[] now has the symbolized argument names of the code block
  @named_args = Hash[*args.zip(@args).flatten] if args
  @named_args ||= {}
end
replace_variable_references(code,replacement,needle) click to toggle source

Recurcively replace all references in a code section

  • code - The code haystack to search and replace in

  • replacement - The replacement code. Either a Sexp (containing code to inline) or a symbol

  • needle - The search needle

# File lib/goling/reduction.rb, line 149
def replace_variable_references(code,replacement,needle)

  #inline = replacement.kind_of?(Sexp)

  case code[0]
  when :lasgn
    code[1]=replacement.sexp if code[1] == needle
  when :lvar
    if code[1] == needle
      unless replacement.inline?
        code[1]=replacement.sexp
      end
    end
  when :call
    code[2]=replacement.sexp if code[2] == needle
  when :lvar
    code[1]=replacement.sexp if code[1] == needle
  end
  # inlining requires complex code:
  if replacement.inline? && [:iter, :block].include?(code[0])
    # we have a inline and a block, replace any references with the sexp
    code[1..-1].each_with_index do |h,i|
      if h && h.kind_of?(Sexp) && h == Sexp.new(:lvar, needle)
        # inline references like s(:lvar, :needle)
        # indicates a call to the needle, thus code wants to inline
        h[0] = replacement.sexp[0]
        h[1] = replacement.sexp[1]
      elsif h && h.kind_of?(Sexp) && @named_args.has_key?(needle) &&
          Reduction.parse(@named_args[needle]).named_args.select{ |k,v|
            h == Sexp.new(:call, Sexp.new(:lvar, needle), :[], Sexp.new(:arglist, Sexp.new(:lit, k)))
          }.size == 1
        # code is asking for a injection of one of the argument's with:
        #  s(:call, s(:lvar, :needle), :[], s(:arglist, s(:lit, :argumen)))
        # which in ruby looks like:
        #  needle[:argument]
        # which again is the way we support calling arguments of the neede
        arg = h[3][1][1]
        sexy = Marshal.load(Marshal.dump(Reduction.parse(Reduction.parse(@named_args[needle]).named_args[arg]).sexp)) # sexp handling is not clean cut
        code[i+1] = sexy[3]
      else
        replace_variable_references(h,replacement,needle) if h && h.kind_of?(Sexp)
      end
    end
  else
    code[1..-1].each do |h|
      replace_variable_references(h,replacement,needle) if h && h.kind_of?(Sexp)
    end
  end
end
to_rexp() click to toggle source
# File lib/goling/reduction.rb, line 199
def to_rexp
  raise "hell" if returns.kind_of?(Array)
  "{#{returns}:#{reduction_id}}"
end