ruby2js 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzA1Mzk5YzBmMWU5OWVhNTQ1MzdlODkwMmE0ZTAyMzhkYjkzZTQzOQ==
4
+ NWUwYjJjY2RiMjE2MDhkMjdlMDVkODQ4MDFlNWUwYWZjODg3NjUwNA==
5
5
  data.tar.gz: !binary |-
6
- NzA2YmI3ODFkM2I0Y2I0ZjY1MTFiOTBkNDIxMTE5ZTM5ODdiNzU3ZA==
6
+ NGU5ZWI1ODU4MThkMTFjOTZjNDZmNDlkYzg2Mjc4NzIxMjNjZDk5MA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NzY0MjhhMzA5YTI0NzVkZWMzYTQxYzBlOTQ1ZjIyZmE5MTkwNmM3ZTViOTA4
10
- OGE0ZDNhY2NjYjk3ZDliNDYxOGRmNWY5NDZjYzUyNjBjMmVjNjFkOGMxOWUz
11
- NjA5ZDI5MmVlZGYyNzdlMmUwYmJkMDhlNmEyYjNhYTYzZTEyOWI=
9
+ ODA4MGVkMTZlZjU0MTA5YWM4YTg1OGNhMThlNTg0Nzc4NTViNjI2NDZhZDkx
10
+ M2YyZmY2YTkxMTE5ZDM5OTA0OGQ4NmNmNzRkNjU2MWJiMjA3MGIwMmExZTFh
11
+ YTM5OWE3MjExN2NhOGI2ZWJhYWNmYjFjYTc2Y2Y4OWZkYmQyOGY=
12
12
  data.tar.gz: !binary |-
13
- YzAxMzRhMmY2NGRkMzZlYWNiYjIzZDkyMWM4ZGYyYWYzODRlZGUzMjdkMjQ1
14
- ZTAyNzUxOTA4ZTViZmQyZGZiZTg2Y2U3NTk5NDU1NjA2OWVlN2M2YTEyOTQ5
15
- ZGI1NTczNDhjNmJlZDNmNjRiMzg1YTljY2EzNmJkMTllZGE3Y2Q=
13
+ MjI0MzE3MmVmNzNjYzdiOWRjODFlYmRmYzA0NjNkY2U0YTdhZTViNTlhNGRj
14
+ NzE5ZjY3NjI3NmE3NjhjNzY3MjFiNDEwMDU3M2RiYWU0YTg4MDA2ODhmZWM5
15
+ YjM0NGM5ZWI2YTA4MWI3OTFiOGM0MTQ5YWE1NzlhNmJiNDU5NTg=
data/README.md CHANGED
@@ -13,10 +13,14 @@ calls IF there are either one or more arguments passed OR parenthesis are
13
13
  used, otherwise Ruby method calls become JavaScript property accesses.
14
14
  By default, methods, lambdas, and procs return `undefined`.
15
15
 
16
- Filters may be provided to add Ruby-specific or Framework specific
16
+ Filters may be provided to add Ruby-specific or framework specific
17
17
  behavior. Filters are essentially macro facilities that operate on
18
18
  an AST representation of the code.
19
19
 
20
+ See
21
+ [notimplemented_spec](https://github.com/rubys/ruby2js/blob/master/spec/notimplemented_spec.rb)
22
+ for a list of Ruby features _known_ to be not implemented.
23
+
20
24
  Synopsis
21
25
  ---
22
26
 
@@ -37,6 +41,72 @@ puts Ruby2JS.convert('"2A".to_i(16)')
37
41
  Conversions can be explored interactively using the
38
42
  [demo](https://github.com/rubys/ruby2js/blob/master/demo/ruby2js.rb) provided.
39
43
 
44
+ Introduction
45
+ ---
46
+
47
+ JavaScript is a language where `0` is considered `false`, strings are
48
+ immutable, and the [behaviors](http://zero.milosz.ca/) for operators like `==`
49
+ are, at best, convoluted.
50
+
51
+ Any attempt to bridge the semantics of Ruby and JavaScript will involve
52
+ trade-offs. Consider the following expression:
53
+
54
+ ```ruby
55
+ a[-1]
56
+ ```
57
+
58
+ Programmers who are familiar with Ruby will recognize that this returns the
59
+ last element (or character) of an array (or string). However, the meaning is
60
+ quite different if a is a Hash.
61
+
62
+ One way to resolve this is to change the way indexing operators are evaluated,
63
+ and to provide a runtime library that adds properties to global JavaScript
64
+ objects to handle this. It’s the approach that [Opal](http://opalrb.org/)
65
+ takes. It is a fine approach, with a number of benefits. It also has some
66
+ notable drawbacks. For example,
67
+ [Readability](http://opalrb.org/try/#code:a%20%3D%20%22abc%22%3B%20puts%20a[-1])
68
+ and
69
+ [compatibility with other frameworks](https://github.com/opal/opal/issues/400).
70
+
71
+ Another approach is to simply accept JavaScript semantics for what they are.
72
+ This would mean that negative indexes would return `undefined` for arrays
73
+ and strings. This is the base approach provided by ruby2js.
74
+
75
+ A third approach would be to do static transformations on the source in order
76
+ to address common usage patterns or idioms. These transformations can even be
77
+ occasionally unsafe, as long as the transformations themselves are opt-in.
78
+ ruby2js provides a number of such filters, including one that handles negative
79
+ indexes when passes as a literal. As indicated above, this is unsafe in that
80
+ it will do the wrong thing when it encounters a hash index which is expressed
81
+ as a literal constant negative one. My experience is that such is rare enough
82
+ to be safely ignored, but YMMV. More troublesome, this also won’t work when
83
+ the index is not a literal (e.g., `a[n]`) where the index happens to be
84
+ negative at runtime.
85
+
86
+ This quickly gets into gray areas. `each` in Ruby is a common method that
87
+ facilitates iteration over arrays. `forEach` is the JavaScript equivalent.
88
+ Mapping this is fine until you start using a framework like jQuery which
89
+ provides a function named [each](http://api.jquery.com/jQuery.each/).
90
+
91
+ These approaches aren’t mutually exclusive. With enough static transformations
92
+ and runtime libraries, one could reproduce any functionality desired. Just be
93
+ forewarned, that implementing a function like `method_missing` would require a
94
+ _lot_ of work.
95
+
96
+ Picking a Ruby to JS mapping tool
97
+ ---
98
+
99
+ If you simply want to get a job done, and would like a mature and tested
100
+ framework, and only use one of the many integrations that
101
+ [Opal](http://opalrb.org/) provides, then Opal is the way to go right now.
102
+
103
+ ruby2js is for those that want to produce JavaScript that looks like it
104
+ wasn’t machine generated, and with the absolute bare minimum in terms of
105
+ limitations as to what JavaScript can be produced.
106
+
107
+ [Try](http://intertwingly.net/projects/ruby2js/all) for yourself.
108
+ [Compare](http://opalrb.org/try/#code:).
109
+
40
110
  License
41
111
  ---
42
112
 
@@ -4,13 +4,20 @@ require 'ruby2js/converter'
4
4
  module Ruby2JS
5
5
  module Filter
6
6
  DEFAULTS = []
7
+
8
+ module SEXP
9
+ # construct an AST Node
10
+ def s(type, *args)
11
+ Parser::AST::Node.new type, args
12
+ end
13
+ end
7
14
  end
8
15
 
9
16
  def self.convert(source, options={})
10
17
 
11
18
  if Proc === source
12
19
  file,line = source.source_location
13
- source = File.read(file)
20
+ source = File.read(file).untaint
14
21
  ast = find_block( parse(source), line )
15
22
  elsif Parser::AST::Node === source
16
23
  ast = source
@@ -31,6 +38,8 @@ module Ruby2JS
31
38
 
32
39
  ruby2js = Ruby2JS::Converter.new( ast )
33
40
 
41
+ ruby2js.binding = options[:binding]
42
+
34
43
  if source.include? "\n"
35
44
  ruby2js.enable_vertical_whitespace
36
45
  lines = ruby2js.to_js.split("\n")
@@ -55,7 +64,8 @@ module Ruby2JS
55
64
 
56
65
  blank = pending
57
66
  end
58
- lines.join("\n")
67
+
68
+ lines.join("\n").gsub(/^ ( *(case.*|default):$)/, '\1')
59
69
  else
60
70
  ruby2js.to_js
61
71
  end
@@ -4,17 +4,26 @@ module Ruby2JS
4
4
  class Converter
5
5
  LOGICAL = :and, :not, :or
6
6
  OPERATORS = [:[], :[]=], [:not, :!], [:*, :/, :%], [:+, :-], [:>>, :<<],
7
- [:<=, :<, :>, :>=], [:==, :!=], [:and, :or]
7
+ [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="], [:and, :or]
8
8
 
9
+ attr_accessor :binding
10
+
9
11
  def initialize( ast, vars = {} )
10
12
  @ast, @vars = ast, vars.dup
11
13
  @sep = '; '
12
14
  @nl = ''
15
+ @ws = ' '
16
+ @varstack = []
13
17
  end
14
18
 
15
19
  def enable_vertical_whitespace
16
20
  @sep = ";\n"
17
21
  @nl = "\n"
22
+ @ws = @nl
23
+ end
24
+
25
+ def binding=(binding)
26
+ @binding = binding
18
27
  end
19
28
 
20
29
  def to_js
@@ -25,10 +34,12 @@ module Ruby2JS
25
34
  OPERATORS.index( OPERATORS.find{ |el| el.include? op } ) || -1
26
35
  end
27
36
 
28
- def scope( ast )
29
- frame = self.class.new( nil, @vars )
30
- frame.enable_vertical_whitespace if @nl == "\n"
31
- frame.parse( ast, :statement )
37
+ def scope( ast, args={} )
38
+ @varstack.push @vars
39
+ @vars = @vars.merge(args)
40
+ parse( ast, :statement )
41
+ ensure
42
+ @vars = @varstack.pop
32
43
  end
33
44
 
34
45
  def s(type, *args)
@@ -54,7 +65,7 @@ module Ruby2JS
54
65
  when :sym
55
66
  ast.children.first.to_s.inspect
56
67
 
57
- when :lvar, :gvar
68
+ when :lvar, :gvar, :cvar
58
69
  ast.children.first
59
70
 
60
71
  when :true, :false
@@ -118,12 +129,41 @@ module Ruby2JS
118
129
  end
119
130
  "{#{ hashy.join(', ') }}"
120
131
 
132
+ when :regexp
133
+ str, opt = ast.children
134
+ if str.children.first.include? '/'
135
+ if opt.children.empty?
136
+ "new RegExp(#{ str.children.first.inspect })"
137
+ else
138
+ "new RegExp(#{ str.children.first.inspect }, #{ opt.children.join.inspect})"
139
+ end
140
+ else
141
+ "/#{ str.children.first }/#{ opt.children.join }"
142
+ end
143
+
121
144
  when :array
122
- list = ast.children.map { |a| parse a }
123
- if list.join(', ').length < 80
124
- "[#{ list.join(', ') }]"
145
+ splat = ast.children.rindex { |a| a.type == :splat }
146
+ if splat
147
+ items = ast.children
148
+ item = items[splat].children.first
149
+ if items.length == 1
150
+ parse item
151
+ elsif splat == items.length - 1
152
+ parse s(:send, s(:array, *items[0..-2]), :concat, item)
153
+ elsif splat == 0
154
+ parse s(:send, item, :concat, s(:array, *items[1..-1]))
155
+ else
156
+ parse s(:send,
157
+ s(:send, s(:array, *items[0..splat-1]), :concat, item),
158
+ :concat, s(:array, *items[splat+1..-1]))
159
+ end
125
160
  else
126
- "[\n#{ list.join(",\n") }\n]"
161
+ list = ast.children.map { |a| parse a }
162
+ if list.join(', ').length < 80
163
+ "[#{ list.join(', ') }]"
164
+ else
165
+ "[\n#{ list.join(",\n") }\n]"
166
+ end
127
167
  end
128
168
 
129
169
  when :begin
@@ -155,9 +195,13 @@ module Ruby2JS
155
195
 
156
196
  when :send, :attr
157
197
  receiver, method, *args = ast.children
198
+ if method =~ /\w[!?]$/
199
+ raise NotImplementedError, "invalid method name #{ method }"
200
+ end
201
+
158
202
  if method == :new and receiver and receiver.children == [nil, :Proc]
159
203
  return parse args.first
160
- elsif method == :lambda and not receiver
204
+ elsif not receiver and [:lambda, :proc].include? method
161
205
  return parse args.first
162
206
  end
163
207
 
@@ -171,30 +215,53 @@ module Ruby2JS
171
215
  group_receiver = receiver.type == :send && op_index <= operator_index( receiver.children[1] ) if receiver
172
216
  group_target = target.type == :send && op_index <= operator_index( target.children[1] ) if target
173
217
 
174
- case method
175
- when :!
176
- group_receiver ||= (receiver.children.length > 1)
177
- "!#{ group_receiver ? group(receiver) : parse(receiver) }"
178
-
179
- when :call
180
- "#{ parse receiver }(#{ parse args.first })"
218
+ if method == :!
219
+ if receiver.type == :defined?
220
+ parse s(:undefined?, *receiver.children)
221
+ else
222
+ group_receiver ||= (receiver.type != :send && receiver.children.length > 1)
223
+ "!#{ group_receiver ? group(receiver) : parse(receiver) }"
224
+ end
181
225
 
182
- when :[]
183
- raise 'parse error' unless receiver
184
- "#{ parse receiver }[#{ parse args.first }]"
226
+ elsif method == :[]
227
+ "#{ parse receiver }[#{ args.map {|arg| parse arg}.join(', ') }]"
185
228
 
186
- when :-@, :+@
229
+ elsif method == :-@ or method == :+@
187
230
  "#{ method.to_s[0] }#{ parse receiver }"
188
-
189
- when *OPERATORS.flatten
231
+
232
+ elsif method == :=~
233
+ "#{ parse args.first }.test(#{ parse receiver })"
234
+
235
+ elsif method == :!~
236
+ "!#{ parse args.first }.test(#{ parse receiver })"
237
+
238
+ elsif OPERATORS.flatten.include? method
190
239
  "#{ group_receiver ? group(receiver) : parse(receiver) } #{ method } #{ group_target ? group(target) : parse(target) }"
191
240
 
192
- when /=$/
241
+ elsif method =~ /=$/
193
242
  "#{ parse receiver }#{ '.' if receiver }#{ method.to_s.sub(/=$/, ' =') } #{ parse args.first }"
194
243
 
244
+ elsif method == :new and receiver
245
+ args = args.map {|a| parse a}.join(', ')
246
+ "new #{ parse receiver }(#{ args })"
247
+
248
+ elsif method == :raise and receiver == nil
249
+ if args.length == 1
250
+ "throw #{ parse args.first }"
251
+ else
252
+ "throw new #{ parse args.first }(#{ parse args[1] })"
253
+ end
254
+
255
+ elsif method == :typeof and receiver == nil
256
+ "typeof #{ parse args.first }"
257
+
195
258
  else
196
259
  if args.length == 0 and not is_method?(ast)
197
260
  "#{ parse receiver }#{ '.' if receiver }#{ method }"
261
+ elsif args.length > 0 and args.last.type == :splat
262
+ parse s(:send, s(:attr, receiver, method), :apply, receiver,
263
+ s(:send, s(:array, *args[0..-2]), :concat,
264
+ args[-1].children.first))
198
265
  else
199
266
  args = args.map {|a| parse a}.join(', ')
200
267
  "#{ parse receiver }#{ '.' if receiver }#{ method }(#{ args })"
@@ -231,6 +298,19 @@ module Ruby2JS
231
298
  condition, block = ast.children
232
299
  "while (#{ parse condition }) {#@nl#{ scope block }#@nl}"
233
300
 
301
+ when :until
302
+ condition, block = ast.children
303
+ parse s(:while, s(:send, condition, :!), block)
304
+
305
+ when :while_post
306
+ condition, block = ast.children
307
+ block = block.updated(:begin) if block.type == :kwbegin
308
+ "do {#@nl#{ scope block }#@nl} while (#{ parse condition })"
309
+
310
+ when :until_post
311
+ condition, block = ast.children
312
+ parse s(:while_post, s(:send, condition, :!), block)
313
+
234
314
  when :for
235
315
  var, expression, block = ast.children
236
316
  parse s(:block,
@@ -238,6 +318,19 @@ module Ruby2JS
238
318
  s(:args, s(:arg, var.children.last)),
239
319
  block);
240
320
 
321
+ when :case
322
+ expr, *whens, other = ast.children
323
+
324
+ whens.map! do |node|
325
+ *values, code = node.children
326
+ cases = values.map {|value| "case #{ parse value }:#@ws"}.join
327
+ "#{ cases }#{ parse code }#{@sep}break#@sep"
328
+ end
329
+
330
+ other = "#{@nl}default:#@ws#{ parse other }#@nl" if other
331
+
332
+ "switch (#{ parse expr }) {#@nl#{whens.join(@nl)}#{other}}"
333
+
241
334
  when :block
242
335
  call, args, block = ast.children
243
336
  block ||= s(:begin)
@@ -247,37 +340,195 @@ module Ruby2JS
247
340
  when :def
248
341
  name, args, body = ast.children
249
342
  body ||= s(:begin)
250
- body = s(:scope, body) unless body.type == :scope
251
- body = parse body
252
- body.sub! /return var (\w+) = ([^;]+)\z/, "var \\1 = \\2#{@sep}return \\1"
253
- "function#{ " #{name}" if name }(#{ parse args }) {#@nl#{ body }#@nl}"
254
-
255
- when :scope
256
- body = ast.children.first
257
- body = s(:begin, body) unless body.type == :begin
258
- block = body.children
259
- scope body
260
-
343
+ if name =~ /[!?]$/
344
+ raise NotImplementedError, "invalid method name #{ name }"
345
+ end
346
+
347
+ vars = {}
348
+ if args and !args.children.empty?
349
+ # splats
350
+ if args.children.last.type == :restarg
351
+ if args.children[-1].children.first
352
+ body = s(:begin, body) unless body.type == :begin
353
+ assign = s(:lvasgn, args.children[-1].children.first,
354
+ s(:send, s(:attr,
355
+ s(:attr, s(:const, nil, :Array), :prototype), :slice),
356
+ :call, s(:lvar, :arguments),
357
+ s(:int, args.children.length-1)))
358
+ body = s(:begin, assign, *body.children)
359
+ end
360
+
361
+ args = s(:args, *args.children[0..-2])
362
+
363
+ elsif args.children.last.type == :blockarg and
364
+ args.children.length > 1 and args.children[-2].type == :restarg
365
+ body = s(:begin, body) unless body.type == :begin
366
+ blk = args.children[-1].children.first
367
+ vararg = args.children[-2].children.first
368
+ last = s(:send, s(:attr, s(:lvar, :arguments), :length), :-,
369
+ s(:int, 1))
370
+
371
+ # set block argument to the last argument passed
372
+ assign2 = s(:lvasgn, blk, s(:send, s(:lvar, :arguments), :[], last))
373
+
374
+ if vararg
375
+ # extract arguments between those defined and the last
376
+ assign1 = s(:lvasgn, vararg, s(:send, s(:attr, s(:attr, s(:const,
377
+ nil, :Array), :prototype), :slice), :call, s(:lvar, :arguments),
378
+ s(:int, args.children.length-1), last))
379
+ # push block argument back onto args if not a function
380
+ pushback = s(:if, s(:send, s(:send, nil, :typeof, s(:lvar, blk)),
381
+ :"!==", s(:str, "function")), s(:begin, s(:send, s(:lvar,
382
+ vararg), :push, s(:lvar, blk)), s(:lvasgn, blk, s(:nil))), nil)
383
+ # set block argument to null if all arguments were defined
384
+ pushback = s(:if, s(:send, s(:attr, s(:lvar, :arguments),
385
+ :length), :<=, s(:int, args.children.length-2)), s(:lvasgn,
386
+ blk, s(:nil)), pushback)
387
+ # combine statements
388
+ body = s(:begin, assign1, assign2, pushback, *body.children)
389
+ else
390
+ # set block argument to null if all arguments were defined
391
+ ignore = s(:if, s(:send, s(:attr, s(:lvar, :arguments),
392
+ :length), :<=, s(:int, args.children.length-2)), s(:lvasgn,
393
+ blk, s(:nil)), nil)
394
+ body = s(:begin, assign2, ignore, *body.children)
395
+ end
396
+
397
+ args = s(:args, *args.children[0..-3])
398
+ end
399
+
400
+ # optional arguments
401
+ args.children.each_with_index do |arg, i|
402
+ if arg.type == :optarg
403
+ body = s(:begin, body) unless body.type == :begin
404
+ argname, value = arg.children
405
+ children = args.children.dup
406
+ children[i] = s(:arg, argname)
407
+ args = s(:args, *children)
408
+ body = s(:begin, body) unless body.type == :begin
409
+ default = s(:if, s(:send, s(:defined?, s(:lvar, argname)), :!),
410
+ s(:lvasgn, argname, value), nil)
411
+ body = s(:begin, default, *body.children)
412
+ end
413
+ vars[arg.children.first] = true
414
+ end
415
+ end
416
+
417
+ "function#{ " #{name}" if name }(#{ parse args }) {#@nl#{ scope body, vars}#{@nl unless body == s(:begin)}}"
418
+
261
419
  when :class
262
420
  name, inheritance, *body = ast.children
421
+ init = s(:def, :initialize, s(:args))
263
422
  body.compact!
264
- body = body.first.children.dup if body.length == 1 and body.first.type == :begin
265
- methods = body.select { |a| a.type == :def }
266
- init = (body.delete methods.find { |m| m.children.first == :initialize }) || s(:def, :initialize)
267
- block = body.collect { |m| parse( m ).sub(/function (\w+)/, "#{ parse name }.prototype.\\1 = function") }.join @sep
268
- "#{ parse( s(:def, parse(name), init.children[1], init.children[2]) ).sub(/return (?:null|(.*))\}\z/, '\1}') }#{ @sep if block and not block.empty?}#{ block }"
423
+
424
+ if body.length == 1 and body.first.type == :begin
425
+ body = body.first.children.dup
426
+ end
427
+
428
+ body.map! do |m|
429
+ if m.type == :def
430
+ if m.children.first == :initialize
431
+ # constructor: remove from body and overwrite init function
432
+ init = m
433
+ nil
434
+ else
435
+ # method: add to prototype
436
+ s(:send, s(:attr, name, :prototype), "#{m.children[0]}=",
437
+ s(:block, s(:send, nil, :proc), *m.children[1..-1]))
438
+ end
439
+ elsif m.type == :defs and m.children.first == s(:self)
440
+ # class method definition: add to prototype
441
+ s(:send, name, "#{m.children[1]}=",
442
+ s(:block, s(:send, nil, :proc), *m.children[2..-1]))
443
+ elsif m.type == :send and m.children.first == nil
444
+ # class method call
445
+ s(:send, name, *m.children[1..-1])
446
+ elsif m.type == :lvasgn
447
+ # class variable
448
+ s(:send, name, "#{m.children[0]}=", *m.children[1..-1])
449
+ elsif m.type == :casgn and m.children[0] == nil
450
+ # class constant
451
+ s(:send, name, "#{m.children[1]}=", *m.children[2..-1])
452
+ else
453
+ raise NotImplementedError, "class #{ m.type }"
454
+ end
455
+ end
456
+
457
+ if inheritance
458
+ body.unshift s(:send, name, :prototype=, s(:send, inheritance, :new))
459
+ end
460
+
461
+ # prepend constructor
462
+ body.unshift s(:def, parse(name), *init.children[1..-1])
463
+
464
+ parse s(:begin, *body.compact)
269
465
 
270
466
  when :args
271
- ast.children.map { |a| a.children.first }.join(', ')
467
+ ast.children.map { |a| parse a }.join(', ')
468
+
469
+ when :arg, :blockarg
470
+ ast.children.first
471
+
472
+ when :block_pass
473
+ parse ast.children.first
272
474
 
273
- when :dstr
475
+ when :dstr, :dsym
274
476
  ast.children.map{ |s| parse s }.join(' + ')
275
477
 
478
+ when :xstr
479
+ if @binding
480
+ @binding.eval(ast.children.first.children.first).to_s
481
+ else
482
+ eval(ast.children.first.children.first).to_s
483
+ end
484
+
276
485
  when :self
277
486
  'this'
278
487
 
488
+ when :break
489
+ 'break'
490
+
491
+ when :next
492
+ 'continue'
493
+
494
+ when :defined?
495
+ "typeof #{ parse ast.children.first } !== 'undefined'"
496
+
497
+ when :undefined?
498
+ "typeof #{ parse ast.children.first } === 'undefined'"
499
+
500
+ when :undef
501
+ ast.children.map {|c| "delete #{c.children.last}"}.join @sep
502
+
503
+ when :kwbegin
504
+ block = ast.children.first
505
+ if block.type == :ensure
506
+ block, finally = block.children
507
+ else
508
+ finally = nil
509
+ end
510
+
511
+ if block and block.type == :rescue
512
+ body, recover, otherwise = block.children
513
+ raise NotImplementedError, "block else" if otherwise
514
+ exception, name, recovery = recover.children
515
+ raise NotImplementedError, parse(exception) if exception
516
+ else
517
+ body = block
518
+ end
519
+
520
+ output = "try {#@nl#{ parse body }#@nl}"
521
+ output += " catch (#{ parse name }) {#@nl#{ parse recovery }#@nl}" if recovery
522
+ output += " finally {#@nl#{ parse finally }#@nl}" if finally
523
+
524
+ if recovery or finally
525
+ output
526
+ else
527
+ parse s(:begin, *ast.children)
528
+ end
529
+
279
530
  else
280
- raise "unknown AST type #{ ast.type }"
531
+ raise NotImplementedError, "unknown AST type #{ ast.type }"
281
532
  end
282
533
  end
283
534
 
@@ -4,6 +4,8 @@ require 'ruby2js'
4
4
  module Ruby2JS
5
5
  module Filter
6
6
  module AngularRB
7
+ include SEXP
8
+
7
9
  def initialize(*args)
8
10
  @ngApp = nil
9
11
  super
@@ -30,13 +32,13 @@ module Ruby2JS
30
32
 
31
33
  # find the block
32
34
  block = process_all(node.children[1..-1])
33
- while block.length == 1 and block.first.type == :begin
35
+ while block.length == 1 and block.first and block.first.type == :begin
34
36
  block = block.first.children.dup
35
37
  end
36
38
 
37
39
  # find use class method calls
38
40
  uses = block.find_all do |node|
39
- node.type == :send and node.children[0..1] == [nil, :use]
41
+ node and node.type == :send and node.children[0..1] == [nil, :use]
40
42
  end
41
43
 
42
44
  # convert use calls into dependencies
@@ -154,13 +156,6 @@ module Ruby2JS
154
156
 
155
157
  node.updated nil, [outer, s(:args), s(:return, inner)]
156
158
  end
157
-
158
- private
159
-
160
- # construct an AST Node
161
- def s(type, *args)
162
- Parser::AST::Node.new type, args
163
- end
164
159
  end
165
160
 
166
161
  DEFAULTS.push AngularRB
@@ -3,15 +3,7 @@ require 'ruby2js'
3
3
  module Ruby2JS
4
4
  module Filter
5
5
  module Functions
6
-
7
- # map $$ to $
8
- def on_gvar(node)
9
- if node.children[0] == :$$
10
- node.updated nil, ['$']
11
- else
12
- super
13
- end
14
- end
6
+ include SEXP
15
7
 
16
8
  def on_send(node)
17
9
  target = process(node.children.first)
@@ -26,15 +18,76 @@ module Ruby2JS
26
18
  elsif node.children[1] == :to_f
27
19
  node.updated nil, [nil, :parseFloat, target, *args]
28
20
 
21
+ elsif node.children[1] == :sub and node.children.length == 4
22
+ source, method, before, after = node.children
23
+ node.updated nil, [source, :replace, before, after]
24
+
25
+ elsif node.children[1] == :gsub and node.children.length == 4
26
+ source, method, before, after = node.children
27
+ if before.type == :regexp
28
+ before = s(:regexp, before.children.first,
29
+ s(:regopt, :g, *before.children[1]))
30
+ elsif before.type == :str
31
+ before = s(:regexp, s(:str, Regexp.escape(before.children.first)),
32
+ s(:regopt, :g))
33
+ end
34
+ node.updated nil, [source, :replace, before, after]
35
+
29
36
  elsif node.children[1] == :each
30
- if target.type == :gvar and target.children == ['$']
31
- super
32
- elsif target.type == :send and target.children == [nil, :jQuery]
37
+ if @each # disable `each` mapping, see jquery filter for an example
33
38
  super
34
39
  else
35
40
  node.updated nil, [target, :forEach, *args]
36
41
  end
37
42
 
43
+ elsif node.children[0..1] == [nil, :puts]
44
+ s(:send, s(:attr, nil, :console), :log, *node.children[2..-1])
45
+
46
+ elsif node.children[1..-1] == [:first]
47
+ node.updated nil, [node.children[0], :[], s(:int, 0)]
48
+
49
+ elsif node.children[1..-1] == [:last]
50
+ on_send node.updated nil, [node.children[0], :[], s(:int, -1)]
51
+
52
+ elsif node.children[1] == :[] and node.children.length == 3
53
+ source = node.children[0]
54
+ index = node.children[2]
55
+
56
+ # resolve negative literal indexes
57
+ i = proc do |index|
58
+ if index.type == :int and index.children.first < 0
59
+ s(:send, s(:attr, source, :length), :-,
60
+ s(:int, -index.children.first))
61
+ else
62
+ index
63
+ end
64
+ end
65
+
66
+ if index.type == :int and index.children.first < 0
67
+ node.updated nil, [source, :[], i.(index)]
68
+
69
+ elsif index.type == :erange
70
+ start, finish = index.children
71
+ node.updated nil, [source, :slice, i.(start), i.(finish)]
72
+
73
+ elsif index.type == :irange
74
+ start, finish = index.children
75
+ start = i.(start)
76
+ if finish.type == :int
77
+ if finish.children.first == -1
78
+ finish = s(:attr, source, :length)
79
+ else
80
+ finish = i.(s(:int, finish.children.first+1))
81
+ end
82
+ else
83
+ finish = s(:send, finish, :+, s(:int, 1))
84
+ end
85
+ node.updated nil, [source, :slice, start, finish]
86
+
87
+ else
88
+ super
89
+ end
90
+
38
91
  elsif node.children[1] == :each_with_index
39
92
  node.updated nil, [target, :forEach, *args]
40
93
 
@@ -42,13 +95,6 @@ module Ruby2JS
42
95
  super
43
96
  end
44
97
  end
45
-
46
- private
47
-
48
- # construct an AST Node
49
- def s(type, *args)
50
- Parser::AST::Node.new type, args
51
- end
52
98
  end
53
99
 
54
100
  DEFAULTS.push Functions
@@ -0,0 +1,37 @@
1
+ require 'ruby2js'
2
+
3
+ module Ruby2JS
4
+ module Filter
5
+ module JQuery
6
+ include SEXP
7
+
8
+ def initialize
9
+ @each = true # disable each mapping, see functions filter
10
+ end
11
+
12
+ # map $$ to $
13
+ def on_gvar(node)
14
+ if node.children[0] == :$$
15
+ node.updated nil, ['$']
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def on_send(node)
22
+ if node.children[1] == :call
23
+ target = process(node.children.first)
24
+ if target.type == :gvar and target.children == ['$']
25
+ s(:send, nil, '$', *process_all(node.children[2..-1]))
26
+ else
27
+ super
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+ end
34
+
35
+ DEFAULTS.push JQuery
36
+ end
37
+ end
@@ -3,6 +3,8 @@ require 'ruby2js'
3
3
  module Ruby2JS
4
4
  module Filter
5
5
  module Return
6
+ include SEXP
7
+
6
8
  EXPRESSIONS = [ :array, :float, :hash, :if, :int, :lvar, :nil, :send ]
7
9
 
8
10
  def on_block(node)
@@ -49,13 +51,6 @@ module Ruby2JS
49
51
 
50
52
  node.updated nil, children
51
53
  end
52
-
53
- private
54
-
55
- # construct an AST Node
56
- def s(type, *args)
57
- Parser::AST::Node.new type, args
58
- end
59
54
  end
60
55
 
61
56
  DEFAULTS.push Return
@@ -1,7 +1,7 @@
1
1
  module Ruby2JS
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 2
3
+ MAJOR = 1
4
+ MINOR = 0
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -2,14 +2,14 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "ruby2js"
5
- s.version = "0.2.0"
5
+ s.version = "1.0.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Sam Ruby"]
9
- s.date = "2013-11-12"
10
- s.description = " The base package maps Ruby syntax to JavaScript semantics.\n Filters may be provided to add Ruby-specific or Framework specific\n behavior.\n"
9
+ s.date = "2013-11-15"
10
+ s.description = " The base package maps Ruby syntax to JavaScript semantics.\n Filters may be provided to add Ruby-specific or framework specific\n behavior.\n"
11
11
  s.email = "rubys@intertwingly.net"
12
- s.files = ["ruby2js.gemspec", "README.md", "lib/ruby2js", "lib/ruby2js/version.rb", "lib/ruby2js/converter.rb", "lib/ruby2js/filter", "lib/ruby2js/filter/return.rb", "lib/ruby2js/filter/angularrb.rb", "lib/ruby2js/filter/functions.rb", "lib/ruby2js.rb"]
12
+ s.files = ["ruby2js.gemspec", "README.md", "lib/ruby2js", "lib/ruby2js/version.rb", "lib/ruby2js/converter.rb", "lib/ruby2js/filter", "lib/ruby2js/filter/return.rb", "lib/ruby2js/filter/angularrb.rb", "lib/ruby2js/filter/functions.rb", "lib/ruby2js/filter/jquery.rb", "lib/ruby2js.rb"]
13
13
  s.homepage = "http://github.com/rubys/ruby2js"
14
14
  s.licenses = ["MIT"]
15
15
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby2js
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-12 00:00:00.000000000 Z
11
+ date: 2013-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  description: ! " The base package maps Ruby syntax to JavaScript semantics.\n Filters
28
- may be provided to add Ruby-specific or Framework specific\n behavior.\n"
28
+ may be provided to add Ruby-specific or framework specific\n behavior.\n"
29
29
  email: rubys@intertwingly.net
30
30
  executables: []
31
31
  extensions: []
@@ -38,6 +38,7 @@ files:
38
38
  - lib/ruby2js/filter/return.rb
39
39
  - lib/ruby2js/filter/angularrb.rb
40
40
  - lib/ruby2js/filter/functions.rb
41
+ - lib/ruby2js/filter/jquery.rb
41
42
  - lib/ruby2js.rb
42
43
  homepage: http://github.com/rubys/ruby2js
43
44
  licenses: