rubyjs 0.7.0

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.
Files changed (144) hide show
  1. data/README +128 -0
  2. data/Rakefile +9 -0
  3. data/bin/rubyjs +140 -0
  4. data/examples/ex1/Rakefile +7 -0
  5. data/examples/ex1/ex1.js +771 -0
  6. data/examples/ex1/ex1.rb +42 -0
  7. data/examples/ex1/index.html +7 -0
  8. data/examples/hw/Rakefile +9 -0
  9. data/examples/hw/hw.js +635 -0
  10. data/examples/hw/hw.rb +7 -0
  11. data/examples/hw/index.html +7 -0
  12. data/patches/parse_tree.rb.diff +34 -0
  13. data/rubyjs.gemspec +24 -0
  14. data/src/rubyjs.rb +4 -0
  15. data/src/rubyjs/code_generator.rb +474 -0
  16. data/src/rubyjs/compiler.rb +2007 -0
  17. data/src/rubyjs/debug_name_generator.rb +75 -0
  18. data/src/rubyjs/encoder.rb +171 -0
  19. data/src/rubyjs/eval_into.rb +59 -0
  20. data/src/rubyjs/lib/core.rb +1008 -0
  21. data/src/rubyjs/lib/json.rb +101 -0
  22. data/src/rubyjs/model.rb +287 -0
  23. data/src/rubyjs/name_generator.rb +71 -0
  24. data/src/rwt/AbsolutePanel.rb +161 -0
  25. data/src/rwt/DOM.Konqueror.rb +89 -0
  26. data/src/rwt/DOM.Opera.rb +65 -0
  27. data/src/rwt/DOM.rb +1044 -0
  28. data/src/rwt/Event.Opera.rb +35 -0
  29. data/src/rwt/Event.rb +429 -0
  30. data/src/rwt/HTTPRequest.IE6.rb +5 -0
  31. data/src/rwt/HTTPRequest.rb +74 -0
  32. data/src/rwt/Label.rb +164 -0
  33. data/src/rwt/Panel.rb +90 -0
  34. data/src/rwt/RootPanel.rb +16 -0
  35. data/src/rwt/UIObject.rb +495 -0
  36. data/src/rwt/Widget.rb +193 -0
  37. data/src/rwt/ported-from/AbsolutePanel.java +158 -0
  38. data/src/rwt/ported-from/DOM.java +571 -0
  39. data/src/rwt/ported-from/DOMImpl.java +426 -0
  40. data/src/rwt/ported-from/DOMImplOpera.java +82 -0
  41. data/src/rwt/ported-from/DOMImplStandard.java +234 -0
  42. data/src/rwt/ported-from/HTTPRequest.java +81 -0
  43. data/src/rwt/ported-from/HTTPRequestImpl.java +103 -0
  44. data/src/rwt/ported-from/Label.java +163 -0
  45. data/src/rwt/ported-from/Panel.java +99 -0
  46. data/src/rwt/ported-from/UIObject.java +614 -0
  47. data/src/rwt/ported-from/Widget.java +221 -0
  48. data/test/benchmark/bm_call_conv1.js +16 -0
  49. data/test/benchmark/bm_call_conv2.js +14 -0
  50. data/test/benchmark/bm_var_acc1.js +13 -0
  51. data/test/benchmark/bm_var_acc2.js +11 -0
  52. data/test/benchmark/bm_vm1_block.js +15 -0
  53. data/test/benchmark/bm_vm1_block.rb +15 -0
  54. data/test/benchmark/bm_vm1_const.js +13 -0
  55. data/test/benchmark/bm_vm1_const.rb +13 -0
  56. data/test/benchmark/bm_vm1_ensure.js +17 -0
  57. data/test/benchmark/bm_vm1_ensure.rb +15 -0
  58. data/test/benchmark/common.js +4 -0
  59. data/test/benchmark/common.rb +5 -0
  60. data/test/benchmark/params.yaml +7 -0
  61. data/test/browser.test.html +4059 -0
  62. data/test/browser.test.js +3225 -0
  63. data/test/common.Browser.rb +13 -0
  64. data/test/common.rb +8 -0
  65. data/test/gen_browser_test_suite.rb +129 -0
  66. data/test/gen_test_suite.rb +41 -0
  67. data/test/run_benchs.rb +58 -0
  68. data/test/run_tests.rb +22 -0
  69. data/test/test_args.rb +24 -0
  70. data/test/test_array.rb +26 -0
  71. data/test/test_case.rb +35 -0
  72. data/test/test_class.rb +55 -0
  73. data/test/test_eql.rb +9 -0
  74. data/test/test_exception.rb +61 -0
  75. data/test/test_expr.rb +12 -0
  76. data/test/test_hash.rb +29 -0
  77. data/test/test_if.rb +28 -0
  78. data/test/test_inspect.rb +10 -0
  79. data/test/test_lebewesen.rb +39 -0
  80. data/test/test_massign.rb +66 -0
  81. data/test/test_new.rb +12 -0
  82. data/test/test_range.rb +53 -0
  83. data/test/test_regexp.rb +22 -0
  84. data/test/test_send.rb +65 -0
  85. data/test/test_simple_output.rb +5 -0
  86. data/test/test_splat.rb +21 -0
  87. data/test/test_string.rb +51 -0
  88. data/test/test_yield.rb +152 -0
  89. data/utils/js/Makefile +9 -0
  90. data/utils/js/RunScript.class +0 -0
  91. data/utils/js/RunScript.java +73 -0
  92. data/utils/js/js.jar +0 -0
  93. data/utils/js/run.sh +3 -0
  94. data/utils/jsc/Makefile +7 -0
  95. data/utils/jsc/README +3 -0
  96. data/utils/jsc/RunScript.c +93 -0
  97. data/utils/jsc/run.sh +15 -0
  98. data/utils/yuicompressor/README +1 -0
  99. data/utils/yuicompressor/yuicompressor-2.2.5.jar +0 -0
  100. data/vendor/ParseTree-1.7.1-patched/History.txt +217 -0
  101. data/vendor/ParseTree-1.7.1-patched/Manifest.txt +22 -0
  102. data/vendor/ParseTree-1.7.1-patched/README.txt +110 -0
  103. data/vendor/ParseTree-1.7.1-patched/Rakefile +41 -0
  104. data/vendor/ParseTree-1.7.1-patched/bin/parse_tree_abc +89 -0
  105. data/vendor/ParseTree-1.7.1-patched/bin/parse_tree_audit +28 -0
  106. data/vendor/ParseTree-1.7.1-patched/bin/parse_tree_deps +62 -0
  107. data/vendor/ParseTree-1.7.1-patched/bin/parse_tree_show +49 -0
  108. data/vendor/ParseTree-1.7.1-patched/demo/printer.rb +20 -0
  109. data/vendor/ParseTree-1.7.1-patched/lib/composite_sexp_processor.rb +49 -0
  110. data/vendor/ParseTree-1.7.1-patched/lib/parse_tree.rb +1013 -0
  111. data/vendor/ParseTree-1.7.1-patched/lib/sexp.rb +235 -0
  112. data/vendor/ParseTree-1.7.1-patched/lib/sexp_processor.rb +330 -0
  113. data/vendor/ParseTree-1.7.1-patched/lib/unique.rb +15 -0
  114. data/vendor/ParseTree-1.7.1-patched/test/pt_testcase.rb +1221 -0
  115. data/vendor/ParseTree-1.7.1-patched/test/something.rb +53 -0
  116. data/vendor/ParseTree-1.7.1-patched/test/test_all.rb +13 -0
  117. data/vendor/ParseTree-1.7.1-patched/test/test_composite_sexp_processor.rb +69 -0
  118. data/vendor/ParseTree-1.7.1-patched/test/test_parse_tree.rb +216 -0
  119. data/vendor/ParseTree-1.7.1-patched/test/test_sexp.rb +291 -0
  120. data/vendor/ParseTree-1.7.1-patched/test/test_sexp_processor.rb +244 -0
  121. data/vendor/ParseTree-1.7.1-patched/validate.sh +31 -0
  122. data/vendor/ParseTree-1.7.1/History.txt +217 -0
  123. data/vendor/ParseTree-1.7.1/Manifest.txt +22 -0
  124. data/vendor/ParseTree-1.7.1/README.txt +110 -0
  125. data/vendor/ParseTree-1.7.1/Rakefile +41 -0
  126. data/vendor/ParseTree-1.7.1/bin/parse_tree_abc +89 -0
  127. data/vendor/ParseTree-1.7.1/bin/parse_tree_audit +28 -0
  128. data/vendor/ParseTree-1.7.1/bin/parse_tree_deps +62 -0
  129. data/vendor/ParseTree-1.7.1/bin/parse_tree_show +49 -0
  130. data/vendor/ParseTree-1.7.1/demo/printer.rb +20 -0
  131. data/vendor/ParseTree-1.7.1/lib/composite_sexp_processor.rb +49 -0
  132. data/vendor/ParseTree-1.7.1/lib/parse_tree.rb +1004 -0
  133. data/vendor/ParseTree-1.7.1/lib/sexp.rb +235 -0
  134. data/vendor/ParseTree-1.7.1/lib/sexp_processor.rb +330 -0
  135. data/vendor/ParseTree-1.7.1/lib/unique.rb +15 -0
  136. data/vendor/ParseTree-1.7.1/test/pt_testcase.rb +1221 -0
  137. data/vendor/ParseTree-1.7.1/test/something.rb +53 -0
  138. data/vendor/ParseTree-1.7.1/test/test_all.rb +13 -0
  139. data/vendor/ParseTree-1.7.1/test/test_composite_sexp_processor.rb +69 -0
  140. data/vendor/ParseTree-1.7.1/test/test_parse_tree.rb +216 -0
  141. data/vendor/ParseTree-1.7.1/test/test_sexp.rb +291 -0
  142. data/vendor/ParseTree-1.7.1/test/test_sexp_processor.rb +244 -0
  143. data/vendor/ParseTree-1.7.1/validate.sh +31 -0
  144. metadata +230 -0
@@ -0,0 +1,2007 @@
1
+ #
2
+ # A Ruby to Javascript compiler.
3
+ #
4
+ # Copyright (c) 2007 by Michael Neumann (mneumann@ntecs.de).
5
+ # All rights reserved.
6
+ #
7
+
8
+ require 'sexp_processor'
9
+ require 'set'
10
+
11
+ class MethodCompiler < SexpProcessor
12
+
13
+ def initialize(model, method_name)
14
+ super()
15
+
16
+ # don't stop at unknown nodes
17
+ self.strict = true
18
+
19
+ # remove the type from the sexp array
20
+ self.auto_shift_type = true
21
+
22
+ # warn whenever the default method is called, i.e.
23
+ # an unknown sexp is processed.
24
+ self.warn_on_default = true
25
+
26
+ # expected result class
27
+ self.expected = String
28
+
29
+ #
30
+ # Used to collect the dynamic variable declarations for
31
+ # an iterator.
32
+ #
33
+ @current_iter_dvars = nil
34
+
35
+ #
36
+ # The model object used for encoding and name generation of all
37
+ # kind of variables etc.
38
+ #
39
+ @model = model
40
+
41
+ #
42
+ # The (encoded) name of the method being compiled
43
+ #
44
+ @method_name = method_name
45
+
46
+ #
47
+ # record all local variables (including arguments)
48
+ #
49
+ @local_variables = Set.new
50
+
51
+ #
52
+ # Those local variables that need not be initialized with "nil" are
53
+ # contained in this set (mostly temporary variables).
54
+ #
55
+ @local_variables_need_no_initialization = Set.new
56
+
57
+ #
58
+ # contains all argument variables except "*args"
59
+ #
60
+ @arguments_no_splat = []
61
+
62
+ #
63
+ # Contains the name of a splat argument "*args"
64
+ # or nil if none has been specified.
65
+ #
66
+ @argument_splat = nil
67
+
68
+ #
69
+ # Include all arguments
70
+ #
71
+ @argument_variables = Set.new
72
+
73
+ #
74
+ # If the result should be an expression, this is set to true.
75
+ #
76
+ @want_expression = false
77
+
78
+ #
79
+ # Usually each method introduces a new local variable scope.
80
+ # Make sure that there is no nested :scope.
81
+ #
82
+ @scope_nesting = 0
83
+
84
+ #
85
+ # We collect all instance variable reads, because they might not be
86
+ # initialized yet and we want them to be initialized as "nil"!
87
+ #
88
+ @read_instance_variables = Set.new
89
+
90
+ #
91
+ # Use to reuse unused temporary variables
92
+ #
93
+ @temporary_variables_pool = []
94
+
95
+
96
+ #
97
+ # If a return value should be assigned or not.
98
+ #
99
+ @want_result = false
100
+
101
+
102
+ #
103
+ # contains the name of the block argument, e.g.
104
+ #
105
+ # def a(&block)
106
+ # end
107
+ #
108
+ # would contain the encoded name of "block".
109
+ #
110
+ @block_arg_name = nil
111
+
112
+ #
113
+ # Argument name used for the block argument:
114
+ #
115
+ # function (#{@block_name}, ...) { ... }
116
+ #
117
+ # Generated on demand with block_name().
118
+ #
119
+ @block_name = nil
120
+
121
+ #
122
+ # The name of the variable that contains the return value.
123
+ #
124
+ @result_name = nil
125
+
126
+ #
127
+ # For implementing +break+ and +next+ we
128
+ # have to keep track whether the +break+
129
+ # or +next+ statement occurs inside a
130
+ # +while+ loop or inside of a block.
131
+ #
132
+ @block_whileloop_stack = []
133
+
134
+ #
135
+ # Whether the method body contains any code that
136
+ # makes use of iterators (e.g. yield, &block).
137
+ #
138
+ @iterators_used = false
139
+
140
+ #
141
+ # Used in a zsuper call (super without arguments)
142
+ #
143
+ @arguments_name = nil
144
+
145
+ #
146
+ # Used in catch statements
147
+ #
148
+ @exception_name = nil
149
+
150
+
151
+
152
+ #
153
+ # Each method has a unique id assigned to it (a simple integer).
154
+ # This is used for returns that occur inside a code-block, which
155
+ # have to return the method in which the code-block is declared and
156
+ # NOT the method in which it is called".
157
+ #
158
+ @unique_method_scope = nil
159
+ end
160
+
161
+ def compile_method(pt)
162
+ process(pt)
163
+ end
164
+
165
+ #
166
+ #
167
+ #
168
+ def want_expression(wish=true)
169
+ old = @want_expression
170
+ @want_expression = wish
171
+ res = yield
172
+ @want_expression = old
173
+ return res
174
+ end
175
+
176
+ #
177
+ # There exist a short cut in the node tree for simple getter and
178
+ # setter-methods:
179
+ #
180
+ # [:defn, :method_name, [:ivar, :variable_name]]
181
+ #
182
+ # [:defn, :method_name, [:attrset, :variable_name]]
183
+ #
184
+ # We don't want to handle them differently, that's why we expand them
185
+ # into:
186
+ #
187
+ # [:block, [:ivar, :variable_name]]
188
+ #
189
+ # [:block, [:args, :v], [:iasgn, :variable_name, [:lvar, :v]]]
190
+ #
191
+ def process_defn(exp)
192
+ raise if @result_name
193
+ method_name = exp.shift || raise
194
+ exp = exp.shift || raise
195
+
196
+ method_body = want_result do
197
+ case exp.first
198
+ when :ivar
199
+ process(s(:block, exp))
200
+ when :attrset
201
+ process(s(:block, s(:args, :_), s(:iasgn, exp[1], s(:lvar, :_))))
202
+ when :scope, :block, :fbody
203
+ process(exp)
204
+ else
205
+ raise
206
+ end
207
+ end
208
+
209
+ str = ""
210
+
211
+ if @argument_variables.empty?
212
+ str << "function(){"
213
+ else
214
+ args_str = ([block_name()] + @arguments_no_splat).join(",")
215
+ str << "function(#{args_str}){"
216
+ end
217
+
218
+ raise if @local_variables.include?(@model.encode_self)
219
+ raise if @argument_variables.include?(@model.encode_self)
220
+
221
+ #
222
+ # Add "self" to the local variables
223
+ #
224
+ @local_variables.add(@model.encode_self)
225
+ @local_variables_need_no_initialization.add(@model.encode_self)
226
+
227
+ #
228
+ # declare local variables (except arguments)
229
+ #
230
+ to_declare = (@local_variables - @argument_variables).to_a
231
+ to_declare << @result_name if @result_name
232
+ unless to_declare.empty?
233
+ str << "var " + to_declare.join(",") + sep()
234
+ end
235
+
236
+ #
237
+ # initialize all local variables (that need initialization) to nil
238
+ #
239
+ to_initialize = (@local_variables - @argument_variables - @local_variables_need_no_initialization).to_a
240
+ to_initialize << @result_name if @result_name
241
+ unless to_initialize.empty?
242
+ str << to_initialize.join("=")
243
+ str << "=#{@model.encode_nil}#{sep()}"
244
+ end
245
+
246
+ #
247
+ # initialize "self"
248
+ #
249
+ str << "#{@model.encode_self}=this#{sep()}"
250
+
251
+ #
252
+ # If a block argument is given (&block) convert it to nil if it is
253
+ # undefined.
254
+ #
255
+ if @block_arg_name
256
+ str << "#{@block_arg_name}=#{block_name()}==null?#{@model.encode_nil}:#{block_name()}#{sep()}"
257
+ end
258
+
259
+ #
260
+ # generate initialization code for each read instance variable
261
+ #
262
+ @read_instance_variables.each do |iv|
263
+ str << "if(#{@model.encode_self}.#{iv}==null)#{@model.encode_self}.#{iv}=#{@model.encode_nil}#{sep()}"
264
+ end
265
+
266
+ #
267
+ # Used in a zsuper call to refer to the methods "arguments"
268
+ # "arguments" itself does not work due to iterators using functions.
269
+ if @arguments_name
270
+ str << "var #{@arguments_name}=arguments#{sep()}"
271
+ end
272
+
273
+ method_body << "#{sep()}return #{@result_name}" if @result_name
274
+
275
+ if @iterators_used
276
+ str << "try{"
277
+ str << method_body
278
+
279
+ #
280
+ # Declare variable x?
281
+ # No, catch introduced a new scope, so we don't have to
282
+ # use a local or temporary variable here!
283
+ #
284
+ x = exception_name()
285
+ iter_jump = @model.encode_globalattr("iter_jump")
286
+ return_value = @model.encode_attr("return_value")
287
+ scope = @model.encode_attr("scope")
288
+ uid = unique_method_scope()
289
+
290
+ str << "}catch(#{x}){"
291
+ # scope == null or scope == uid
292
+ str << "if(#{x} instanceof #{iter_jump} && (!#{x}.#{scope} || #{x}.#{scope}==#{uid}))return #{x}.#{return_value}#{sep()}"
293
+ str << "throw(#{x})}"
294
+ else
295
+ str << method_body
296
+ end
297
+
298
+ str << "}"
299
+
300
+ return str
301
+ end
302
+
303
+ def sep
304
+ if $RUBYJS__OPTS.include?('PrettyPrint')
305
+ ";\n"
306
+ else
307
+ ";"
308
+ end
309
+ end
310
+
311
+ def unique_method_scope
312
+ @unique_method_scope ||= @model.next_unique_scope_id
313
+ end
314
+
315
+ def process_scope(exp)
316
+ raise "nested scope" if @scope_nesting > 0
317
+ @scope_nesting += 1
318
+ res = process(exp.shift)
319
+ @scope_nesting -= 1
320
+ return res
321
+ end
322
+
323
+ def process_fbody(exp)
324
+ process(exp.shift)
325
+ end
326
+
327
+ def process_block(exp)
328
+ raise "empty block" if exp.empty?
329
+
330
+ res = []
331
+
332
+ # all statements except the last don't want a result
333
+ without_result do
334
+ exp[0..-2].each do |stmt|
335
+ res << process(stmt)
336
+ end
337
+ end
338
+ # last statement
339
+ res << process(exp[-1])
340
+
341
+ res = res.reject {|r| r.nil? || r.empty? || r == "" || r == ";"}
342
+
343
+ exp.clear
344
+
345
+ if @want_expression
346
+ "(" + res.join(",") + ")"
347
+ else
348
+ res.join(sep())
349
+ end
350
+ end
351
+
352
+ #
353
+ # STATEMENT
354
+ #
355
+ def process_block_arg(exp)
356
+ raise if @want_expression || @block_arg_name
357
+ block = exp.shift
358
+ block_name() # make sure that the function signature contains a block argument!
359
+ @block_arg_name = @model.encode_local_variable(block)
360
+ @local_variables.add(@block_arg_name)
361
+ @local_variables_need_no_initialization.add(@block_arg_name)
362
+ return ""
363
+ end
364
+
365
+ #
366
+ # TODO?
367
+ #
368
+ def process_block_pass(exp)
369
+ block = exp.shift
370
+ call = exp.shift
371
+ without_result do
372
+ want_expression do
373
+ put_iter(process(block))
374
+ end
375
+ end
376
+ process(call)
377
+ end
378
+
379
+ def throw_argument_error(n)
380
+ @model.add_method_call(m = @model.encode_method("new"))
381
+ "throw(" + @model.lookup_constant('::ArgumentError') +
382
+ ".#{m}(#{@model.encode_nil},'wrong number of arguments ('+Math.max(0,arguments.length-1).toString()+' for #{n})'))"
383
+ end
384
+
385
+ #
386
+ # STATEMENT
387
+ #
388
+ # Method arguments.
389
+ #
390
+ # Generate arity checks and default argument assignment.
391
+ #
392
+ def process_args(exp)
393
+ raise if @want_expression
394
+
395
+ args = []
396
+ default_values = nil
397
+
398
+ loop do
399
+ arg = exp.shift
400
+ break if arg.nil?
401
+ if arg.is_a?(Symbol)
402
+ args << arg
403
+ else
404
+ raise unless exp.empty?
405
+ default_values = arg
406
+ end
407
+ end
408
+
409
+ args.each do |arg|
410
+ arg = arg.to_s
411
+ if arg[0,1] == '*'
412
+ raise if @argument_splat
413
+ @argument_splat = @model.encode_local_variable(arg[1..-1])
414
+ # argument_splat is not an argument in the function's argument list
415
+ @local_variables.add(@argument_splat)
416
+ else
417
+ v = @model.encode_local_variable(arg)
418
+ @arguments_no_splat << v
419
+ @local_variables.add(v)
420
+ @argument_variables.add(v)
421
+ end
422
+ end
423
+
424
+ # that's not the correct arity, but we decrease it by one for each
425
+ # optional argument.
426
+ min_arity = @arguments_no_splat.size
427
+ max_arity = @arguments_no_splat.size
428
+
429
+ str = ""
430
+
431
+ #
432
+ # Generate code for the default values of arguments. We check
433
+ # whether a argument has been assigned a value, if not (== null or == undefined),
434
+ # then we assign the default value.
435
+ #
436
+ # NOTE: A check to ==null also returns true if the argument is undefined.
437
+ #
438
+ if default_values
439
+ raise unless default_values[0] == :block
440
+ default_values[1..-1].each do |dv|
441
+ min_arity -= 1
442
+ raise unless dv[0] == :lasgn
443
+ raise unless dv.size == 3
444
+ arg = @model.encode_local_variable(dv[1])
445
+ @local_variables.add(arg)
446
+ @argument_variables.add(arg)
447
+ value = dv[2]
448
+
449
+ str << "if(#{arg}==null)"
450
+ str << "#{arg}="
451
+ str << want_expression do process(value) end
452
+ str << sep()
453
+ end
454
+ end
455
+
456
+ # now as we know the min_arity, we prepend an arity check before the
457
+ # code generated above.
458
+ str2 = ""
459
+
460
+ if @argument_splat
461
+ # max_arity == infinity => no check
462
+
463
+ if min_arity == 0
464
+ # min_arity == infinity as well => we need no check
465
+ else
466
+ # +1 because we have a block argument anyway.
467
+ str2 << "if(arguments.length<#{min_arity+1})#{throw_argument_error(min_arity)}#{sep()}"
468
+ end
469
+ else
470
+ if min_arity == 0
471
+ # can't be less than 0 arguments anyway! => no check
472
+ str2 << "if(arguments.length>#{max_arity+1})#{throw_argument_error(max_arity)}#{sep()}"
473
+ else
474
+ if min_arity == max_arity
475
+ str2 << "if(arguments.length!=#{min_arity+1})#{throw_argument_error(min_arity)}#{sep()}"
476
+ else
477
+ str2 << "if(arguments.length<#{min_arity+1})#{throw_argument_error(min_arity)}#{sep()}"
478
+ str2 << "if(arguments.length>#{max_arity+1})#{throw_argument_error(max_arity)}#{sep()}"
479
+ end
480
+ end
481
+ end
482
+
483
+ # NoArgumentArityChecks disable argument arity checks
484
+ if $RUBYJS__OPTS.include?('NoArgumentArityChecks')
485
+ else
486
+ # prepend
487
+ str = str2 + str
488
+ end
489
+
490
+ if @argument_splat
491
+ # construct the code to initialize the splat argument.
492
+ # unluckily the arguments object is not an array, instead it's a
493
+ # special object that has only the length() and [] methods. There
494
+ # is no way to convert it to an array, except looping over each
495
+ # value and pushing the value into a new array.
496
+ # FIXME: variable "i"
497
+ str << "#{@argument_splat}=[]#{sep()}"
498
+ @local_variables_need_no_initialization.add(@argument_splat)
499
+ with_temporary_variable do |i|
500
+ @local_variables_need_no_initialization.add(i)
501
+ str << "for(#{i}=#{@arguments_no_splat.size+1};#{i}<arguments.length;#{i}++)#{@argument_splat}.push(arguments[#{i}])#{sep()}"
502
+ end
503
+ end
504
+
505
+ return str
506
+ end
507
+
508
+
509
+ #
510
+ # Same as generate_method_call, but where recv_exp is an sexp.
511
+ # We take care that it gets processed, and that in case of a
512
+ # numeric literal, it gets surrounded by parens.
513
+ #
514
+ def generate_method_call_receiver_exp(recv_exp, method, iter, args)
515
+ generate_method_call(receiver_exp(recv_exp), method, iter, args)
516
+ end
517
+
518
+ def receiver_exp(recv_exp)
519
+ is_num = is_numeric_literal(recv_exp)
520
+ receiver = want_expression do process(recv_exp) end
521
+ receiver = "(" + receiver + ")" if is_num
522
+ return receiver
523
+ end
524
+
525
+ #
526
+ # Generates a method call.
527
+ #
528
+ def generate_method_call(receiver, method, iter, args)
529
+
530
+ method_name = @model.encode_method(method)
531
+ @model.add_method_call(method_name)
532
+
533
+ without_result do
534
+ want_expression do
535
+ if args.nil?
536
+ # no arguments
537
+ #
538
+ # NOTE: We don't have to encode an iter of "nil" as "nil".
539
+ # Instead we save us the space and check for undefined in the
540
+ # method definition.
541
+ "#{receiver}.#{method_name}(#{iter})"
542
+ elsif args.first == :array
543
+ # one or more arguments
544
+ args_string = args[1..-1].map{|a| process(a)}.join(",")
545
+ "#{receiver}.#{method_name}(#{iter || @model.encode_nil},#{args_string})"
546
+ elsif args.first == :splat or args.first == :argscat
547
+ #
548
+ # puts(*a) # => [:fcall, :puts, [:splat, [:lvar, :a]]]]]]
549
+ #
550
+ # puts(1, *a) # => ... [:argscat, [:array, [:lit, 1]], [:lvar, :a]]
551
+ #
552
+ @model.add_method_call(__invoke = @model.encode_method('__invoke'))
553
+ "#{receiver}.#{__invoke}(#{iter || @model.encode_nil},'#{method_name}',#{ process(args) })"
554
+ else
555
+ raise
556
+ end
557
+ end
558
+ end
559
+ end
560
+
561
+ #
562
+ # Generates a super call.
563
+ #
564
+ def generate_super_call(iter, args)
565
+ @model.add_method_call(@method_name)
566
+
567
+ args_str = without_result do
568
+ want_expression do
569
+ if args.nil?
570
+ # no arguments
571
+ #
572
+ # NOTE: We don't have to encode an iter of "nil" as "nil".
573
+ # Instead we save us the space and check for undefined in the
574
+ # method definition.
575
+ "[]"
576
+ else
577
+ process(args)
578
+ end
579
+ end
580
+ end
581
+
582
+ sc = @model.encode_globalattr('supercall')
583
+ "#{sc}(#{@model.encode_self},'#{@method_name}',#{iter || @model.encode_nil},#{args_str})"
584
+ end
585
+
586
+ #
587
+ # EXPRESSION
588
+ #
589
+ # Super call.
590
+ #
591
+ def process_super(exp)
592
+ args = exp.shift
593
+ generate_super_call(get_iter(), args)
594
+ end
595
+
596
+ #
597
+ # EXPRESSION
598
+ #
599
+ # Super call without arguments "super"
600
+ #
601
+ # We need to introduce a new variable instead of
602
+ # using "arguments" because "arguments" is not
603
+ # available when called within an iterator, because
604
+ # an iterator introduces a new function.
605
+ #
606
+ def process_zsuper(exp)
607
+ @model.add_method_call(@method_name)
608
+ sc = @model.encode_globalattr('zsupercall')
609
+ "#{sc}(#{@model.encode_self},'#{@method_name}',#{arguments_name()})"
610
+ end
611
+
612
+ #
613
+ # EXPRESSION
614
+ #
615
+ # Method call without receiver
616
+ #
617
+ def process_fcall(exp)
618
+ method = exp.shift
619
+ args = exp.shift
620
+
621
+ str = without_result do
622
+ generate_method_call(@model.encode_self, method, get_iter(), args)
623
+ end
624
+
625
+ resultify(str)
626
+ end
627
+
628
+ #
629
+ # EXPRESSION
630
+ #
631
+ # Attribute assignment: receiver.attr=(value)
632
+ #
633
+ # Same as a method call!
634
+ #
635
+ def process_attrasgn(exp)
636
+ process_call(exp)
637
+ end
638
+
639
+ #
640
+ # The ||= operator.
641
+ #
642
+ # For example:
643
+ #
644
+ # a = a || "hallo"
645
+ #
646
+ # [:lasgn, :a, [:or, [:lvar, :a], [:str, "hallo"]]],
647
+ #
648
+ # a ||= "hallo"
649
+ #
650
+ # [:op_asgn_or, [:lvar, :a], [:lasgn, :a, [:str, "hallo"]]]]]]
651
+ #
652
+ # We rewrite the one to the other.
653
+ #
654
+ def process_op_asgn_or(exp)
655
+ ref = exp.shift
656
+ asgn = exp.shift
657
+ asgn[2] = [:or, ref, asgn[2]]
658
+ process(asgn)
659
+ end
660
+
661
+ #
662
+ # EXPRESSION
663
+ #
664
+ # Method call with receiver
665
+ #
666
+ def process_call(exp)
667
+ receiver = exp.shift
668
+ method = exp.shift
669
+ args = exp.shift
670
+
671
+ iter = get_iter()
672
+
673
+ #
674
+ # RubyJS::inline("str")
675
+ #
676
+ # A different form of Javascript inline. We have this form
677
+ # because ``-style Javascript-inline has a different esacping
678
+ # of \. Sometimes we want no escaping, so
679
+ #
680
+ # RubyJS::inline %q(...) could be used.
681
+ #
682
+ if receiver == [:const, :RubyJS] and method == :inline and
683
+ args[0] == :array and args.size == 2 and args[1].size == 2 and args[1][0] == :str and iter.nil?
684
+ raise if @want_expression
685
+ return @model.interpolate(args[1][1])
686
+ end
687
+
688
+ str =
689
+ if $RUBYJS__OPTS.include?('OptimizeArithOps') and %w(> >= < <= + - * /).include?(method.to_s) and
690
+ iter.nil? and args and args[0] == :array and args.size == 2
691
+ without_result do
692
+ want_expression do
693
+ "(#{process(receiver)})#{method.to_s}(#{process(args[1])})"
694
+ end
695
+ end
696
+ else
697
+ without_result do
698
+ generate_method_call_receiver_exp(receiver, method, iter, args)
699
+ end
700
+ end
701
+ resultify(str)
702
+ end
703
+
704
+ #
705
+ # EXPRESSION
706
+ #
707
+ # Virtual call. Either a method call or a variable, determined at
708
+ # runtime.
709
+ #
710
+ # Ruby cannot distinguish a local variable and a method called without
711
+ # parentheses and without receiver at parse time. Example:
712
+ #
713
+ # def my_method
714
+ # method_or_not.inspect
715
+ # end
716
+ #
717
+ # If a local variable of that name exists, then it is used as
718
+ # receiver. Otherwise it is seen as a method and is called.
719
+ #
720
+ # NOTE: A vcall can never have an iterator! Because if an iterator is
721
+ # specified it is automatically no variable and as such a fcall or
722
+ # call is generated.
723
+ #
724
+ # NOTE: As RubyJS disallows to use eval(), a vcall can never be a
725
+ # local variable (FIXME: to be prooved).
726
+ #
727
+ def process_vcall(exp)
728
+ method = exp.shift
729
+
730
+ resultify(generate_method_call(@model.encode_self, method, nil, nil))
731
+ end
732
+
733
+ #
734
+ # EXPRESSION
735
+ #
736
+ # Q: Always call with "nil" if no argument is given?
737
+ # A: No, because then we could not distinguish between
738
+ # no arguments and "nil" as argument.
739
+ #
740
+ def process_yield(exp)
741
+ value = exp.shift
742
+
743
+ str = without_result do
744
+ want_expression do
745
+ block_name() + "(" + (value ? process(value) : '') + ")"
746
+ end
747
+ end
748
+ resultify(str)
749
+ end
750
+
751
+ #
752
+ # Constant lookup
753
+ # ===============
754
+ #
755
+ # Constant lookup in RubyJS is performed statically. This is possible
756
+ # because the whole class hierarchy is available at compile-time.
757
+
758
+ #
759
+ # EXPRESSION
760
+ #
761
+ def process_const(exp)
762
+ resultify(@model.lookup_constant(constify(exp, :const)))
763
+ end
764
+
765
+ #
766
+ # helper methods
767
+ #
768
+
769
+ def constify(exp, type)
770
+ exp.unshift(type)
771
+ const_rec(exp).join("::")
772
+ end
773
+
774
+ def const_rec(exp)
775
+ case exp.shift
776
+ when :const
777
+ return [exp.shift]
778
+ when :colon2
779
+ const_rec(exp.shift) + [exp.shift]
780
+ when :colon3
781
+ [nil, exp.shift]
782
+ else
783
+ raise
784
+ end
785
+ end
786
+
787
+ #
788
+ # EXPRESSION
789
+ #
790
+ # A::B # => [:colon2, [:const, :A], :B]
791
+ #
792
+ def process_colon2(exp)
793
+ resultify(@model.lookup_constant(constify(exp, :colon2)))
794
+ end
795
+
796
+ #
797
+ # EXPRESSION
798
+ #
799
+ # ::A # => [:colon3, :A]
800
+ #
801
+ def process_colon3(exp)
802
+ resultify(@model.lookup_constant(constify(exp, :colon3)))
803
+ end
804
+
805
+ #
806
+ # EXPRESSION
807
+ #
808
+ # Implements the "&&" operator, which has short circuit behaviour.
809
+ #
810
+ # a && b
811
+ #
812
+ # is equivalent in Ruby to
813
+ #
814
+ # if a then b else a end
815
+ #
816
+ def process_and(exp)
817
+ a = exp.shift
818
+ b = exp.shift
819
+
820
+ res = without_result do
821
+ want_expression do
822
+ with_temporary_variable do |tmp|
823
+ @local_variables_need_no_initialization.add(tmp)
824
+ "(#{tmp}=#{process(a)}, (#{tmp}!==false&&#{tmp}!==nil) ? (#{process(b)}) : #{tmp})"
825
+ end
826
+ end
827
+ end
828
+
829
+ return resultify(res)
830
+ end
831
+
832
+ #
833
+ # EXPRESSION
834
+ #
835
+ # Implements the "||" operator, which has short circuit behaviour.
836
+ #
837
+ # a || b
838
+ #
839
+ # is equivalent in Ruby to
840
+ #
841
+ # if a then a else b end
842
+ #
843
+ def process_or(exp)
844
+ a = exp.shift
845
+ b = exp.shift
846
+
847
+ res = without_result do
848
+ want_expression do
849
+ with_temporary_variable do |tmp|
850
+ @local_variables_need_no_initialization.add(tmp)
851
+ "(#{tmp}=#{process(a)}, (#{tmp}!==false&&#{tmp}!==nil) ? #{tmp} : (#{process(b)}))"
852
+ end
853
+ end
854
+ end
855
+
856
+ return resultify(res)
857
+ end
858
+
859
+ #
860
+ # EXPRESSION
861
+ #
862
+ def process_not(exp)
863
+ a = exp.shift
864
+
865
+ without_result do
866
+ want_expression do
867
+ a = conditionalize(a, true)
868
+ end
869
+ end
870
+
871
+ return resultify(a)
872
+ end
873
+
874
+ #
875
+ # STATEMENT/EXPRESSION
876
+ #
877
+ # unless is converted by the Ruby parser into an "if":
878
+ #
879
+ # unless X
880
+ # block
881
+ # end
882
+ #
883
+ # =>
884
+ #
885
+ # if X
886
+ # else
887
+ # block
888
+ # end
889
+ #
890
+ def process_if(exp)
891
+ cond = exp.shift
892
+ _then = exp.shift
893
+ _else = exp.shift
894
+
895
+ _then_processed = if _then
896
+ process(_then)
897
+ else
898
+ nil
899
+ end
900
+
901
+ _else_processed = if _else
902
+ process(_else)
903
+ else
904
+ nil
905
+ end
906
+
907
+ negate = false
908
+
909
+ if _then_processed.nil?
910
+ # no "then" block as in
911
+ #
912
+ # if X
913
+ # else
914
+ # block
915
+ # end
916
+ #
917
+ # => convert into
918
+ #
919
+ # if not X
920
+ # block
921
+ # end
922
+ #
923
+ # by swapping then and else blocks
924
+ # and negating the condition
925
+
926
+ _then_processed = _else_processed
927
+ _else_processed = nil
928
+ negate = true
929
+ end
930
+
931
+ cond_processed = without_result do
932
+ conditionalize(cond, negate)
933
+ end
934
+
935
+ str = ""
936
+
937
+ if @want_expression
938
+ _then_processed ||= resultify(@model.encode_nil)
939
+ _else_processes ||= resultify(@model.encode_nil)
940
+ str << "(#{cond_processed}?#{_then_processed}:#{_else_processed})"
941
+ else
942
+ str << "if(#{cond_processed}){"
943
+ str << (_then_processed || (@want_result ? resultify(@model.encode_nil) : ''))
944
+ str << "}"
945
+ if @want_result
946
+ _else_processed ||= resultify(@model.encode_nil)
947
+ end
948
+ if _else_processed
949
+ str << "else{"
950
+ str << _else_processed
951
+ str << "}"
952
+ end
953
+ end
954
+
955
+ return str
956
+ end
957
+
958
+ #
959
+ # STATEMENT/EXPRESSION
960
+ #
961
+ # case obj
962
+ # when c1
963
+ # block1
964
+ # when c2, c3
965
+ # block23
966
+ # else
967
+ # blockelse
968
+ # end
969
+ #
970
+ # [:case,
971
+ # obj,
972
+ # [:when, [:array, c1], block1],
973
+ # [:when, [:array, c2, c3], block23],
974
+ # blockelse or nil
975
+ # ]
976
+ #
977
+ # We are transforming the "case" into "if".
978
+ #
979
+ # _tmp = obj;
980
+ # if c1 === _tmp
981
+ # block1
982
+ # else
983
+ # if c2 === _tmp or c3 === _tmp then
984
+ # block23
985
+ # else
986
+ # blockelse
987
+ # end
988
+ # end
989
+ #
990
+ def process_case(exp)
991
+ obj = exp.shift
992
+
993
+ with_temporary_variable do |tmp|
994
+ @local_variables_need_no_initialization.add(tmp)
995
+ asgn = want_expression do
996
+ "#{tmp}=#{process(obj)}"
997
+ end
998
+
999
+ new_exp = current_exp = []
1000
+
1001
+ while not exp.empty?
1002
+ _when = exp.shift
1003
+ if _when.nil? or _when.first != :when
1004
+ # last element
1005
+ raise exp.inspect unless exp.empty?
1006
+ current_exp << _when
1007
+ else
1008
+ conds = _when[1]
1009
+ block = _when[2]
1010
+ raise unless conds.first == :array
1011
+
1012
+ cond = multi_or(conds[1..-1].map {|c| [:call, c, :===, [:array, [:special_inline_js_value, tmp]]] })
1013
+
1014
+ my_exp = [:if, cond, block]
1015
+
1016
+ current_exp << my_exp
1017
+ current_exp = my_exp
1018
+ end
1019
+ end
1020
+
1021
+ if_code = process(new_exp.first)
1022
+
1023
+ code =
1024
+ if @want_expression
1025
+ "(#{asgn}, #{if_code})"
1026
+ else
1027
+ "#{asgn}#{sep()}#{if_code}"
1028
+ end
1029
+
1030
+ code
1031
+ end
1032
+ end
1033
+
1034
+ def multi_or(exprs)
1035
+ case exprs.size
1036
+ when 0
1037
+ raise
1038
+ when 1
1039
+ # multi_or([1]) => 1
1040
+ exprs[0]
1041
+ when 2
1042
+ # multi_or([1, 2]) => [:or, 1, 2]
1043
+ [:or, exprs[0], exprs[1]]
1044
+ else
1045
+ # multi_or([1, 2, 3]) => [:or, 1, multi_or([2,3])] == [:or, 1, [:or, 2, 3]]
1046
+ [:or, exprs[0], multi_or(exprs[1..-1])]
1047
+ end
1048
+ end
1049
+
1050
+ #
1051
+ # STATEMENT
1052
+ #
1053
+ def process_begin(exp)
1054
+ raise if @want_expression
1055
+ block = exp.shift
1056
+ process(block)
1057
+ end
1058
+
1059
+ #
1060
+ # STATEMENT
1061
+ #
1062
+ def process_rescue(exp)
1063
+ raise if @want_expression
1064
+ block = exp.shift
1065
+ handler = exp.shift
1066
+ else_handler = exp.shift
1067
+ raise unless handler.first == :resbody
1068
+
1069
+ x = exception_name()
1070
+
1071
+ code = [:block, block]
1072
+ code << else_handler if else_handler
1073
+
1074
+ str = ""
1075
+ str << "try{#{process(code)}}"
1076
+ str << "catch(#{x}){"
1077
+
1078
+ # check whether it's an iterator break. if yes, pass it on
1079
+ iter_jump = @model.encode_globalattr("iter_jump")
1080
+ str << "if(#{x} instanceof #{iter_jump})throw(#{x})#{sep()}"
1081
+ str << "#{process(handler)}"
1082
+ str << "}"
1083
+
1084
+ str
1085
+ end
1086
+
1087
+ #
1088
+ # STATEMENT
1089
+ #
1090
+ def process_resbody(exp)
1091
+ raise if @want_expression
1092
+ condition = exp.shift
1093
+ if_body = exp.shift
1094
+ else_body = exp.shift
1095
+
1096
+ x = exception_name()
1097
+
1098
+ # if only "rescue" is used (and not e.g. "rescue Exception")
1099
+ condition ||= [:array, [:const, :StandardError]]
1100
+ raise unless condition.first == :array
1101
+
1102
+ # if this is the last resbody (else_body is nil) then
1103
+ # the exception couldn't be catched and we rethrow it!
1104
+ else_body ||= [:special_inline_js_value, "throw(#{x})"]
1105
+
1106
+ # build condition expression
1107
+ cond =
1108
+ multi_or(condition[1..-1].map do |c|
1109
+ [:call, c, :===, [:array, [:special_inline_js_value, x]]]
1110
+ end)
1111
+
1112
+ process([:if, cond, if_body, else_body])
1113
+ end
1114
+
1115
+ #
1116
+ # STATEMENT
1117
+ #
1118
+ def process_ensure(exp)
1119
+ raise if @want_expression
1120
+ try_body = process(exp.shift)
1121
+ ensure_body = without_result { process(exp.shift) }
1122
+
1123
+ "try{#{try_body}}finally{#{ensure_body}}"
1124
+ end
1125
+
1126
+ #
1127
+ # STATEMENT
1128
+ #
1129
+ def process_return(exp)
1130
+ return_value =
1131
+ if param = exp.shift
1132
+ want_expression do
1133
+ without_result do
1134
+ process(param)
1135
+ end
1136
+ end
1137
+ else
1138
+ @model.encode_nil
1139
+ end
1140
+ if @block_whileloop_stack.last == :iter
1141
+ # return from within an iterator
1142
+ throw_iter_jump(return_value, unique_method_scope())
1143
+ elsif @want_expression
1144
+ throw_iter_jump(return_value)
1145
+ else
1146
+ "return #{return_value}"
1147
+ end
1148
+ end
1149
+
1150
+ #
1151
+ # STATEMENT
1152
+ #
1153
+ def process_while(exp)
1154
+ raise if @want_expression
1155
+
1156
+ cond = exp.shift
1157
+ block = exp.shift
1158
+ flag = exp.shift
1159
+ raise unless flag == true # FIXME: document
1160
+
1161
+ str = without_result do
1162
+ c = conditionalize(cond)
1163
+ @block_whileloop_stack.push(:while)
1164
+ b = process(block)
1165
+ @block_whileloop_stack.pop || raise
1166
+
1167
+ "while(#{c}){#{b}}"
1168
+ end
1169
+
1170
+ if @want_result
1171
+ str << sep() + resultify(@model.encode_nil) + sep()
1172
+ end
1173
+
1174
+ return str
1175
+ end
1176
+
1177
+ #
1178
+ # STATEMENT
1179
+ #
1180
+ def process_break(exp)
1181
+ case @block_whileloop_stack.last
1182
+ when :while
1183
+ raise "break with arguments inside while not yet supported" unless exp.empty?
1184
+ raise if @want_expression
1185
+ "break"
1186
+ when :iter
1187
+ return_value =
1188
+ if param = exp.shift
1189
+ without_result do
1190
+ process(param)
1191
+ end
1192
+ else
1193
+ @model.encode_nil
1194
+ end
1195
+ throw_iter_jump(return_value)
1196
+ when nil
1197
+ raise("break not in loop/block scope")
1198
+ else
1199
+ raise "FATAL"
1200
+ end
1201
+ end
1202
+
1203
+ def throw_iter_jump(return_value, scope=nil)
1204
+ @iterators_used = true # used to produce catch code in this method
1205
+ iter_jump = @model.encode_globalattr('iter_jump')
1206
+ scope ||= "null"
1207
+ th =
1208
+ if @want_expression
1209
+ @model.encode_globalattr("throw_expr")
1210
+ else
1211
+ "throw"
1212
+ end
1213
+
1214
+ "#{th}(new #{iter_jump}(#{return_value},#{scope}))"
1215
+ end
1216
+
1217
+ #
1218
+ # STATEMENT
1219
+ #
1220
+ def process_next(exp)
1221
+ case @block_whileloop_stack.last
1222
+ when :while
1223
+ raise "next with arguments inside while not yet supported" unless exp.empty?
1224
+ raise if @want_expression
1225
+ "continue"
1226
+ when :iter
1227
+ # next inside a code-block generates a "return"
1228
+ raise if @want_expression
1229
+
1230
+ return_value =
1231
+ if param = exp.shift
1232
+ want_expression do
1233
+ without_result do
1234
+ process(param)
1235
+ end
1236
+ end
1237
+ else
1238
+ @model.encode_nil
1239
+ end
1240
+
1241
+ "return #{return_value}"
1242
+ when nil
1243
+ raise("next not in loop/block scope")
1244
+ else
1245
+ raise "FATAL"
1246
+ end
1247
+ end
1248
+
1249
+ #
1250
+ # UNDEFINED
1251
+ #
1252
+ # Backtick strings: `inline javascript`
1253
+ #
1254
+ # We use them for inline Javascript.
1255
+ #
1256
+ # It's unclear whether it's a STATEMENT or EXPRESSION.
1257
+ # It depends on the Javascript.
1258
+ #
1259
+ # NOTE: You have to take care to return a value yourself
1260
+ # in case of @want_result, i.e. there is no automatic handling
1261
+ # thereof.
1262
+ #
1263
+ def process_xstr(exp)
1264
+ str = exp.shift
1265
+ @model.interpolate(str)
1266
+ end
1267
+
1268
+ #
1269
+ # EXPRESSION
1270
+ #
1271
+ # String interpolation
1272
+ #
1273
+ def process_dstr(exp)
1274
+ pre_str = exp.shift
1275
+ res = without_result do
1276
+ want_expression do
1277
+ # NOTE:
1278
+ # We use +to_s+, and the + operator to
1279
+ # build the interpolated string.
1280
+ @model.add_method_call(to_s = @model.encode_method("to_s"))
1281
+ "(" + ([pre_str.inspect] + exp.map {|e| "(" + process(e) + ").#{to_s}()"}).join(" + ") + ")"
1282
+ end
1283
+ end
1284
+ exp.clear
1285
+ resultify(res)
1286
+ end
1287
+
1288
+ #
1289
+ # EXPRESSION
1290
+ #
1291
+ # Process literals
1292
+ #
1293
+ def process_lit(exp)
1294
+ lit = exp.shift
1295
+ res = case lit
1296
+ when Fixnum, Bignum, Float
1297
+ lit.to_s
1298
+ when String
1299
+ lit.inspect
1300
+ when Symbol
1301
+ lit.to_s.inspect
1302
+ when Regexp
1303
+ lit.inspect
1304
+ when Range
1305
+ range = @model.lookup_constant('::Range')
1306
+ @model.add_method_call(m = @model.encode_method("new"))
1307
+ "#{range}.#{m}(#{@model.encode_nil},#{lit.first},#{lit.last},#{lit.exclude_end?})"
1308
+ else
1309
+ raise
1310
+ end
1311
+
1312
+ resultify(res)
1313
+ end
1314
+
1315
+ def is_numeric_literal(exp)
1316
+ type = exp[0]
1317
+ return false if exp[0] != :lit
1318
+ case exp[1]
1319
+ when Fixnum, Bignum, Float
1320
+ true
1321
+ else
1322
+ false
1323
+ end
1324
+ end
1325
+
1326
+ #
1327
+ # EXPRESSION
1328
+ #
1329
+ # Process string literal
1330
+ #
1331
+ def process_str(exp)
1332
+ str = exp.shift
1333
+ resultify(str.inspect)
1334
+ end
1335
+
1336
+ #
1337
+ # EXPRESSION
1338
+ #
1339
+ def process_true(exp)
1340
+ resultify("true")
1341
+ end
1342
+
1343
+ #
1344
+ # EXPRESSION
1345
+ #
1346
+ def process_false(exp)
1347
+ resultify("false")
1348
+ end
1349
+
1350
+ #
1351
+ # EXPRESSION
1352
+ #
1353
+ def process_nil(exp)
1354
+ resultify(@model.encode_nil)
1355
+ end
1356
+
1357
+ #
1358
+ # EXPRESSION
1359
+ #
1360
+ def process_self(exp)
1361
+ resultify(@model.encode_self)
1362
+ end
1363
+
1364
+ #
1365
+ # EXPRESSION
1366
+ #
1367
+ def process_splat(exp)
1368
+ value = exp.shift
1369
+ to_splat = @model.encode_globalattr('to_splat')
1370
+ str = without_result do
1371
+ want_expression do
1372
+ "#{to_splat}(#{ process(value) })"
1373
+ end
1374
+ end
1375
+ resultify(str)
1376
+ end
1377
+
1378
+ #
1379
+ # EXPRESSION
1380
+ #
1381
+ def process_argscat(exp)
1382
+ prefix = exp.shift
1383
+ splat = exp.shift
1384
+ raise unless prefix.first == :array
1385
+ to_splat = @model.encode_globalattr('to_splat')
1386
+ str = without_result do
1387
+ want_expression do
1388
+ "#{process(prefix)}.concat(#{to_splat}(#{ process(splat) }))"
1389
+ end
1390
+ end
1391
+ resultify(str)
1392
+ end
1393
+
1394
+ #
1395
+ # EXPRESSION
1396
+ #
1397
+ # Array literal
1398
+ #
1399
+ def process_array(exp)
1400
+ str = without_result do
1401
+ want_expression do
1402
+ "[" + exp.map{|e| process(e)}.compact.join(",") + "]"
1403
+ end
1404
+ end
1405
+ exp.clear
1406
+ resultify(str)
1407
+ end
1408
+
1409
+ #
1410
+ # EXPRESSION
1411
+ #
1412
+ # We box the hash and use our own implementation, as Javascript's
1413
+ # associative arrays are very different to Ruby hashes.
1414
+ #
1415
+ def process_hash(exp)
1416
+ kv_list = exp
1417
+ raise if kv_list.length % 2 != 0
1418
+
1419
+ args =
1420
+ without_result do
1421
+ want_expression do
1422
+ kv_list.map {|i| process(i)}.join(",")
1423
+ end
1424
+ end
1425
+
1426
+ str = @model.lookup_constant('::Hash') + "."
1427
+ if kv_list.empty?
1428
+ # empty Hash
1429
+ @model.add_method_call(m = @model.encode_method("new"))
1430
+ str << "#{m}()"
1431
+ else
1432
+ @model.add_method_call(m = @model.encode_method("new_from_key_value_list"))
1433
+ str << "#{m}(#{@model.encode_nil},#{args})"
1434
+ end
1435
+
1436
+ exp.clear
1437
+
1438
+ return str
1439
+ end
1440
+
1441
+ #
1442
+ # EXPRESSION
1443
+ #
1444
+ # Empty array.
1445
+ #
1446
+ def process_zarray(exp)
1447
+ resultify("[]")
1448
+ end
1449
+
1450
+
1451
+ #
1452
+ # EXPRESSION
1453
+ #
1454
+ # Local variable assignment
1455
+ #
1456
+ # We have to write down all local variables, because they have to be
1457
+ # declared at the top of the method (otherwise they are seen as
1458
+ # "global" variables :)
1459
+ #
1460
+ def process_lasgn(exp)
1461
+ lvar = exp.shift
1462
+ value = exp.shift
1463
+
1464
+ lvar_name = @model.encode_local_variable(lvar)
1465
+ @local_variables.add(lvar_name)
1466
+ # local variables need not to be initialized, because they
1467
+ # have to be asigned to before usage, otherwise they would
1468
+ # not be local variables but methods instead.
1469
+ @local_variables_need_no_initialization.add(lvar_name)
1470
+
1471
+ str = without_result do
1472
+ want_expression do
1473
+ "#{lvar_name}=#{process(value)}"
1474
+ end
1475
+ end
1476
+
1477
+ resultify(str)
1478
+ end
1479
+
1480
+ #
1481
+ # EXPRESSION
1482
+ #
1483
+ # Local variable lookup
1484
+ #
1485
+ def process_lvar(exp)
1486
+ lvar = exp.shift
1487
+
1488
+ lvar_name = @model.encode_local_variable(lvar)
1489
+ raise "variable not available" unless @local_variables.include?(lvar_name)
1490
+
1491
+ resultify("#{lvar_name}")
1492
+ end
1493
+
1494
+ #
1495
+ # EXPRESSION
1496
+ #
1497
+ # Regular expression lookup $1 .. $9
1498
+ #
1499
+ def process_nth_ref(exp)
1500
+ n = exp.shift
1501
+ raise if n < 1 or n > 9
1502
+ resultify("(RegExp.$#{n} || #{@model.encode_nil})")
1503
+ end
1504
+
1505
+ #
1506
+ # EXPRESSION
1507
+ #
1508
+ # Global variable lookup
1509
+ #
1510
+ def process_gvar(exp)
1511
+ gvar = exp.shift
1512
+
1513
+ res =
1514
+ case gvar.to_s
1515
+ when "$!"
1516
+ # this is a special variable which holds the current exception
1517
+ exception_name()
1518
+ else
1519
+ gvar_name = @model.encode_global_variable(gvar)
1520
+ "(typeof(#{gvar_name})=='undefined'?#{@model.encode_nil}:#{gvar_name})"
1521
+ end
1522
+
1523
+ resultify(res)
1524
+ end
1525
+
1526
+ #
1527
+ # EXPRESSION
1528
+ #
1529
+ # Global variable assignment
1530
+ #
1531
+ def process_gasgn(exp)
1532
+ gvar = exp.shift
1533
+ value = exp.shift
1534
+
1535
+ gvar_name = @model.encode_global_variable(gvar)
1536
+
1537
+ str = without_result do
1538
+ want_expression do
1539
+ "#{gvar_name}=#{process(value)}"
1540
+ end
1541
+ end
1542
+
1543
+ resultify(str)
1544
+ end
1545
+
1546
+ #
1547
+ # EXPRESSION
1548
+ #
1549
+ # Dynamic variable lookup
1550
+ #
1551
+ def process_dvar(exp)
1552
+ dvar = exp.shift
1553
+ dvar_name = @model.encode_local_variable(dvar)
1554
+ raise "dynamic variable not available" unless @current_iter_dvars.include?(dvar_name)
1555
+
1556
+ resultify("#{dvar_name}")
1557
+ end
1558
+
1559
+ #
1560
+ # EXPRESSION
1561
+ #
1562
+ # Dynamic variable assignment
1563
+ #
1564
+ def process_dasgn(exp)
1565
+ dvar = exp.shift
1566
+ value = exp.shift
1567
+ dvar_name = @model.encode_local_variable(dvar)
1568
+ raise "dynamic variable not available" unless @current_iter_dvars.include?(dvar_name)
1569
+
1570
+ str = without_result do
1571
+ want_expression do
1572
+ "#{dvar_name}=#{process(value)}"
1573
+ end
1574
+ end
1575
+
1576
+ resultify(str)
1577
+ end
1578
+
1579
+ #
1580
+ # EXPRESSION
1581
+ #
1582
+ # Dynamic variable declaration and assignment
1583
+ #
1584
+ def process_dasgn_curr(exp)
1585
+ dvar = exp.shift
1586
+ value = exp.shift
1587
+ dvar_name = @model.encode_local_variable(dvar)
1588
+
1589
+ @current_iter_dvars.add(dvar_name)
1590
+
1591
+ str = without_result do
1592
+ want_expression do
1593
+ if value
1594
+ "#{dvar_name}=#{process(value)}"
1595
+ else
1596
+ # this should never happen, see process_iter()
1597
+ raise "no declarations possible"
1598
+ end
1599
+ end
1600
+ end
1601
+
1602
+ resultify(str)
1603
+ end
1604
+
1605
+ #
1606
+ # EXPRESSION
1607
+ #
1608
+ # Instance variable lookup
1609
+ #
1610
+ def process_ivar(exp)
1611
+ ivar = exp.shift
1612
+ ivar_name = @model.encode_instance_variable(ivar)
1613
+ @read_instance_variables.add(ivar_name)
1614
+ resultify("#{@model.encode_self}.#{ivar_name}")
1615
+ end
1616
+
1617
+ def process_svalue
1618
+ raise
1619
+ end
1620
+
1621
+ def process_to_ary(exp)
1622
+ value = exp.shift
1623
+ str = without_result do
1624
+ want_expression do
1625
+ case value.first
1626
+ when :lit
1627
+ "[" + process(value) + "]"
1628
+ when :array, :zarray
1629
+ process(value)
1630
+ when :special_to_ary
1631
+ # this is used inside of process_iter to build the multiple
1632
+ # assignment.
1633
+ value[1]
1634
+ else
1635
+ generate_method_call_receiver_exp(value, "to_ary", nil, nil)
1636
+ end
1637
+ end
1638
+ end
1639
+ resultify(str)
1640
+ end
1641
+
1642
+ #
1643
+ # EXPRESSION
1644
+ #
1645
+ # This is used to insert a pure inline JS string into the
1646
+ # code. It is used for example in process_masgn.
1647
+ #
1648
+ # It is not part of the ParseTree returned node types!
1649
+ #
1650
+ def process_special_inline_js_value(exp)
1651
+ return exp.shift
1652
+ end
1653
+
1654
+ #
1655
+ # EXPRESSION
1656
+ #
1657
+ # Multiple assignment
1658
+ #
1659
+ # Simple case:
1660
+ #
1661
+ # a, b = 1, 2
1662
+ #
1663
+ # [:masgn,
1664
+ # [:array, [:lasgn, :a], [:lasgn, :b]],
1665
+ # [:array, [:lit, 1], [:lit, 2]]]]]]
1666
+ #
1667
+ # Case with splat argument:
1668
+ #
1669
+ # a, *b = 1, 2, 3
1670
+ #
1671
+ # [:masgn,
1672
+ # [:array, [:lasgn, :a]],
1673
+ # [:lasgn, :b],
1674
+ # [:array, [:lit, 1], [:lit, 2], [:lit, 3]]]]]]
1675
+ #
1676
+ # Another case:
1677
+ #
1678
+ # a, b = 1
1679
+ #
1680
+ # [:masgn,
1681
+ # [:array, [:lasgn, :a], [:lasgn, :b]],
1682
+ # [:to_ary, [:lit, 1]]]
1683
+ #
1684
+ # We actually implement multiple assignment using a
1685
+ # temporary array. Example:
1686
+ #
1687
+ # a, b = b, a
1688
+ #
1689
+ # leads to the following javascript code
1690
+ #
1691
+ # (_t = [b,a],
1692
+ # a = _t[0] == null ? nil : _t[0],
1693
+ # b = _t[1] == null ? nil : _t[1])
1694
+ #
1695
+ # When a splat argument is given, there's just an
1696
+ # additional assignment which takes the rest of the
1697
+ # array.
1698
+ #
1699
+ def process_masgn(exp)
1700
+ lhs = exp.shift
1701
+ rhs = exp.pop
1702
+ splat = exp.shift
1703
+
1704
+ raise unless lhs.first == :array
1705
+ raise unless rhs.first == :array or rhs.first == :to_ary
1706
+
1707
+ want_expression do
1708
+ with_temporary_variable do |tmp|
1709
+ assgn = []
1710
+ without_result do
1711
+ assgn << "#{tmp}=#{process(rhs)}"
1712
+
1713
+ # lhs[0] == :array -> skip it
1714
+ lhs[1..-1].each_with_index do |assignment, i| # for example where assignment == [:lasgn, :a]
1715
+ assignment << s(:special_inline_js_value, "#{tmp}[#{i}]==null?#{@model.encode_nil}:#{tmp}[#{i}]")
1716
+ assgn << process(assignment)
1717
+ end
1718
+
1719
+ if splat
1720
+ # splat is for example [:lasgn, :a]
1721
+ splat << s(:special_inline_js_value, "#{tmp}.slice(#{lhs.size-1})")
1722
+ assgn << process(splat)
1723
+ end
1724
+ end
1725
+
1726
+ # return value of the expression is the array
1727
+ assgn << resultify("#{tmp}")
1728
+
1729
+ "(" + assgn.join(",") + ")"
1730
+ end
1731
+ end
1732
+ end
1733
+
1734
+ #
1735
+ # EXPRESSION
1736
+ #
1737
+ # Instance variable assignment
1738
+ #
1739
+ def process_iasgn(exp)
1740
+ ivar = exp.shift
1741
+ value = exp.shift
1742
+ ivar_name = @model.encode_instance_variable(ivar)
1743
+
1744
+ str = without_result do
1745
+ want_expression do
1746
+ "#{@model.encode_self}.#{ivar_name}=#{process(value)}"
1747
+ end
1748
+ end
1749
+
1750
+ resultify(str)
1751
+ end
1752
+
1753
+ #
1754
+ # EXPRESSION (doesn't generate any code directly!)
1755
+ #
1756
+ # Iterator block.
1757
+ #
1758
+ # We process as follows:
1759
+ #
1760
+ # 1. Generate code for the arguments of the block (multiple
1761
+ # assignment etc.)
1762
+ #
1763
+ # 2. Generate code for the code block.
1764
+ #
1765
+ # 3. In 1 and 2 we also write down all dynamic variable assignments.
1766
+ # ("dasgn_curr" which introduces a new scope)
1767
+ #
1768
+ # 4. Generate code for variable declarations.
1769
+ #
1770
+ # TODO: assign arity to function object!
1771
+ #
1772
+ def process_iter(exp)
1773
+ call = exp.shift # the method call to which the iterator is passed
1774
+ params = exp.shift # arguments of the block
1775
+ code = exp.shift # the code block
1776
+
1777
+ old_iter_dvars = @current_iter_dvars
1778
+ @current_iter_dvars = Set.new
1779
+ @block_whileloop_stack.push(:iter)
1780
+
1781
+ # Get an argument name for the iterator function signature.
1782
+ arg_name = @model.encode_fresh_local_variable()
1783
+
1784
+ fun_str = ""
1785
+ asgn_str = ""
1786
+
1787
+ if params.nil?
1788
+ # Case 1: {}: Any number of arguments may be passed
1789
+ arity = -1
1790
+ fun_str << "function(){"
1791
+ elsif params == :zero_arguments
1792
+ # Case 2: {||}: Zero arguments
1793
+ arity = 0
1794
+ fun_str << "function(){"
1795
+ elsif params.first == :masgn
1796
+ # Case 3: {|i,j|}: Multiple arguments (multiple assignment)
1797
+ arity = nil # |arity| >= 2
1798
+ fun_str << "function(#{arg_name}){"
1799
+
1800
+ # TODO: remove masgn_iter and instead put the corresponding logic
1801
+ # into yield!
1802
+ masgn_iter = @model.encode_globalattr("masgn_iter")
1803
+ params << [:to_ary, [:special_to_ary, "#{masgn_iter}(#{arg_name})"]]
1804
+ want_expression(false) do
1805
+ without_result do
1806
+ asgn_str << process(params)
1807
+ end
1808
+ end
1809
+ else
1810
+ # Case 4: {|i|}: Single argument
1811
+ # TODO: warning if not exactly one argument is passed
1812
+ arity = 1
1813
+ fun_str << "function(#{arg_name}){"
1814
+
1815
+ # we convert a single argument into a multiple assignment clause
1816
+ # with one argument.
1817
+
1818
+ sasgn_iter = @model.encode_globalattr("sasgn_iter")
1819
+ params << [:special_inline_js_value, "#{arg_name}==null?#{@model.encode_nil}:#{arg_name}"]
1820
+
1821
+ want_expression(false) do
1822
+ without_result do
1823
+ asgn_str << process(params)
1824
+ end
1825
+ end
1826
+ end
1827
+
1828
+ old_result_name = @result_name
1829
+ @result_name = nil
1830
+
1831
+ block_str = ""
1832
+ want_expression(false) do
1833
+ want_result do
1834
+ block_str << process(code) if code
1835
+ end
1836
+ end
1837
+
1838
+ # generate code for the variable declarations
1839
+ var_str = ""
1840
+ unless @current_iter_dvars.empty?
1841
+ var_str << "var "
1842
+ var_str << @current_iter_dvars.to_a.join(",")
1843
+ var_str << sep()
1844
+ end
1845
+
1846
+ # declare and initialize the return value
1847
+ if @result_name
1848
+ var_str << "var #{@result_name}=#{@model.encode_nil}#{sep()}"
1849
+ end
1850
+
1851
+ # NOTE: we don't need to initialize any dvar to nil, as
1852
+ # they can't be used before being assigned to (because
1853
+ # otherwise they are vcall's and not dynamic variables).
1854
+
1855
+ str = fun_str + var_str + asgn_str + sep() + block_str
1856
+ str << "#{sep()}return #{@result_name}" if @result_name
1857
+ str << "}"
1858
+
1859
+ put_iter(str)
1860
+
1861
+ @result_name = old_result_name
1862
+ @current_iter_dvars = old_iter_dvars
1863
+ @block_whileloop_stack.pop || raise
1864
+
1865
+ return process(call)
1866
+ end
1867
+
1868
+ #
1869
+ # a =~ /regexp/
1870
+ #
1871
+ # is converted to
1872
+ #
1873
+ # [:match3, [:lit, /regexp/], [:lvar, :a]]
1874
+ #
1875
+ # We just convert it to:
1876
+ #
1877
+ # [:call, [:lvar, :a], :=~, [:array, [:lit, /regexp/]]]
1878
+ #
1879
+ def process_match3(exp)
1880
+ right = exp.shift
1881
+ left = exp.shift
1882
+ return process(s(:call, left, :=~, s(:array, right)))
1883
+ end
1884
+
1885
+ #
1886
+ # if /regexp/ =~ a
1887
+ #
1888
+ # is converted to
1889
+ #
1890
+ # [:match2, [:lit, /regexp/], [:lvar, :a]]
1891
+ #
1892
+ # We just convert it to:
1893
+ #
1894
+ # [:call, [:lit, /regexp/], :=~, [:array, [:lvar, :a]]]
1895
+ #
1896
+ def process_match2(exp)
1897
+ left = exp.shift
1898
+ right = exp.shift
1899
+ return process(s(:call, left, :=~, s(:array, right)))
1900
+ end
1901
+
1902
+ #######################################################################
1903
+
1904
+ private
1905
+
1906
+ #######################################################################
1907
+
1908
+ def without_result(&block)
1909
+ want_result(false, &block)
1910
+ end
1911
+
1912
+ def want_result(wish=true)
1913
+ old_want_result = @want_result
1914
+ begin
1915
+ @want_result = wish
1916
+ return yield
1917
+ ensure
1918
+ @want_result = old_want_result
1919
+ end
1920
+ end
1921
+
1922
+ def resultify(str)
1923
+ if @want_result
1924
+ # FIXME
1925
+ result_name() + "=" + str
1926
+ else
1927
+ str
1928
+ end
1929
+ end
1930
+
1931
+ def result_name
1932
+ @result_name ||= @model.encode_fresh_local_variable()
1933
+ #@local_variables.add(@result_name)
1934
+ @result_name
1935
+ end
1936
+
1937
+ #
1938
+ # Generate a name for @block_name (which is the argument for
1939
+ # the block in the Javascript function signature).
1940
+ #
1941
+ def block_name
1942
+ @block_name ||= @model.encode_fresh_local_variable()
1943
+ @argument_variables.add(@block_name)
1944
+ @iterators_used = true
1945
+ # FIXME: not true. passing an interator &block does not
1946
+ # mean that iterators are used!
1947
+ @block_name
1948
+ end
1949
+
1950
+ def arguments_name()
1951
+ @arguments_name ||= @model.encode_fresh_local_variable()
1952
+ end
1953
+
1954
+ def exception_name()
1955
+ @exception_name ||= @model.encode_fresh_local_variable()
1956
+ end
1957
+
1958
+ def conditionalize(exp, negate=false)
1959
+ # Optimize literals
1960
+ case exp.first
1961
+ when :true, :lit, :self, :str # evaluate to true
1962
+ return (negate ? "false" : "true")
1963
+ when :false, :nil # evaluate to false
1964
+ return (negate ? "true" : "false")
1965
+ end
1966
+
1967
+ want_expression do
1968
+ with_temporary_variable do |tmp|
1969
+ @local_variables_need_no_initialization.add(tmp)
1970
+ if negate
1971
+ "(#{tmp}=#{process(exp)},#{tmp}===false||#{tmp}===nil)"
1972
+ else
1973
+ "(#{tmp}=#{process(exp)},#{tmp}!==false&&#{tmp}!==nil)"
1974
+ end
1975
+ end
1976
+ end
1977
+ end
1978
+
1979
+ def with_temporary_variable
1980
+ var = get_temporary_variable()
1981
+ begin
1982
+ return (yield var)
1983
+ ensure
1984
+ put_temporary_variable(var)
1985
+ end
1986
+ end
1987
+
1988
+ def get_temporary_variable
1989
+ tmp = @temporary_variables_pool.shift || @model.encode_fresh_local_variable
1990
+ @local_variables.add(tmp)
1991
+ return tmp
1992
+ end
1993
+
1994
+ def put_temporary_variable(tmp)
1995
+ @temporary_variables_pool.unshift(tmp)
1996
+ end
1997
+
1998
+ def get_iter
1999
+ res = @iter
2000
+ @iter = nil
2001
+ res
2002
+ end
2003
+
2004
+ def put_iter(iter)
2005
+ @iter = iter
2006
+ end
2007
+ end