Class: Goling::Reduction

Inherits:
Object
  • Object
show all
Defined in:
lib/goling/reduction.rb

Constant Summary collapse

@@reductions =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ Reduction

Returns a new instance of Reduction.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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

Instance Attribute Details

#argsObject

Returns the value of attribute args.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def args
  @args
end

#fromObject

Returns the value of attribute from.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def from
  @from
end

#inlineObject

Returns the value of attribute inline.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def inline
  @inline
end

#langObject

Returns the value of attribute lang.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def lang
  @lang
end

#lineObject

Returns the value of attribute line.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def line
  @line
end

#locationObject

Returns the value of attribute location.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def location
  @location
end

#named_argsObject

Returns the value of attribute named_args.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def named_args
  @named_args
end

#reduction_idObject

Returns the value of attribute reduction_id.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def reduction_id
  @reduction_id
end

#regexpObject

Returns the value of attribute regexp.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def regexp
  @regexp
end

#returnsObject

Returns the value of attribute returns.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def returns
  @returns
end

#rule_argsObject

Returns the value of attribute rule_args.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def rule_args
  @rule_args
end

#sexpObject

Returns the value of attribute sexp.



20
21
22
# File 'lib/goling/reduction.rb', line 20

def sexp
  @sexp
end

Class Method Details

.parse(str) ⇒ Object



65
66
67
68
69
70
71
72
73
# 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

Instance Method Details

#compile_with_return_to_var(return_variable, replace = {}) ⇒ Object

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



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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_argumentsObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 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) ⇒ Object

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



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# 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_rexpObject



199
200
201
202
# File 'lib/goling/reduction.rb', line 199

def to_rexp
  raise "hell" if returns.kind_of?(Array)
  "{#{returns}:#{reduction_id}}"
end