ruby2c 1.0.0.6
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.
- data.tar.gz.sig +1 -0
- data/.autotest +21 -0
- data/History.txt +173 -0
- data/Manifest.txt +35 -0
- data/README.txt +76 -0
- data/Rakefile +21 -0
- data/demo/char.rb +13 -0
- data/demo/factorial.rb +11 -0
- data/demo/hello.rb +11 -0
- data/demo/misc.rb +25 -0
- data/demo/newarray.rb +11 -0
- data/demo/strcat.rb +12 -0
- data/lib/crewriter.rb +199 -0
- data/lib/function_table.rb +45 -0
- data/lib/function_type.rb +46 -0
- data/lib/handle.rb +14 -0
- data/lib/r2cenvironment.rb +59 -0
- data/lib/rewriter.rb +35 -0
- data/lib/ruby_to_ansi_c.rb +673 -0
- data/lib/ruby_to_ruby_c.rb +382 -0
- data/lib/type.rb +148 -0
- data/lib/type_checker.rb +920 -0
- data/lib/typed_sexp.rb +88 -0
- data/test/r2ctestcase.rb +1196 -0
- data/test/test_crewriter.rb +328 -0
- data/test/test_extras.rb +68 -0
- data/test/test_function_table.rb +90 -0
- data/test/test_function_type.rb +125 -0
- data/test/test_handle.rb +39 -0
- data/test/test_r2cenvironment.rb +191 -0
- data/test/test_rewriter.rb +16 -0
- data/test/test_ruby_to_ansi_c.rb +487 -0
- data/test/test_ruby_to_ruby_c.rb +161 -0
- data/test/test_type.rb +193 -0
- data/test/test_type_checker.rb +805 -0
- data/test/test_typed_sexp.rb +138 -0
- metadata +164 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
class FunctionTable
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@functions = Hash.new do |h,k|
|
5
|
+
h[k] = []
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def cheat(name) # HACK: just here for debugging
|
10
|
+
puts "\n# WARNING: FunctionTable.cheat called from #{caller[0]}" if $DEBUG
|
11
|
+
@functions[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name) # HACK: just here for transition
|
15
|
+
puts "\n# WARNING: FunctionTable.[] called from #{caller[0]}" if $DEBUG
|
16
|
+
@functions[name].first
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_key?(name) # HACK: just here for transition
|
20
|
+
puts "\n# WARNING: FunctionTable.has_key? called from #{caller[0]}" if $DEBUG
|
21
|
+
@functions.has_key?(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_function(name, type)
|
25
|
+
@functions[name] << type
|
26
|
+
type
|
27
|
+
end
|
28
|
+
|
29
|
+
def unify(name, type)
|
30
|
+
success = false
|
31
|
+
@functions[name].each do |o| # unify(type)
|
32
|
+
begin
|
33
|
+
o.unify type
|
34
|
+
success = true
|
35
|
+
rescue
|
36
|
+
# ignore
|
37
|
+
end
|
38
|
+
end
|
39
|
+
unless success then
|
40
|
+
yield(name, type) if block_given?
|
41
|
+
end
|
42
|
+
type
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
class FunctionType
|
3
|
+
|
4
|
+
attr_accessor :receiver_type
|
5
|
+
attr_accessor :formal_types
|
6
|
+
attr_accessor :return_type
|
7
|
+
|
8
|
+
def initialize(receiver_type, formal_types, return_type)
|
9
|
+
raise "nil not allowed" if formal_types.nil? or return_type.nil?
|
10
|
+
@receiver_type = receiver_type
|
11
|
+
@formal_types = formal_types
|
12
|
+
@return_type = return_type
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
return nil unless other.class == self.class
|
17
|
+
|
18
|
+
return false unless other.receiver_type == self.receiver_type
|
19
|
+
return false unless other.return_type == self.return_type
|
20
|
+
return false unless other.formal_types == self.formal_types
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
def unify_components(other)
|
25
|
+
raise TypeError, "Unable to unify: different number of args #{self.inspect} vs #{other.inspect}" unless
|
26
|
+
@formal_types.length == other.formal_types.length
|
27
|
+
|
28
|
+
@formal_types.each_with_index do |t, i|
|
29
|
+
t.unify other.formal_types[i]
|
30
|
+
end
|
31
|
+
|
32
|
+
@receiver_type.unify other.receiver_type
|
33
|
+
@return_type.unify other.return_type
|
34
|
+
# rescue RuntimeError # print more complete warning message
|
35
|
+
# raise "Unable to unify\n#{self}\nwith\n#{other}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
formals = formal_types.map do |t|
|
40
|
+
t.inspect
|
41
|
+
end
|
42
|
+
|
43
|
+
"function(#{receiver_type.inspect}, [#{formals.join ', '}], #{return_type.inspect})"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/handle.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'ruby_parser_extras' # TODO: split out to environment.rb?
|
2
|
+
|
3
|
+
class R2CEnvironment < Environment
|
4
|
+
|
5
|
+
TYPE = 0
|
6
|
+
VALUE = 1
|
7
|
+
|
8
|
+
attr_reader :env
|
9
|
+
|
10
|
+
def add(id, type, depth = 0)
|
11
|
+
raise "Adding illegal identifier #{id.inspect}" unless
|
12
|
+
Symbol === id
|
13
|
+
raise ArgumentError, "type must be a valid Type instance" unless
|
14
|
+
Type === type
|
15
|
+
|
16
|
+
@env[depth][id.to_s.sub(/^\*/, '').intern][TYPE] = type
|
17
|
+
end
|
18
|
+
|
19
|
+
def depth
|
20
|
+
@env.length
|
21
|
+
end
|
22
|
+
|
23
|
+
alias :old_extend :extend
|
24
|
+
def extend # override
|
25
|
+
@env.unshift(Hash.new { |h,k| h[k] = [] })
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_val(name)
|
29
|
+
self._get(name)[VALUE]
|
30
|
+
end
|
31
|
+
|
32
|
+
def lookup(name)
|
33
|
+
# HACK: if name is :self, cheat for now until we have full defn remapping
|
34
|
+
return Type.fucked if name == :self
|
35
|
+
|
36
|
+
return self._get(name)[TYPE]
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_val(name, val)
|
40
|
+
self._get(name)[VALUE] = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def scope
|
44
|
+
self.extend
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
self.unextend
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def _get(name)
|
53
|
+
@env.each do |closure|
|
54
|
+
return closure[name] if closure.has_key? name
|
55
|
+
end
|
56
|
+
|
57
|
+
raise NameError, "Unbound var: #{name.inspect} in #{@env.inspect}"
|
58
|
+
end
|
59
|
+
end
|
data/lib/rewriter.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
begin require 'rubygems'; rescue LoadError; end
|
3
|
+
require 'sexp'
|
4
|
+
require 'sexp_processor'
|
5
|
+
require 'unique'
|
6
|
+
require 'unified_ruby'
|
7
|
+
|
8
|
+
class Sexp
|
9
|
+
# add arglist because we introduce the new array type in this file
|
10
|
+
@@array_types << :arglist
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Rewriter (probably should be renamed) is a first-pass filter that
|
15
|
+
# normalizes some of ruby's ASTs to make them more processable later
|
16
|
+
# in the pipeline. It only has processors for what it is interested
|
17
|
+
# in, so real the individual methods for a better understanding of
|
18
|
+
# what it does.
|
19
|
+
|
20
|
+
class Rewriter < SexpProcessor
|
21
|
+
def rewrite_defn(exp)
|
22
|
+
case exp.last[0]
|
23
|
+
when :ivar then
|
24
|
+
ivar = exp.pop
|
25
|
+
exp.push s(:scope, s(:block, s(:return, ivar)))
|
26
|
+
when :attrset then
|
27
|
+
var = exp.pop
|
28
|
+
exp.push s(:scope,
|
29
|
+
s(:block,
|
30
|
+
s(:return, s(:iasgn, var.last, s(:lvar, :arg)))))
|
31
|
+
end
|
32
|
+
exp
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,673 @@
|
|
1
|
+
|
2
|
+
$TESTING = false unless defined? $TESTING
|
3
|
+
|
4
|
+
require 'pp'
|
5
|
+
begin require 'rubygems'; rescue LoadError; end
|
6
|
+
require 'ruby_parser'
|
7
|
+
require 'sexp_processor'
|
8
|
+
require 'composite_sexp_processor'
|
9
|
+
|
10
|
+
require 'type_checker'
|
11
|
+
require 'rewriter'
|
12
|
+
require 'crewriter'
|
13
|
+
require 'r2cenvironment'
|
14
|
+
|
15
|
+
##
|
16
|
+
# The whole point of this project! RubyToC is an actually very simple
|
17
|
+
# SexpProcessor that does the final conversion from Sexp to C code.
|
18
|
+
# This class has more unsupported nodes than any other (on
|
19
|
+
# purpose--we'd like TypeChecker and friends to be as generally useful
|
20
|
+
# as possible), and as a result, supports a very small subset of ruby.
|
21
|
+
|
22
|
+
class RubyToAnsiC < SexpProcessor
|
23
|
+
|
24
|
+
VERSION = '1.0.0.6' # HACK version should be 1.0.0.beta.6, but rubygems sucks
|
25
|
+
|
26
|
+
# TODO: remove me
|
27
|
+
def no(exp) # :nodoc:
|
28
|
+
raise "no: #{caller[0].split[1]} #{exp.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns a textual version of a C type that corresponds to a sexp
|
33
|
+
# type.
|
34
|
+
|
35
|
+
def self.c_type(typ)
|
36
|
+
base_type =
|
37
|
+
case typ.type.contents # HACK this is breaking demeter
|
38
|
+
when :float then
|
39
|
+
"double"
|
40
|
+
when :long then
|
41
|
+
"long"
|
42
|
+
when :str then
|
43
|
+
"str"
|
44
|
+
when :symbol then
|
45
|
+
"symbol"
|
46
|
+
when :bool then # TODO: subject to change
|
47
|
+
"bool"
|
48
|
+
when :void then
|
49
|
+
"void"
|
50
|
+
when :homo then
|
51
|
+
"void *" # HACK
|
52
|
+
when :value, :unknown then
|
53
|
+
"void *" # HACK
|
54
|
+
# HACK: uncomment this and fix the above when you want to have good tests
|
55
|
+
# when :unknown then
|
56
|
+
# raise "You should not have unknown types by now!"
|
57
|
+
else
|
58
|
+
raise "Bug! Unknown type #{typ.inspect} in c_type"
|
59
|
+
end
|
60
|
+
|
61
|
+
base_type += " *" if typ.list? unless typ.unknown?
|
62
|
+
|
63
|
+
base_type
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Provides a place to put things at the file scope.
|
68
|
+
# Be smart, make them static (hence the name).
|
69
|
+
|
70
|
+
attr_reader :statics
|
71
|
+
|
72
|
+
##
|
73
|
+
# Provides access to the variable scope.
|
74
|
+
|
75
|
+
attr_reader :env
|
76
|
+
|
77
|
+
##
|
78
|
+
# Provides access to the method signature prototypes that are needed
|
79
|
+
# at the top of the C file.
|
80
|
+
|
81
|
+
attr_reader :prototypes
|
82
|
+
|
83
|
+
##
|
84
|
+
# Provides a (rather bogus) preamble. Put your includes and defines
|
85
|
+
# here. It really should be made to be much more clean and
|
86
|
+
# extendable.
|
87
|
+
|
88
|
+
def preamble
|
89
|
+
"// BEGIN METARUBY PREAMBLE
|
90
|
+
#include <ruby.h>
|
91
|
+
#define RB_COMPARE(x, y) (x) == (y) ? 0 : (x) < (y) ? -1 : 1
|
92
|
+
typedef char * str;
|
93
|
+
#define case_equal_long(x, y) ((x) == (y))
|
94
|
+
// END METARUBY PREAMBLE
|
95
|
+
" + self.prototypes.join('')
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Lazy initializer for the composite RubytoC translator chain.
|
100
|
+
|
101
|
+
def self.translator
|
102
|
+
unless defined? @translator then
|
103
|
+
@translator = CompositeSexpProcessor.new
|
104
|
+
@translator << Rewriter.new
|
105
|
+
@translator << TypeChecker.new
|
106
|
+
# @translator << CRewriter.new
|
107
|
+
@translator << RubyToAnsiC.new
|
108
|
+
@translator.on_error_in(:defn) do |processor, exp, err|
|
109
|
+
result = processor.expected.new
|
110
|
+
case result
|
111
|
+
when Array then
|
112
|
+
result << :error
|
113
|
+
end
|
114
|
+
msg = "// ERROR: #{err.class}: #{err}"
|
115
|
+
msg += " in #{exp.inspect}" unless exp.nil? or $TESTING
|
116
|
+
msg += " from #{caller.join(', ')}" unless $TESTING
|
117
|
+
result << msg
|
118
|
+
result
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@translator
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize # :nodoc:
|
125
|
+
super
|
126
|
+
@env = ::R2CEnvironment.new
|
127
|
+
self.auto_shift_type = true
|
128
|
+
self.unsupported = [:alias, :alloca, :argscat, :argspush, :attrasgn, :attrset, :back_ref, :begin, :block_arg, :block_pass, :bmethod, :break, :case, :cdecl, :cfunc, :colon2, :colon3, :cref, :cvasgn, :cvdecl, :dasgn, :defined, :defs, :dmethod, :dot2, :dot3, :dregx, :dregx_once, :dstr, :dsym, :dxstr, :ensure, :evstr, :fbody, :fcall, :flip2, :flip3, :for, :gasgn, :hash, :ifunc, :last, :masgn, :match, :match2, :match3, :memo, :method, :module, :newline, :next, :nth_ref, :op_asgn_or, :op_asgn1, :op_asgn2, :op_asgn_and, :opt_n, :postexe, :redo, :resbody, :rescue, :retry, :sclass, :self, :splat, :super, :svalue, :to_ary, :undef, :until, :valias, :vcall, :when, :xstr, :yield, :zarray, :zsuper]
|
129
|
+
|
130
|
+
self.strict = true
|
131
|
+
self.expected = String
|
132
|
+
|
133
|
+
@statics = []
|
134
|
+
@prototypes = []
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Logical And. Nothing exciting here
|
139
|
+
|
140
|
+
def process_and(exp)
|
141
|
+
lhs = process exp.shift
|
142
|
+
rhs = process exp.shift
|
143
|
+
|
144
|
+
return "#{lhs} && #{rhs}"
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Arglist is used by call arg lists.
|
149
|
+
|
150
|
+
def process_arglist(exp)
|
151
|
+
return '' if exp.empty?
|
152
|
+
return process_array(exp)
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Argument List including variable types.
|
157
|
+
|
158
|
+
def process_args(exp)
|
159
|
+
args = []
|
160
|
+
|
161
|
+
until exp.empty? do
|
162
|
+
arg = exp.shift
|
163
|
+
name = arg.first.to_s.sub(/^\*/, '').intern
|
164
|
+
type = arg.sexp_type
|
165
|
+
@env.add name, type
|
166
|
+
args << "#{self.class.c_type(type)} #{name}"
|
167
|
+
end
|
168
|
+
|
169
|
+
return "(#{args.join ', '})"
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Array is used as call arg lists and as initializers for variables.
|
174
|
+
|
175
|
+
def process_array(exp)
|
176
|
+
return "rb_ary_new()" if exp.empty? # HACK FIX! not ansi c!
|
177
|
+
|
178
|
+
code = []
|
179
|
+
until exp.empty? do
|
180
|
+
code << process(exp.shift)
|
181
|
+
end
|
182
|
+
|
183
|
+
s = code.join ', '
|
184
|
+
|
185
|
+
return s
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Block doesn't have an analog in C, except maybe as a functions's
|
190
|
+
# outer braces.
|
191
|
+
|
192
|
+
def process_block(exp)
|
193
|
+
code = []
|
194
|
+
until exp.empty? do
|
195
|
+
code << process(exp.shift)
|
196
|
+
end
|
197
|
+
|
198
|
+
body = code.join(";\n")
|
199
|
+
body += ";" unless body =~ /[;}]\Z/
|
200
|
+
body += "\n"
|
201
|
+
|
202
|
+
return body
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Call, both unary and binary operators and regular function calls.
|
207
|
+
#
|
208
|
+
# TODO: This needs a lot of work. We've cheated with the case
|
209
|
+
# statement below. We need a real function signature lookup like we
|
210
|
+
# have in R2CRewriter.
|
211
|
+
|
212
|
+
def process_call(exp)
|
213
|
+
receiver = exp.shift
|
214
|
+
name = exp.shift
|
215
|
+
|
216
|
+
receiver_type = Type.unknown
|
217
|
+
unless receiver.nil? then
|
218
|
+
receiver_type = receiver.sexp_type
|
219
|
+
end
|
220
|
+
receiver = process receiver
|
221
|
+
|
222
|
+
case name
|
223
|
+
# TODO: these need to be numerics
|
224
|
+
# emacs gets confused by :/ below, need quotes to fix indentation
|
225
|
+
when :==, :<, :>, :<=, :>=, :-, :+, :*, :"/", :% then
|
226
|
+
args = process exp.shift[1]
|
227
|
+
return "#{receiver} #{name} #{args}"
|
228
|
+
when :<=>
|
229
|
+
args = process exp.shift[1]
|
230
|
+
return "RB_COMPARE(#{receiver}, #{args})"
|
231
|
+
when :equal?
|
232
|
+
args = process exp.shift
|
233
|
+
return "#{receiver} == #{args}" # equal? == address equality
|
234
|
+
when :[]
|
235
|
+
args = process exp.shift
|
236
|
+
return "#{receiver}[#{args}]"
|
237
|
+
when :nil?
|
238
|
+
exp.clear
|
239
|
+
return receiver.to_s
|
240
|
+
else
|
241
|
+
args = process exp.shift
|
242
|
+
|
243
|
+
if receiver.nil? and args.nil? then
|
244
|
+
args = ""
|
245
|
+
elsif receiver.nil? then
|
246
|
+
# nothing to do
|
247
|
+
elsif args.nil? or args.empty? then
|
248
|
+
args = receiver
|
249
|
+
else
|
250
|
+
args = "#{receiver}, #{args}"
|
251
|
+
end
|
252
|
+
|
253
|
+
args = '' if args == 'rb_ary_new()' # HACK
|
254
|
+
|
255
|
+
return "#{name}(#{args})"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
##
|
260
|
+
# DOC
|
261
|
+
|
262
|
+
def process_class(exp)
|
263
|
+
name = exp.shift
|
264
|
+
superklass = exp.shift
|
265
|
+
|
266
|
+
result = []
|
267
|
+
|
268
|
+
until exp.empty? do
|
269
|
+
# HACK: cheating!
|
270
|
+
result << process(exp.shift)
|
271
|
+
end
|
272
|
+
|
273
|
+
result.unshift(*statics)
|
274
|
+
result.unshift "// class #{name} < #{superklass}"
|
275
|
+
|
276
|
+
return result.join("\n\n")
|
277
|
+
end
|
278
|
+
|
279
|
+
##
|
280
|
+
# Constants, must be pre-defined in the global env for ansi c.
|
281
|
+
|
282
|
+
def process_const(exp)
|
283
|
+
name = exp.shift
|
284
|
+
return name.to_s
|
285
|
+
end
|
286
|
+
|
287
|
+
##
|
288
|
+
# Constants, must be defined in the global env.
|
289
|
+
#
|
290
|
+
# TODO: This will cause a lot of errors with the built in classes
|
291
|
+
# until we add them to the bootstrap phase.
|
292
|
+
# HACK: what is going on here??? We have NO tests for this node
|
293
|
+
|
294
|
+
def process_cvar(exp)
|
295
|
+
# TODO: we should treat these as globals and have them in the top scope
|
296
|
+
name = exp.shift
|
297
|
+
return name.to_s
|
298
|
+
end
|
299
|
+
|
300
|
+
##
|
301
|
+
# Iterator variables.
|
302
|
+
#
|
303
|
+
# TODO: check to see if this is the least bit relevant anymore. We
|
304
|
+
# might have rewritten them all.
|
305
|
+
|
306
|
+
def process_dasgn_curr(exp) # TODO: audit against obfuscator
|
307
|
+
var = exp.shift
|
308
|
+
@env.add var.to_sym, exp.sexp_type
|
309
|
+
return var.to_s
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# Function definition
|
314
|
+
|
315
|
+
METHOD_MAP = { # TODO: steal map from ZenTest
|
316
|
+
:| => "or",
|
317
|
+
:& => "and",
|
318
|
+
:^ => "xor",
|
319
|
+
}
|
320
|
+
|
321
|
+
def process_defn(exp) # TODO: audit against obfuscator
|
322
|
+
name = exp.shift
|
323
|
+
name = METHOD_MAP[name] if METHOD_MAP.has_key? name
|
324
|
+
name = name.to_s.sub(/(.*)\?$/, 'is_\1').intern
|
325
|
+
args = process exp.shift
|
326
|
+
body = process exp.shift
|
327
|
+
function_type = exp.sexp_type
|
328
|
+
|
329
|
+
ret_type = self.class.c_type function_type.list_type.return_type
|
330
|
+
|
331
|
+
@prototypes << "#{ret_type} #{name}#{args};\n"
|
332
|
+
"#{ret_type}\n#{name}#{args} #{body}"
|
333
|
+
end
|
334
|
+
|
335
|
+
def process_defx(exp) # TODO: audit against obfuscator
|
336
|
+
return process_defn(exp)
|
337
|
+
end
|
338
|
+
|
339
|
+
##
|
340
|
+
# Generic handler. Ignore me, I'm not here.
|
341
|
+
#
|
342
|
+
# TODO: nuke dummy nodes by using new SexpProcessor rewrite rules.
|
343
|
+
|
344
|
+
def process_dummy(exp)
|
345
|
+
process_block(exp).chomp
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# Dynamic variables, should be the same as lvar at this stage.
|
350
|
+
#
|
351
|
+
# TODO: remove / rewrite?
|
352
|
+
|
353
|
+
def process_dvar(exp)
|
354
|
+
var = exp.shift
|
355
|
+
@env.add var.to_sym, exp.sexp_type
|
356
|
+
return var.to_s
|
357
|
+
end
|
358
|
+
|
359
|
+
##
|
360
|
+
# DOC - TODO: what is this?!?
|
361
|
+
|
362
|
+
def process_error(exp)
|
363
|
+
return exp.shift
|
364
|
+
end
|
365
|
+
|
366
|
+
##
|
367
|
+
# False. Pretty straightforward.
|
368
|
+
|
369
|
+
def process_false(exp)
|
370
|
+
return "0"
|
371
|
+
end
|
372
|
+
|
373
|
+
# TODO: process_gasgn
|
374
|
+
|
375
|
+
##
|
376
|
+
# Global variables, evil but necessary.
|
377
|
+
#
|
378
|
+
# TODO: get the case statement out by using proper bootstrap in genv.
|
379
|
+
|
380
|
+
def process_gvar(exp)
|
381
|
+
name = exp.shift
|
382
|
+
type = exp.sexp_type
|
383
|
+
case name
|
384
|
+
when :$stderr then
|
385
|
+
"stderr"
|
386
|
+
else
|
387
|
+
raise "Bug! Unhandled gvar #{name.inspect} (type = #{type})"
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# Instance Variable Assignment
|
393
|
+
|
394
|
+
def process_iasgn(exp)
|
395
|
+
name = exp.shift
|
396
|
+
val = process exp.shift
|
397
|
+
"self->#{name.to_s.sub(/^@/, '')} = #{val}"
|
398
|
+
end
|
399
|
+
|
400
|
+
##
|
401
|
+
# Conditional statements
|
402
|
+
#
|
403
|
+
# TODO: implementation is ugly as hell... PLEASE try to clean
|
404
|
+
|
405
|
+
def process_if(exp)
|
406
|
+
cond_part = process exp.shift
|
407
|
+
|
408
|
+
result = "if (#{cond_part})"
|
409
|
+
|
410
|
+
then_block = ! exp.first.nil? && exp.first.first == :block
|
411
|
+
then_part = process exp.shift
|
412
|
+
else_block = ! exp.first.nil? && exp.first.first == :block
|
413
|
+
else_part = process exp.shift
|
414
|
+
|
415
|
+
then_part = "" if then_part.nil?
|
416
|
+
else_part = "" if else_part.nil?
|
417
|
+
|
418
|
+
result += " {\n"
|
419
|
+
|
420
|
+
then_part = then_part.join(";\n") if Array === then_part
|
421
|
+
then_part += ";" unless then_part =~ /[;}]\Z/
|
422
|
+
# HACK: um... deal with nil correctly (see unless support)
|
423
|
+
result += then_part.to_s # + ";"
|
424
|
+
result += ";" if then_part.nil?
|
425
|
+
result += "\n" unless result =~ /\n\Z/
|
426
|
+
result += "}"
|
427
|
+
|
428
|
+
if else_part != "" then
|
429
|
+
result += " else {\n"
|
430
|
+
else_part = else_part.join(";\n") if Array === else_part
|
431
|
+
else_part += ";" unless else_part =~ /[;}]\Z/
|
432
|
+
result += else_part
|
433
|
+
result += "\n}"
|
434
|
+
end
|
435
|
+
|
436
|
+
result
|
437
|
+
end
|
438
|
+
|
439
|
+
##
|
440
|
+
# Iterators for loops. After rewriter nearly all iter nodes
|
441
|
+
# should be able to be interpreted as a for loop. If not, then you
|
442
|
+
# are doing something not supported by C in the first place.
|
443
|
+
|
444
|
+
def process_iter(exp) # TODO: audit against obfuscator
|
445
|
+
out = []
|
446
|
+
# Only support enums in C-land
|
447
|
+
raise UnsupportedNodeError if exp[0][1].nil? # HACK ugly
|
448
|
+
@env.scope do
|
449
|
+
enum = exp[0][1][1] # HACK ugly t(:iter, t(:call, lhs <-- get lhs
|
450
|
+
|
451
|
+
p exp
|
452
|
+
|
453
|
+
call = process exp.shift
|
454
|
+
var = process(exp.shift).intern # semi-HACK-y
|
455
|
+
body = process exp.shift
|
456
|
+
index = "index_#{var}"
|
457
|
+
|
458
|
+
body += ";" unless body =~ /[;}]\Z/
|
459
|
+
body.gsub!(/\n\n+/, "\n")
|
460
|
+
|
461
|
+
out << "unsigned long #{index};"
|
462
|
+
out << "for (#{index} = 0; #{enum}[#{index}] != NULL; ++#{index}) {"
|
463
|
+
out << "#{self.class.c_type @env.lookup(var)} #{var} = #{enum}[#{index}];"
|
464
|
+
out << body
|
465
|
+
out << "}"
|
466
|
+
end
|
467
|
+
|
468
|
+
return out.join("\n")
|
469
|
+
end
|
470
|
+
|
471
|
+
##
|
472
|
+
# Instance Variable Access
|
473
|
+
|
474
|
+
def process_ivar(exp)
|
475
|
+
name = exp.shift
|
476
|
+
"self->#{name.to_s.sub(/^@/, '')}"
|
477
|
+
end
|
478
|
+
|
479
|
+
##
|
480
|
+
# Assignment to a local variable.
|
481
|
+
#
|
482
|
+
# TODO: figure out array issues and clean up.
|
483
|
+
|
484
|
+
def process_lasgn(exp) # TODO: audit against obfuscator
|
485
|
+
out = ""
|
486
|
+
|
487
|
+
var = exp.shift
|
488
|
+
value = exp.shift
|
489
|
+
# grab the size of the args, if any, before process converts to a string
|
490
|
+
arg_count = 0
|
491
|
+
arg_count = value.length - 1 if value.first == :array
|
492
|
+
args = value
|
493
|
+
|
494
|
+
exp_type = exp.sexp_type
|
495
|
+
@env.add var.to_sym, exp_type
|
496
|
+
var_type = self.class.c_type exp_type
|
497
|
+
|
498
|
+
if exp_type.list? then
|
499
|
+
assert_type args, :array
|
500
|
+
|
501
|
+
raise "array must be of one type" unless args.sexp_type == Type.homo
|
502
|
+
|
503
|
+
# HACK: until we figure out properly what to do w/ zarray
|
504
|
+
# before we know what its type is, we will default to long.
|
505
|
+
array_type = args.sexp_types.empty? ? 'void *' : self.class.c_type(args.sexp_types.first)
|
506
|
+
|
507
|
+
args.shift # :arglist
|
508
|
+
# TODO: look into alloca
|
509
|
+
out << "#{var} = (#{array_type}) malloc(sizeof(#{array_type}) * #{args.length});\n"
|
510
|
+
args.each_with_index do |o,i|
|
511
|
+
out << "#{var}[#{i}] = #{process o};\n"
|
512
|
+
end
|
513
|
+
else
|
514
|
+
out << "#{var} = #{process args}"
|
515
|
+
end
|
516
|
+
|
517
|
+
out.sub!(/;\n\Z/, '')
|
518
|
+
|
519
|
+
return out
|
520
|
+
end
|
521
|
+
|
522
|
+
##
|
523
|
+
# Literals, numbers for the most part. Will probably cause
|
524
|
+
# compilation errors if you try to translate bignums and other
|
525
|
+
# values that don't have analogs in the C world. Sensing a pattern?
|
526
|
+
|
527
|
+
def process_lit(exp)
|
528
|
+
# TODO what about floats and big numbers?
|
529
|
+
|
530
|
+
value = exp.shift
|
531
|
+
sexp_type = exp.sexp_type
|
532
|
+
case sexp_type
|
533
|
+
when Type.long, Type.float then
|
534
|
+
return value.to_s
|
535
|
+
when Type.symbol then
|
536
|
+
return value.to_s.inspect # HACK wrong! write test!
|
537
|
+
else
|
538
|
+
raise "Bug! no: Unknown literal #{value}:#{value.class}"
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
##
|
543
|
+
# Local variable
|
544
|
+
|
545
|
+
def process_lvar(exp)
|
546
|
+
name = exp.shift
|
547
|
+
# do nothing at this stage, var should have been checked for
|
548
|
+
# existance already.
|
549
|
+
return name.to_s
|
550
|
+
end
|
551
|
+
|
552
|
+
# TODO: pull masgn from obfuscator
|
553
|
+
# TODO: pull module from obfuscator
|
554
|
+
# TODO: pull next from obfuscator
|
555
|
+
|
556
|
+
##
|
557
|
+
# Nil, currently ruby nil, not C NULL (0).
|
558
|
+
|
559
|
+
def process_nil(exp)
|
560
|
+
return "NULL"
|
561
|
+
end
|
562
|
+
|
563
|
+
##
|
564
|
+
# Nil, currently ruby nil, not C NULL (0).
|
565
|
+
|
566
|
+
def process_not(exp)
|
567
|
+
term = process exp.shift
|
568
|
+
return "!(#{term})"
|
569
|
+
end
|
570
|
+
|
571
|
+
##
|
572
|
+
# Logical or. Nothing exciting here
|
573
|
+
|
574
|
+
def process_or(exp)
|
575
|
+
lhs = process exp.shift
|
576
|
+
rhs = process exp.shift
|
577
|
+
|
578
|
+
return "#{lhs} || #{rhs}"
|
579
|
+
end
|
580
|
+
|
581
|
+
##
|
582
|
+
# Return statement. Nothing exciting here
|
583
|
+
|
584
|
+
def process_return(exp)
|
585
|
+
return "return #{process exp.shift}"
|
586
|
+
end
|
587
|
+
|
588
|
+
##
|
589
|
+
# Scope has no real equivalent in C-land, except that like
|
590
|
+
# process_block above. We put variable declarations here before the
|
591
|
+
# body and use this as our opportunity to open a variable
|
592
|
+
# scope. Crafty, no?
|
593
|
+
|
594
|
+
def process_scope(exp)
|
595
|
+
declarations, body = with_scope do
|
596
|
+
process exp.shift unless exp.empty?
|
597
|
+
end
|
598
|
+
|
599
|
+
declarations = declarations.reject { |d| d =~ / static_/ }
|
600
|
+
|
601
|
+
result = []
|
602
|
+
result << "{"
|
603
|
+
result << declarations.join("\n") unless declarations.empty?
|
604
|
+
result << body.chomp if body
|
605
|
+
result << "}"
|
606
|
+
|
607
|
+
return result.join("\n")
|
608
|
+
end
|
609
|
+
|
610
|
+
##
|
611
|
+
# A bogus ruby sexp type for generating static variable declarations
|
612
|
+
|
613
|
+
def process_static(exp)
|
614
|
+
return exp.shift
|
615
|
+
end
|
616
|
+
|
617
|
+
##
|
618
|
+
# Strings. woot.
|
619
|
+
|
620
|
+
def process_str(exp)
|
621
|
+
return exp.shift.inspect
|
622
|
+
end
|
623
|
+
|
624
|
+
# TODO: pull scope from obfuscator
|
625
|
+
|
626
|
+
##
|
627
|
+
# Truth... what is truth?
|
628
|
+
|
629
|
+
def process_true(exp)
|
630
|
+
return "1"
|
631
|
+
end
|
632
|
+
|
633
|
+
##
|
634
|
+
# While block. Nothing exciting here.
|
635
|
+
|
636
|
+
def process_while(exp)
|
637
|
+
cond = process exp.shift
|
638
|
+
body = process exp.shift
|
639
|
+
body += ";" unless body =~ /;/
|
640
|
+
is_precondition = exp.shift
|
641
|
+
if is_precondition then
|
642
|
+
return "while (#{cond}) {\n#{body.strip}\n}"
|
643
|
+
else
|
644
|
+
return "{\n#{body.strip}\n} while (#{cond})"
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
def with_scope
|
649
|
+
declarations = []
|
650
|
+
result = nil
|
651
|
+
outer_scope = @env.all.keys
|
652
|
+
|
653
|
+
@env.scope do
|
654
|
+
result = yield
|
655
|
+
@env.current.sort_by { |v,_| v.to_s }.each do |var, (type,val)|
|
656
|
+
next if outer_scope.include? var
|
657
|
+
decl = "#{self.class.c_type type} #{var}"
|
658
|
+
case val
|
659
|
+
when nil then
|
660
|
+
# do nothing
|
661
|
+
when /^\[/ then
|
662
|
+
decl << "#{val}"
|
663
|
+
else
|
664
|
+
decl << " = #{val}"
|
665
|
+
end
|
666
|
+
decl << ';'
|
667
|
+
declarations << decl
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
return declarations, result
|
672
|
+
end
|
673
|
+
end
|