superchris-rubyjs 0.8.2

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 (86) hide show
  1. data/README +131 -0
  2. data/Rakefile +65 -0
  3. data/bin/rubyjs +144 -0
  4. data/rubyjs.gemspec +112 -0
  5. data/src/rubyjs.rb +3 -0
  6. data/src/rubyjs/code_generator.rb +474 -0
  7. data/src/rubyjs/compiler.rb +2061 -0
  8. data/src/rubyjs/debug_name_generator.rb +95 -0
  9. data/src/rubyjs/encoder.rb +171 -0
  10. data/src/rubyjs/eval_into.rb +59 -0
  11. data/src/rubyjs/lib/core.rb +1016 -0
  12. data/src/rubyjs/lib/dom_element.rb +66 -0
  13. data/src/rubyjs/lib/json.rb +101 -0
  14. data/src/rubyjs/lib/microunit.rb +188 -0
  15. data/src/rubyjs/model.rb +293 -0
  16. data/src/rubyjs/name_generator.rb +71 -0
  17. data/src/rwt/AbsolutePanel.rb +161 -0
  18. data/src/rwt/DOM.Konqueror.rb +89 -0
  19. data/src/rwt/DOM.Opera.rb +65 -0
  20. data/src/rwt/DOM.rb +1044 -0
  21. data/src/rwt/Event.Opera.rb +35 -0
  22. data/src/rwt/Event.rb +429 -0
  23. data/src/rwt/HTTPRequest.IE6.rb +5 -0
  24. data/src/rwt/HTTPRequest.rb +74 -0
  25. data/src/rwt/Label.rb +164 -0
  26. data/src/rwt/Panel.rb +90 -0
  27. data/src/rwt/RootPanel.rb +16 -0
  28. data/src/rwt/UIObject.rb +495 -0
  29. data/src/rwt/Widget.rb +193 -0
  30. data/src/rwt/ported-from/AbsolutePanel.java +158 -0
  31. data/src/rwt/ported-from/DOM.java +571 -0
  32. data/src/rwt/ported-from/DOMImpl.java +426 -0
  33. data/src/rwt/ported-from/DOMImplOpera.java +82 -0
  34. data/src/rwt/ported-from/DOMImplStandard.java +234 -0
  35. data/src/rwt/ported-from/HTTPRequest.java +81 -0
  36. data/src/rwt/ported-from/HTTPRequestImpl.java +103 -0
  37. data/src/rwt/ported-from/Label.java +163 -0
  38. data/src/rwt/ported-from/Panel.java +99 -0
  39. data/src/rwt/ported-from/UIObject.java +614 -0
  40. data/src/rwt/ported-from/Widget.java +221 -0
  41. data/test/benchmark/bm_vm1_block.rb +15 -0
  42. data/test/benchmark/bm_vm1_const.rb +13 -0
  43. data/test/benchmark/bm_vm1_ensure.rb +15 -0
  44. data/test/benchmark/common.rb +5 -0
  45. data/test/benchmark/params.yaml +7 -0
  46. data/test/common.Browser.rb +13 -0
  47. data/test/common.rb +8 -0
  48. data/test/gen_browser_test_suite.rb +129 -0
  49. data/test/gen_test_suite.rb +41 -0
  50. data/test/run_benchs.rb +58 -0
  51. data/test/run_tests.rb +22 -0
  52. data/test/test_args.rb +24 -0
  53. data/test/test_array.rb +22 -0
  54. data/test/test_case.rb +35 -0
  55. data/test/test_class.rb +55 -0
  56. data/test/test_eql.rb +9 -0
  57. data/test/test_exception.rb +61 -0
  58. data/test/test_expr.rb +12 -0
  59. data/test/test_hash.rb +29 -0
  60. data/test/test_hot_ruby.rb +146 -0
  61. data/test/test_if.rb +28 -0
  62. data/test/test_insertion_sort.rb +25 -0
  63. data/test/test_inspect.rb +10 -0
  64. data/test/test_lebewesen.rb +39 -0
  65. data/test/test_massign.rb +66 -0
  66. data/test/test_new.rb +12 -0
  67. data/test/test_range.rb +70 -0
  68. data/test/test_regexp.rb +22 -0
  69. data/test/test_send.rb +65 -0
  70. data/test/test_simple_output.rb +5 -0
  71. data/test/test_splat.rb +21 -0
  72. data/test/test_string.rb +51 -0
  73. data/test/test_test.rb +17 -0
  74. data/test/test_yield.rb +154 -0
  75. data/utils/js/Makefile +9 -0
  76. data/utils/js/RunScript.class +0 -0
  77. data/utils/js/RunScript.java +73 -0
  78. data/utils/js/js.jar +0 -0
  79. data/utils/js/run.sh +3 -0
  80. data/utils/jsc/Makefile +7 -0
  81. data/utils/jsc/README +3 -0
  82. data/utils/jsc/RunScript.c +93 -0
  83. data/utils/jsc/run.sh +15 -0
  84. data/utils/yuicompressor/README +1 -0
  85. data/utils/yuicompressor/yuicompressor-2.2.5.jar +0 -0
  86. metadata +157 -0
@@ -0,0 +1,2061 @@
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
+ # for var in expr do ... end
630
+ #
631
+ def process_for(exp)
632
+ receiver = exp.shift
633
+ asgn = exp.shift
634
+ block = exp.shift
635
+
636
+ new_exp = [:iter,
637
+ [:call, receiver, :each],
638
+ asgn,
639
+ block]
640
+
641
+ process(new_exp)
642
+ end
643
+
644
+ def process_dot2(exp)
645
+ range(exp, false)
646
+ end
647
+
648
+ def process_dot3(exp)
649
+ range(exp, true)
650
+ end
651
+
652
+ def range(exp, exclude_end)
653
+ from = exp.shift
654
+ to = exp.shift
655
+
656
+ without_result do
657
+ want_expression do
658
+ from = process(from)
659
+ to = process(to)
660
+ end
661
+ end
662
+
663
+ range = @model.lookup_constant('::Range')
664
+ @model.add_method_call(m = @model.encode_method("new"))
665
+ res = "#{range}.#{m}(#{@model.encode_nil},#{from},#{to},#{exclude_end})"
666
+ resultify(res)
667
+ end
668
+
669
+ #
670
+ # EXPRESSION
671
+ #
672
+ # Attribute assignment: receiver.attr=(value)
673
+ #
674
+ # Same as a method call!
675
+ #
676
+ def process_attrasgn(exp)
677
+ process_call(exp)
678
+ end
679
+
680
+ #
681
+ # The ||= operator.
682
+ #
683
+ # For example:
684
+ #
685
+ # a = a || "hallo"
686
+ #
687
+ # [:lasgn, :a, [:or, [:lvar, :a], [:str, "hallo"]]],
688
+ #
689
+ # a ||= "hallo"
690
+ #
691
+ # [:op_asgn_or, [:lvar, :a], [:lasgn, :a, [:str, "hallo"]]]]]]
692
+ #
693
+ # We rewrite the one to the other.
694
+ #
695
+ def process_op_asgn_or(exp)
696
+ ref = exp.shift
697
+ asgn = exp.shift
698
+ asgn[2] = [:or, ref, asgn[2]]
699
+ process(asgn)
700
+ end
701
+
702
+ #
703
+ # EXPRESSION
704
+ #
705
+ # Method call with receiver
706
+ #
707
+ def process_call(exp)
708
+ receiver = exp.shift
709
+ method = exp.shift
710
+ args = exp.shift
711
+
712
+ iter = get_iter()
713
+
714
+ #
715
+ # RubyJS::inline("str")
716
+ #
717
+ # A different form of Javascript inline. We have this form
718
+ # because ``-style Javascript-inline has a different esacping
719
+ # of \. Sometimes we want no escaping, so
720
+ #
721
+ # RubyJS::inline %q(...) could be used.
722
+ #
723
+ if receiver == [:const, :RubyJS] and method == :inline and
724
+ args[0] == :array and args.size == 2 and args[1].size == 2 and args[1][0] == :str and iter.nil?
725
+ raise if @want_expression
726
+ return @model.interpolate(args[1][1])
727
+ end
728
+
729
+ str =
730
+ if $RUBYJS__OPTS.include?('OptimizeArithOps') and %w(> >= < <= + - * /).include?(method.to_s) and
731
+ iter.nil? and args and args[0] == :array and args.size == 2
732
+ without_result do
733
+ want_expression do
734
+ "(#{process(receiver)})#{method.to_s}(#{process(args[1])})"
735
+ end
736
+ end
737
+ else
738
+ without_result do
739
+ generate_method_call_receiver_exp(receiver, method, iter, args)
740
+ end
741
+ end
742
+ resultify(str)
743
+ end
744
+
745
+ #
746
+ # EXPRESSION
747
+ #
748
+ # Virtual call. Either a method call or a variable, determined at
749
+ # runtime.
750
+ #
751
+ # Ruby cannot distinguish a local variable and a method called without
752
+ # parentheses and without receiver at parse time. Example:
753
+ #
754
+ # def my_method
755
+ # method_or_not.inspect
756
+ # end
757
+ #
758
+ # If a local variable of that name exists, then it is used as
759
+ # receiver. Otherwise it is seen as a method and is called.
760
+ #
761
+ # NOTE: A vcall can never have an iterator! Because if an iterator is
762
+ # specified it is automatically no variable and as such a fcall or
763
+ # call is generated.
764
+ #
765
+ # NOTE: As RubyJS disallows to use eval(), a vcall can never be a
766
+ # local variable (FIXME: to be prooved).
767
+ #
768
+ def process_vcall(exp)
769
+ method = exp.shift
770
+
771
+ resultify(generate_method_call(@model.encode_self, method, nil, nil))
772
+ end
773
+
774
+ #
775
+ # EXPRESSION
776
+ #
777
+ # Q: Always call with "nil" if no argument is given?
778
+ # A: No, because then we could not distinguish between
779
+ # no arguments and "nil" as argument.
780
+ #
781
+ def process_yield(exp)
782
+ value = exp.shift
783
+
784
+ str = without_result do
785
+ want_expression do
786
+ block_name() + "(" + (value ? process(value) : '') + ")"
787
+ end
788
+ end
789
+ resultify(str)
790
+ end
791
+
792
+ #
793
+ # Constant lookup
794
+ # ===============
795
+ #
796
+ # Constant lookup in RubyJS is performed statically. This is possible
797
+ # because the whole class hierarchy is available at compile-time.
798
+
799
+ #
800
+ # EXPRESSION
801
+ #
802
+ def process_const(exp)
803
+ resultify(@model.lookup_constant(constify(exp, :const)))
804
+ end
805
+
806
+ #
807
+ # helper methods
808
+ #
809
+
810
+ def constify(exp, type)
811
+ exp.unshift(type)
812
+ const_rec(exp).join("::")
813
+ end
814
+
815
+ def const_rec(exp)
816
+ case exp.shift
817
+ when :const
818
+ return [exp.shift]
819
+ when :colon2
820
+ const_rec(exp.shift) + [exp.shift]
821
+ when :colon3
822
+ [nil, exp.shift]
823
+ else
824
+ raise
825
+ end
826
+ end
827
+
828
+ #
829
+ # EXPRESSION
830
+ #
831
+ # A::B # => [:colon2, [:const, :A], :B]
832
+ #
833
+ def process_colon2(exp)
834
+ resultify(@model.lookup_constant(constify(exp, :colon2)))
835
+ end
836
+
837
+ #
838
+ # EXPRESSION
839
+ #
840
+ # ::A # => [:colon3, :A]
841
+ #
842
+ def process_colon3(exp)
843
+ resultify(@model.lookup_constant(constify(exp, :colon3)))
844
+ end
845
+
846
+ #
847
+ # EXPRESSION
848
+ #
849
+ # Implements the "&&" operator, which has short circuit behaviour.
850
+ #
851
+ # a && b
852
+ #
853
+ # is equivalent in Ruby to
854
+ #
855
+ # if a then b else a end
856
+ #
857
+ def process_and(exp)
858
+ a = exp.shift
859
+ b = exp.shift
860
+
861
+ res = without_result do
862
+ want_expression do
863
+ with_temporary_variable do |tmp|
864
+ @local_variables_need_no_initialization.add(tmp)
865
+ "(#{tmp}=#{process(a)}, (#{tmp}!==false&&#{tmp}!==nil) ? (#{process(b)}) : #{tmp})"
866
+ end
867
+ end
868
+ end
869
+
870
+ return resultify(res)
871
+ end
872
+
873
+ #
874
+ # EXPRESSION
875
+ #
876
+ # Implements the "||" operator, which has short circuit behaviour.
877
+ #
878
+ # a || b
879
+ #
880
+ # is equivalent in Ruby to
881
+ #
882
+ # if a then a else b end
883
+ #
884
+ def process_or(exp)
885
+ a = exp.shift
886
+ b = exp.shift
887
+
888
+ res = without_result do
889
+ want_expression do
890
+ with_temporary_variable do |tmp|
891
+ @local_variables_need_no_initialization.add(tmp)
892
+ "(#{tmp}=#{process(a)}, (#{tmp}!==false&&#{tmp}!==nil) ? #{tmp} : (#{process(b)}))"
893
+ end
894
+ end
895
+ end
896
+
897
+ return resultify(res)
898
+ end
899
+
900
+ #
901
+ # EXPRESSION
902
+ #
903
+ def process_not(exp)
904
+ a = exp.shift
905
+
906
+ without_result do
907
+ want_expression do
908
+ a = conditionalize(a, true)
909
+ end
910
+ end
911
+
912
+ return resultify(a)
913
+ end
914
+
915
+ #
916
+ # STATEMENT/EXPRESSION
917
+ #
918
+ # unless is converted by the Ruby parser into an "if":
919
+ #
920
+ # unless X
921
+ # block
922
+ # end
923
+ #
924
+ # =>
925
+ #
926
+ # if X
927
+ # else
928
+ # block
929
+ # end
930
+ #
931
+ def process_if(exp)
932
+ cond = exp.shift
933
+ _then = exp.shift
934
+ _else = exp.shift
935
+
936
+ _then_processed = if _then
937
+ process(_then)
938
+ else
939
+ nil
940
+ end
941
+
942
+ _else_processed = if _else
943
+ process(_else)
944
+ else
945
+ nil
946
+ end
947
+
948
+ negate = false
949
+
950
+ if _then_processed.nil?
951
+ # no "then" block as in
952
+ #
953
+ # if X
954
+ # else
955
+ # block
956
+ # end
957
+ #
958
+ # => convert into
959
+ #
960
+ # if not X
961
+ # block
962
+ # end
963
+ #
964
+ # by swapping then and else blocks
965
+ # and negating the condition
966
+
967
+ _then_processed = _else_processed
968
+ _else_processed = nil
969
+ negate = true
970
+ end
971
+
972
+ cond_processed = without_result do
973
+ conditionalize(cond, negate)
974
+ end
975
+
976
+ str = ""
977
+
978
+ if @want_expression
979
+ _then_processed ||= resultify(@model.encode_nil)
980
+ _else_processes ||= resultify(@model.encode_nil)
981
+ str << "(#{cond_processed}?#{_then_processed}:#{_else_processed})"
982
+ else
983
+ str << "if(#{cond_processed}){"
984
+ str << (_then_processed || (@want_result ? resultify(@model.encode_nil) : ''))
985
+ str << "}"
986
+ if @want_result
987
+ _else_processed ||= resultify(@model.encode_nil)
988
+ end
989
+ if _else_processed
990
+ str << "else{"
991
+ str << _else_processed
992
+ str << "}"
993
+ end
994
+ end
995
+
996
+ return str
997
+ end
998
+
999
+ #
1000
+ # STATEMENT/EXPRESSION
1001
+ #
1002
+ # case obj
1003
+ # when c1
1004
+ # block1
1005
+ # when c2, c3
1006
+ # block23
1007
+ # else
1008
+ # blockelse
1009
+ # end
1010
+ #
1011
+ # [:case,
1012
+ # obj,
1013
+ # [:when, [:array, c1], block1],
1014
+ # [:when, [:array, c2, c3], block23],
1015
+ # blockelse or nil
1016
+ # ]
1017
+ #
1018
+ # We are transforming the "case" into "if".
1019
+ #
1020
+ # _tmp = obj;
1021
+ # if c1 === _tmp
1022
+ # block1
1023
+ # else
1024
+ # if c2 === _tmp or c3 === _tmp then
1025
+ # block23
1026
+ # else
1027
+ # blockelse
1028
+ # end
1029
+ # end
1030
+ #
1031
+ def process_case(exp)
1032
+ obj = exp.shift
1033
+
1034
+ with_temporary_variable do |tmp|
1035
+ @local_variables_need_no_initialization.add(tmp)
1036
+ asgn = want_expression do
1037
+ "#{tmp}=#{process(obj)}"
1038
+ end
1039
+
1040
+ new_exp = current_exp = []
1041
+
1042
+ while not exp.empty?
1043
+ _when = exp.shift
1044
+ if _when.nil? or _when.first != :when
1045
+ # last element
1046
+ raise exp.inspect unless exp.empty?
1047
+ current_exp << _when
1048
+ else
1049
+ conds = _when[1]
1050
+ block = _when[2]
1051
+ raise unless conds.first == :array
1052
+
1053
+ cond = multi_or(conds[1..-1].map {|c| [:call, c, :===, [:array, [:special_inline_js_value, tmp]]] })
1054
+
1055
+ my_exp = [:if, cond, block]
1056
+
1057
+ current_exp << my_exp
1058
+ current_exp = my_exp
1059
+ end
1060
+ end
1061
+
1062
+ if_code = process(new_exp.first)
1063
+
1064
+ code =
1065
+ if @want_expression
1066
+ "(#{asgn}, #{if_code})"
1067
+ else
1068
+ "#{asgn}#{sep()}#{if_code}"
1069
+ end
1070
+
1071
+ code
1072
+ end
1073
+ end
1074
+
1075
+ def multi_or(exprs)
1076
+ case exprs.size
1077
+ when 0
1078
+ raise
1079
+ when 1
1080
+ # multi_or([1]) => 1
1081
+ exprs[0]
1082
+ when 2
1083
+ # multi_or([1, 2]) => [:or, 1, 2]
1084
+ [:or, exprs[0], exprs[1]]
1085
+ else
1086
+ # multi_or([1, 2, 3]) => [:or, 1, multi_or([2,3])] == [:or, 1, [:or, 2, 3]]
1087
+ [:or, exprs[0], multi_or(exprs[1..-1])]
1088
+ end
1089
+ end
1090
+
1091
+ #
1092
+ # STATEMENT
1093
+ #
1094
+ def process_begin(exp)
1095
+ raise if @want_expression
1096
+ block = exp.shift
1097
+ process(block)
1098
+ end
1099
+
1100
+ #
1101
+ # STATEMENT
1102
+ #
1103
+ def process_rescue(exp)
1104
+ raise if @want_expression
1105
+ block = exp.shift
1106
+ handler = exp.shift
1107
+ else_handler = exp.shift
1108
+ raise unless handler.first == :resbody
1109
+
1110
+ x = exception_name()
1111
+
1112
+ code = [:block, block]
1113
+ code << else_handler if else_handler
1114
+
1115
+ str = ""
1116
+ str << "try{#{process(code)}}"
1117
+ str << "catch(#{x}){"
1118
+
1119
+ # check whether it's an iterator break. if yes, pass it on
1120
+ iter_jump = @model.encode_globalattr("iter_jump")
1121
+ str << "if(#{x} instanceof #{iter_jump})throw(#{x})#{sep()}"
1122
+ str << "#{process(handler)}"
1123
+ str << "}"
1124
+
1125
+ str
1126
+ end
1127
+
1128
+ #
1129
+ # STATEMENT
1130
+ #
1131
+ def process_resbody(exp)
1132
+ raise if @want_expression
1133
+ condition = exp.shift
1134
+ if_body = exp.shift
1135
+ else_body = exp.shift
1136
+
1137
+ x = exception_name()
1138
+
1139
+ # if only "rescue" is used (and not e.g. "rescue Exception")
1140
+ condition ||= [:array, [:const, :StandardError]]
1141
+ raise unless condition.first == :array
1142
+
1143
+ # if this is the last resbody (else_body is nil) then
1144
+ # the exception couldn't be catched and we rethrow it!
1145
+ else_body ||= [:special_inline_js_value, "throw(#{x})"]
1146
+
1147
+ # build condition expression
1148
+ cond =
1149
+ multi_or(condition[1..-1].map do |c|
1150
+ [:call, c, :===, [:array, [:special_inline_js_value, x]]]
1151
+ end)
1152
+
1153
+ process([:if, cond, if_body, else_body])
1154
+ end
1155
+
1156
+ #
1157
+ # STATEMENT
1158
+ #
1159
+ def process_ensure(exp)
1160
+ raise if @want_expression
1161
+ try_body = process(exp.shift)
1162
+ ensure_body = without_result { process(exp.shift) }
1163
+
1164
+ "try{#{try_body}}finally{#{ensure_body}}"
1165
+ end
1166
+
1167
+ #
1168
+ # STATEMENT
1169
+ #
1170
+ def process_return(exp)
1171
+ return_value =
1172
+ if param = exp.shift
1173
+ want_expression do
1174
+ without_result do
1175
+ process(param)
1176
+ end
1177
+ end
1178
+ else
1179
+ @model.encode_nil
1180
+ end
1181
+ if @block_whileloop_stack.last == :iter
1182
+ # return from within an iterator
1183
+ throw_iter_jump(return_value, unique_method_scope())
1184
+ elsif @want_expression
1185
+ throw_iter_jump(return_value)
1186
+ else
1187
+ "return #{return_value}"
1188
+ end
1189
+ end
1190
+
1191
+ #
1192
+ # STATEMENT
1193
+ #
1194
+ def process_while(exp)
1195
+ raise if @want_expression
1196
+
1197
+ cond = exp.shift
1198
+ block = exp.shift
1199
+ flag = exp.shift
1200
+ raise unless flag == true # FIXME: document
1201
+
1202
+ str = without_result do
1203
+ c = conditionalize(cond)
1204
+ @block_whileloop_stack.push(:while)
1205
+ b = process(block)
1206
+ @block_whileloop_stack.pop || raise
1207
+
1208
+ "while(#{c}){#{b}}"
1209
+ end
1210
+
1211
+ if @want_result
1212
+ str << sep() + resultify(@model.encode_nil) + sep()
1213
+ end
1214
+
1215
+ return str
1216
+ end
1217
+
1218
+ #
1219
+ # STATEMENT
1220
+ #
1221
+ def process_break(exp)
1222
+ case @block_whileloop_stack.last
1223
+ when :while
1224
+ raise "break with arguments inside while not yet supported" unless exp.empty?
1225
+ raise if @want_expression
1226
+ "break"
1227
+ when :iter
1228
+ return_value =
1229
+ if param = exp.shift
1230
+ without_result do
1231
+ process(param)
1232
+ end
1233
+ else
1234
+ @model.encode_nil
1235
+ end
1236
+ throw_iter_jump(return_value)
1237
+ when nil
1238
+ raise("break not in loop/block scope")
1239
+ else
1240
+ raise "FATAL"
1241
+ end
1242
+ end
1243
+
1244
+ def throw_iter_jump(return_value, scope=nil)
1245
+ @iterators_used = true # used to produce catch code in this method
1246
+ iter_jump = @model.encode_globalattr('iter_jump')
1247
+ scope ||= "null"
1248
+ th =
1249
+ if @want_expression
1250
+ @model.encode_globalattr("throw_expr")
1251
+ else
1252
+ "throw"
1253
+ end
1254
+
1255
+ "#{th}(new #{iter_jump}(#{return_value},#{scope}))"
1256
+ end
1257
+
1258
+ #
1259
+ # STATEMENT
1260
+ #
1261
+ def process_next(exp)
1262
+ case @block_whileloop_stack.last
1263
+ when :while
1264
+ raise "next with arguments inside while not yet supported" unless exp.empty?
1265
+ raise if @want_expression
1266
+ "continue"
1267
+ when :iter
1268
+ # next inside a code-block generates a "return"
1269
+ raise if @want_expression
1270
+
1271
+ return_value =
1272
+ if param = exp.shift
1273
+ want_expression do
1274
+ without_result do
1275
+ process(param)
1276
+ end
1277
+ end
1278
+ else
1279
+ @model.encode_nil
1280
+ end
1281
+
1282
+ "return #{return_value}"
1283
+ when nil
1284
+ raise("next not in loop/block scope")
1285
+ else
1286
+ raise "FATAL"
1287
+ end
1288
+ end
1289
+
1290
+ #
1291
+ # UNDEFINED
1292
+ #
1293
+ # Backtick strings: `inline javascript`
1294
+ #
1295
+ # We use them for inline Javascript.
1296
+ #
1297
+ # It's unclear whether it's a STATEMENT or EXPRESSION.
1298
+ # It depends on the Javascript.
1299
+ #
1300
+ # NOTE: You have to take care to return a value yourself
1301
+ # in case of @want_result, i.e. there is no automatic handling
1302
+ # thereof.
1303
+ #
1304
+ def process_xstr(exp)
1305
+ str = exp.shift
1306
+ @model.interpolate(str)
1307
+ end
1308
+
1309
+ #
1310
+ # EXPRESSION
1311
+ #
1312
+ # String interpolation
1313
+ #
1314
+ def process_dstr(exp)
1315
+ arr = []
1316
+ arr << exp.shift.inspect
1317
+
1318
+ without_result do
1319
+ want_expression do
1320
+ exp.each {|e| arr << "(" + process(e) + ")" }
1321
+ end
1322
+ end
1323
+
1324
+ exp.clear
1325
+ resultify("(" + arr.join(" + ") + ")")
1326
+ end
1327
+
1328
+ def process_evstr(exp)
1329
+ e = exp.shift
1330
+
1331
+ @model.add_method_call(to_s = @model.encode_method("to_s"))
1332
+
1333
+ res = without_result do
1334
+ want_expression do
1335
+ "(" + process(e) + ").#{to_s}()"
1336
+ end
1337
+ end
1338
+
1339
+ resultify(res)
1340
+ end
1341
+
1342
+ #
1343
+ # EXPRESSION
1344
+ #
1345
+ # Process literals
1346
+ #
1347
+ def process_lit(exp)
1348
+ lit = exp.shift
1349
+ res = case lit
1350
+ when Fixnum, Bignum, Float
1351
+ lit.to_s
1352
+ when String
1353
+ lit.inspect
1354
+ when Symbol
1355
+ lit.to_s.inspect
1356
+ when Regexp
1357
+ lit.inspect
1358
+ when Range
1359
+ range = @model.lookup_constant('::Range')
1360
+ @model.add_method_call(m = @model.encode_method("new"))
1361
+ "#{range}.#{m}(#{@model.encode_nil},#{lit.first},#{lit.last},#{lit.exclude_end?})"
1362
+ else
1363
+ raise
1364
+ end
1365
+
1366
+ resultify(res)
1367
+ end
1368
+
1369
+ def is_numeric_literal(exp)
1370
+ type = exp[0]
1371
+ return false if exp[0] != :lit
1372
+ case exp[1]
1373
+ when Fixnum, Bignum, Float
1374
+ true
1375
+ else
1376
+ false
1377
+ end
1378
+ end
1379
+
1380
+ #
1381
+ # EXPRESSION
1382
+ #
1383
+ # Process string literal
1384
+ #
1385
+ def process_str(exp)
1386
+ str = exp.shift
1387
+ resultify(str.inspect)
1388
+ end
1389
+
1390
+ #
1391
+ # EXPRESSION
1392
+ #
1393
+ def process_true(exp)
1394
+ resultify("true")
1395
+ end
1396
+
1397
+ #
1398
+ # EXPRESSION
1399
+ #
1400
+ def process_false(exp)
1401
+ resultify("false")
1402
+ end
1403
+
1404
+ #
1405
+ # EXPRESSION
1406
+ #
1407
+ def process_nil(exp)
1408
+ resultify(@model.encode_nil)
1409
+ end
1410
+
1411
+ #
1412
+ # EXPRESSION
1413
+ #
1414
+ def process_self(exp)
1415
+ resultify(@model.encode_self)
1416
+ end
1417
+
1418
+ #
1419
+ # EXPRESSION
1420
+ #
1421
+ def process_splat(exp)
1422
+ value = exp.shift
1423
+ to_splat = @model.encode_globalattr('to_splat')
1424
+ str = without_result do
1425
+ want_expression do
1426
+ "#{to_splat}(#{ process(value) })"
1427
+ end
1428
+ end
1429
+ resultify(str)
1430
+ end
1431
+
1432
+ #
1433
+ # EXPRESSION
1434
+ #
1435
+ def process_argscat(exp)
1436
+ prefix = exp.shift
1437
+ splat = exp.shift
1438
+ raise unless prefix.first == :array
1439
+ to_splat = @model.encode_globalattr('to_splat')
1440
+ str = without_result do
1441
+ want_expression do
1442
+ "#{process(prefix)}.concat(#{to_splat}(#{ process(splat) }))"
1443
+ end
1444
+ end
1445
+ resultify(str)
1446
+ end
1447
+
1448
+ #
1449
+ # EXPRESSION
1450
+ #
1451
+ # Array literal
1452
+ #
1453
+ def process_array(exp)
1454
+ str = without_result do
1455
+ want_expression do
1456
+ "[" + exp.map{|e| process(e)}.compact.join(",") + "]"
1457
+ end
1458
+ end
1459
+ exp.clear
1460
+ resultify(str)
1461
+ end
1462
+
1463
+ #
1464
+ # EXPRESSION
1465
+ #
1466
+ # We box the hash and use our own implementation, as Javascript's
1467
+ # associative arrays are very different to Ruby hashes.
1468
+ #
1469
+ def process_hash(exp)
1470
+ kv_list = exp
1471
+ raise if kv_list.length % 2 != 0
1472
+
1473
+ args =
1474
+ without_result do
1475
+ want_expression do
1476
+ kv_list.map {|i| process(i)}.join(",")
1477
+ end
1478
+ end
1479
+
1480
+ str = @model.lookup_constant('::Hash') + "."
1481
+ if kv_list.empty?
1482
+ # empty Hash
1483
+ @model.add_method_call(m = @model.encode_method("new"))
1484
+ str << "#{m}()"
1485
+ else
1486
+ @model.add_method_call(m = @model.encode_method("new_from_key_value_list"))
1487
+ str << "#{m}(#{@model.encode_nil},#{args})"
1488
+ end
1489
+
1490
+ exp.clear
1491
+
1492
+ return str
1493
+ end
1494
+
1495
+ #
1496
+ # EXPRESSION
1497
+ #
1498
+ # Empty array.
1499
+ #
1500
+ def process_zarray(exp)
1501
+ resultify("[]")
1502
+ end
1503
+
1504
+
1505
+ #
1506
+ # EXPRESSION
1507
+ #
1508
+ # Local variable assignment
1509
+ #
1510
+ # We have to write down all local variables, because they have to be
1511
+ # declared at the top of the method (otherwise they are seen as
1512
+ # "global" variables :)
1513
+ #
1514
+ def process_lasgn(exp)
1515
+ lvar = exp.shift
1516
+ value = exp.shift
1517
+
1518
+ lvar_name = @model.encode_local_variable(lvar)
1519
+ @local_variables.add(lvar_name)
1520
+ # local variables need not to be initialized, because they
1521
+ # have to be asigned to before usage, otherwise they would
1522
+ # not be local variables but methods instead.
1523
+ @local_variables_need_no_initialization.add(lvar_name)
1524
+
1525
+ str = without_result do
1526
+ want_expression do
1527
+ "#{lvar_name}=#{process(value)}"
1528
+ end
1529
+ end
1530
+
1531
+ resultify(str)
1532
+ end
1533
+
1534
+ #
1535
+ # EXPRESSION
1536
+ #
1537
+ # Local variable lookup
1538
+ #
1539
+ def process_lvar(exp)
1540
+ lvar = exp.shift
1541
+
1542
+ lvar_name = @model.encode_local_variable(lvar)
1543
+ raise "variable not available" unless @local_variables.include?(lvar_name)
1544
+
1545
+ resultify("#{lvar_name}")
1546
+ end
1547
+
1548
+ #
1549
+ # EXPRESSION
1550
+ #
1551
+ # Regular expression lookup $1 .. $9
1552
+ #
1553
+ def process_nth_ref(exp)
1554
+ n = exp.shift
1555
+ raise if n < 1 or n > 9
1556
+ resultify("(RegExp.$#{n} || #{@model.encode_nil})")
1557
+ end
1558
+
1559
+ #
1560
+ # EXPRESSION
1561
+ #
1562
+ # Global variable lookup
1563
+ #
1564
+ def process_gvar(exp)
1565
+ gvar = exp.shift
1566
+
1567
+ res =
1568
+ case gvar.to_s
1569
+ when "$!"
1570
+ # this is a special variable which holds the current exception
1571
+ exception_name()
1572
+ else
1573
+ gvar_name = @model.encode_global_variable(gvar)
1574
+ "(typeof(#{gvar_name})=='undefined'?#{@model.encode_nil}:#{gvar_name})"
1575
+ end
1576
+
1577
+ resultify(res)
1578
+ end
1579
+
1580
+ #
1581
+ # EXPRESSION
1582
+ #
1583
+ # Global variable assignment
1584
+ #
1585
+ def process_gasgn(exp)
1586
+ gvar = exp.shift
1587
+ value = exp.shift
1588
+
1589
+ gvar_name = @model.encode_global_variable(gvar)
1590
+
1591
+ str = without_result do
1592
+ want_expression do
1593
+ "#{gvar_name}=#{process(value)}"
1594
+ end
1595
+ end
1596
+
1597
+ resultify(str)
1598
+ end
1599
+
1600
+ #
1601
+ # EXPRESSION
1602
+ #
1603
+ # Dynamic variable lookup
1604
+ #
1605
+ def process_dvar(exp)
1606
+ dvar = exp.shift
1607
+ dvar_name = @model.encode_local_variable(dvar)
1608
+ raise "dynamic variable not available" unless @current_iter_dvars.include?(dvar_name)
1609
+
1610
+ resultify("#{dvar_name}")
1611
+ end
1612
+
1613
+ #
1614
+ # EXPRESSION
1615
+ #
1616
+ # Dynamic variable assignment
1617
+ #
1618
+ def process_dasgn(exp)
1619
+ dvar = exp.shift
1620
+ value = exp.shift
1621
+ dvar_name = @model.encode_local_variable(dvar)
1622
+ raise "dynamic variable not available" unless @current_iter_dvars.include?(dvar_name)
1623
+
1624
+ str = without_result do
1625
+ want_expression do
1626
+ "#{dvar_name}=#{process(value)}"
1627
+ end
1628
+ end
1629
+
1630
+ resultify(str)
1631
+ end
1632
+
1633
+ #
1634
+ # EXPRESSION
1635
+ #
1636
+ # Dynamic variable declaration and assignment
1637
+ #
1638
+ def process_dasgn_curr(exp)
1639
+ dvar = exp.shift
1640
+ value = exp.shift
1641
+ dvar_name = @model.encode_local_variable(dvar)
1642
+
1643
+ @current_iter_dvars.add(dvar_name)
1644
+
1645
+ str = without_result do
1646
+ want_expression do
1647
+ if value
1648
+ "#{dvar_name}=#{process(value)}"
1649
+ else
1650
+ # this should never happen, see process_iter()
1651
+ raise "no declarations possible"
1652
+ end
1653
+ end
1654
+ end
1655
+
1656
+ resultify(str)
1657
+ end
1658
+
1659
+ #
1660
+ # EXPRESSION
1661
+ #
1662
+ # Instance variable lookup
1663
+ #
1664
+ def process_ivar(exp)
1665
+ ivar = exp.shift
1666
+ ivar_name = @model.encode_instance_variable(ivar)
1667
+ @read_instance_variables.add(ivar_name)
1668
+ resultify("#{@model.encode_self}.#{ivar_name}")
1669
+ end
1670
+
1671
+ def process_svalue
1672
+ raise
1673
+ end
1674
+
1675
+ def process_to_ary(exp)
1676
+ value = exp.shift
1677
+ str = without_result do
1678
+ want_expression do
1679
+ case value.first
1680
+ when :lit
1681
+ "[" + process(value) + "]"
1682
+ when :array, :zarray
1683
+ process(value)
1684
+ when :special_to_ary
1685
+ # this is used inside of process_iter to build the multiple
1686
+ # assignment.
1687
+ value[1]
1688
+ else
1689
+ generate_method_call_receiver_exp(value, "to_ary", nil, nil)
1690
+ end
1691
+ end
1692
+ end
1693
+ resultify(str)
1694
+ end
1695
+
1696
+ #
1697
+ # EXPRESSION
1698
+ #
1699
+ # This is used to insert a pure inline JS string into the
1700
+ # code. It is used for example in process_masgn.
1701
+ #
1702
+ # It is not part of the ParseTree returned node types!
1703
+ #
1704
+ def process_special_inline_js_value(exp)
1705
+ return exp.shift
1706
+ end
1707
+
1708
+ #
1709
+ # EXPRESSION
1710
+ #
1711
+ # Multiple assignment
1712
+ #
1713
+ # Simple case:
1714
+ #
1715
+ # a, b = 1, 2
1716
+ #
1717
+ # [:masgn,
1718
+ # [:array, [:lasgn, :a], [:lasgn, :b]],
1719
+ # [:array, [:lit, 1], [:lit, 2]]]]]]
1720
+ #
1721
+ # Case with splat argument:
1722
+ #
1723
+ # a, *b = 1, 2, 3
1724
+ #
1725
+ # [:masgn,
1726
+ # [:array, [:lasgn, :a]],
1727
+ # [:lasgn, :b],
1728
+ # [:array, [:lit, 1], [:lit, 2], [:lit, 3]]]]]]
1729
+ #
1730
+ # Another case:
1731
+ #
1732
+ # a, b = 1
1733
+ #
1734
+ # [:masgn,
1735
+ # [:array, [:lasgn, :a], [:lasgn, :b]],
1736
+ # [:to_ary, [:lit, 1]]]
1737
+ #
1738
+ # We actually implement multiple assignment using a
1739
+ # temporary array. Example:
1740
+ #
1741
+ # a, b = b, a
1742
+ #
1743
+ # leads to the following javascript code
1744
+ #
1745
+ # (_t = [b,a],
1746
+ # a = _t[0] == null ? nil : _t[0],
1747
+ # b = _t[1] == null ? nil : _t[1])
1748
+ #
1749
+ # When a splat argument is given, there's just an
1750
+ # additional assignment which takes the rest of the
1751
+ # array.
1752
+ #
1753
+ def process_masgn(exp)
1754
+ lhs = exp.shift
1755
+ rhs = exp.pop
1756
+ splat = exp.shift
1757
+
1758
+ raise unless lhs.first == :array
1759
+ raise unless rhs.first == :array or rhs.first == :to_ary
1760
+
1761
+ want_expression do
1762
+ with_temporary_variable do |tmp|
1763
+ assgn = []
1764
+ without_result do
1765
+ assgn << "#{tmp}=#{process(rhs)}"
1766
+
1767
+ # lhs[0] == :array -> skip it
1768
+ lhs[1..-1].each_with_index do |assignment, i| # for example where assignment == [:lasgn, :a]
1769
+ assignment << s(:special_inline_js_value, "#{tmp}[#{i}]==null?#{@model.encode_nil}:#{tmp}[#{i}]")
1770
+ assgn << process(assignment)
1771
+ end
1772
+
1773
+ if splat
1774
+ # splat is for example [:lasgn, :a]
1775
+ splat << s(:special_inline_js_value, "#{tmp}.slice(#{lhs.size-1})")
1776
+ assgn << process(splat)
1777
+ end
1778
+ end
1779
+
1780
+ # return value of the expression is the array
1781
+ assgn << resultify("#{tmp}")
1782
+
1783
+ "(" + assgn.join(",") + ")"
1784
+ end
1785
+ end
1786
+ end
1787
+
1788
+ #
1789
+ # EXPRESSION
1790
+ #
1791
+ # Instance variable assignment
1792
+ #
1793
+ def process_iasgn(exp)
1794
+ ivar = exp.shift
1795
+ value = exp.shift
1796
+ ivar_name = @model.encode_instance_variable(ivar)
1797
+
1798
+ str = without_result do
1799
+ want_expression do
1800
+ "#{@model.encode_self}.#{ivar_name}=#{process(value)}"
1801
+ end
1802
+ end
1803
+
1804
+ resultify(str)
1805
+ end
1806
+
1807
+ #
1808
+ # EXPRESSION (doesn't generate any code directly!)
1809
+ #
1810
+ # Iterator block.
1811
+ #
1812
+ # We process as follows:
1813
+ #
1814
+ # 1. Generate code for the arguments of the block (multiple
1815
+ # assignment etc.)
1816
+ #
1817
+ # 2. Generate code for the code block.
1818
+ #
1819
+ # 3. In 1 and 2 we also write down all dynamic variable assignments.
1820
+ # ("dasgn_curr" which introduces a new scope)
1821
+ #
1822
+ # 4. Generate code for variable declarations.
1823
+ #
1824
+ # TODO: assign arity to function object!
1825
+ #
1826
+ def process_iter(exp)
1827
+ call = exp.shift # the method call to which the iterator is passed
1828
+ params = exp.shift # arguments of the block
1829
+ code = exp.shift # the code block
1830
+
1831
+ old_iter_dvars = @current_iter_dvars
1832
+ @current_iter_dvars = Set.new
1833
+ @block_whileloop_stack.push(:iter)
1834
+
1835
+ # Get an argument name for the iterator function signature.
1836
+ arg_name = @model.encode_fresh_local_variable()
1837
+
1838
+ fun_str = ""
1839
+ asgn_str = ""
1840
+
1841
+ if params.nil?
1842
+ # Case 1: {}: Any number of arguments may be passed
1843
+ arity = -1
1844
+ fun_str << "function(){"
1845
+ elsif params == 0
1846
+ # Case 2: {||}: Zero arguments
1847
+ arity = 0
1848
+ fun_str << "function(){"
1849
+ elsif params.first == :masgn
1850
+ # Case 3: {|i,j|}: Multiple arguments (multiple assignment)
1851
+ arity = nil # |arity| >= 2
1852
+ fun_str << "function(#{arg_name}){"
1853
+
1854
+ # TODO: remove masgn_iter and instead put the corresponding logic
1855
+ # into yield!
1856
+ masgn_iter = @model.encode_globalattr("masgn_iter")
1857
+ params << [:to_ary, [:special_to_ary, "#{masgn_iter}(#{arg_name})"]]
1858
+ want_expression(false) do
1859
+ without_result do
1860
+ asgn_str << process(params)
1861
+ end
1862
+ end
1863
+ else
1864
+ # Case 4: {|i|}: Single argument
1865
+ # TODO: warning if not exactly one argument is passed
1866
+ arity = 1
1867
+ fun_str << "function(#{arg_name}){"
1868
+
1869
+ # we convert a single argument into a multiple assignment clause
1870
+ # with one argument.
1871
+
1872
+ sasgn_iter = @model.encode_globalattr("sasgn_iter")
1873
+ params << [:special_inline_js_value, "#{arg_name}==null?#{@model.encode_nil}:#{arg_name}"]
1874
+
1875
+ want_expression(false) do
1876
+ without_result do
1877
+ asgn_str << process(params)
1878
+ end
1879
+ end
1880
+ end
1881
+
1882
+ old_result_name = @result_name
1883
+ @result_name = nil
1884
+
1885
+ block_str = ""
1886
+ want_expression(false) do
1887
+ want_result do
1888
+ block_str << process(code) if code
1889
+ end
1890
+ end
1891
+
1892
+ # generate code for the variable declarations
1893
+ var_str = ""
1894
+ unless @current_iter_dvars.empty?
1895
+ var_str << "var "
1896
+ var_str << @current_iter_dvars.to_a.join(",")
1897
+ var_str << sep()
1898
+ end
1899
+
1900
+ # declare and initialize the return value
1901
+ if @result_name
1902
+ var_str << "var #{@result_name}=#{@model.encode_nil}#{sep()}"
1903
+ end
1904
+
1905
+ # NOTE: we don't need to initialize any dvar to nil, as
1906
+ # they can't be used before being assigned to (because
1907
+ # otherwise they are vcall's and not dynamic variables).
1908
+
1909
+ str = fun_str + var_str + asgn_str + sep() + block_str
1910
+ str << "#{sep()}return #{@result_name}" if @result_name
1911
+ str << "}"
1912
+
1913
+ put_iter(str)
1914
+
1915
+ @result_name = old_result_name
1916
+ @current_iter_dvars = old_iter_dvars
1917
+ @block_whileloop_stack.pop || raise
1918
+
1919
+ return process(call)
1920
+ end
1921
+
1922
+ #
1923
+ # a =~ /regexp/
1924
+ #
1925
+ # is converted to
1926
+ #
1927
+ # [:match3, [:lit, /regexp/], [:lvar, :a]]
1928
+ #
1929
+ # We just convert it to:
1930
+ #
1931
+ # [:call, [:lvar, :a], :=~, [:array, [:lit, /regexp/]]]
1932
+ #
1933
+ def process_match3(exp)
1934
+ right = exp.shift
1935
+ left = exp.shift
1936
+ return process(s(:call, left, :=~, s(:array, right)))
1937
+ end
1938
+
1939
+ #
1940
+ # if /regexp/ =~ a
1941
+ #
1942
+ # is converted to
1943
+ #
1944
+ # [:match2, [:lit, /regexp/], [:lvar, :a]]
1945
+ #
1946
+ # We just convert it to:
1947
+ #
1948
+ # [:call, [:lit, /regexp/], :=~, [:array, [:lvar, :a]]]
1949
+ #
1950
+ def process_match2(exp)
1951
+ left = exp.shift
1952
+ right = exp.shift
1953
+ return process(s(:call, left, :=~, s(:array, right)))
1954
+ end
1955
+
1956
+ #######################################################################
1957
+
1958
+ private
1959
+
1960
+ #######################################################################
1961
+
1962
+ def without_result(&block)
1963
+ want_result(false, &block)
1964
+ end
1965
+
1966
+ def want_result(wish=true)
1967
+ old_want_result = @want_result
1968
+ begin
1969
+ @want_result = wish
1970
+ return yield
1971
+ ensure
1972
+ @want_result = old_want_result
1973
+ end
1974
+ end
1975
+
1976
+ def resultify(str)
1977
+ if @want_result
1978
+ # FIXME
1979
+ result_name() + "=" + str
1980
+ else
1981
+ str
1982
+ end
1983
+ end
1984
+
1985
+ def result_name
1986
+ @result_name ||= @model.encode_fresh_local_variable()
1987
+ #@local_variables.add(@result_name)
1988
+ @result_name
1989
+ end
1990
+
1991
+ #
1992
+ # Generate a name for @block_name (which is the argument for
1993
+ # the block in the Javascript function signature).
1994
+ #
1995
+ def block_name
1996
+ @block_name ||= @model.encode_fresh_local_variable()
1997
+ @argument_variables.add(@block_name)
1998
+ @iterators_used = true
1999
+ # FIXME: not true. passing an interator &block does not
2000
+ # mean that iterators are used!
2001
+ @block_name
2002
+ end
2003
+
2004
+ def arguments_name()
2005
+ @arguments_name ||= @model.encode_fresh_local_variable()
2006
+ end
2007
+
2008
+ def exception_name()
2009
+ @exception_name ||= @model.encode_fresh_local_variable()
2010
+ end
2011
+
2012
+ def conditionalize(exp, negate=false)
2013
+ # Optimize literals
2014
+ case exp.first
2015
+ when :true, :lit, :self, :str # evaluate to true
2016
+ return (negate ? "false" : "true")
2017
+ when :false, :nil # evaluate to false
2018
+ return (negate ? "true" : "false")
2019
+ end
2020
+
2021
+ want_expression do
2022
+ with_temporary_variable do |tmp|
2023
+ @local_variables_need_no_initialization.add(tmp)
2024
+ if negate
2025
+ "(#{tmp}=#{process(exp)},#{tmp}===false||#{tmp}===nil)"
2026
+ else
2027
+ "(#{tmp}=#{process(exp)},#{tmp}!==false&&#{tmp}!==nil)"
2028
+ end
2029
+ end
2030
+ end
2031
+ end
2032
+
2033
+ def with_temporary_variable
2034
+ var = get_temporary_variable()
2035
+ begin
2036
+ return (yield var)
2037
+ ensure
2038
+ put_temporary_variable(var)
2039
+ end
2040
+ end
2041
+
2042
+ def get_temporary_variable
2043
+ tmp = @temporary_variables_pool.shift || @model.encode_fresh_local_variable
2044
+ @local_variables.add(tmp)
2045
+ return tmp
2046
+ end
2047
+
2048
+ def put_temporary_variable(tmp)
2049
+ @temporary_variables_pool.unshift(tmp)
2050
+ end
2051
+
2052
+ def get_iter
2053
+ res = @iter
2054
+ @iter = nil
2055
+ res
2056
+ end
2057
+
2058
+ def put_iter(iter)
2059
+ @iter = iter
2060
+ end
2061
+ end