ruby2js 0.2.0 → 1.0.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.
- checksums.yaml +8 -8
- data/README.md +71 -1
- data/lib/ruby2js.rb +12 -2
- data/lib/ruby2js/converter.rb +295 -44
- data/lib/ruby2js/filter/angularrb.rb +4 -9
- data/lib/ruby2js/filter/functions.rb +65 -19
- data/lib/ruby2js/filter/jquery.rb +37 -0
- data/lib/ruby2js/filter/return.rb +2 -7
- data/lib/ruby2js/version.rb +2 -2
- data/ruby2js.gemspec +4 -4
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NWUwYjJjY2RiMjE2MDhkMjdlMDVkODQ4MDFlNWUwYWZjODg3NjUwNA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NGU5ZWI1ODU4MThkMTFjOTZjNDZmNDlkYzg2Mjc4NzIxMjNjZDk5MA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ODA4MGVkMTZlZjU0MTA5YWM4YTg1OGNhMThlNTg0Nzc4NTViNjI2NDZhZDkx
|
10
|
+
M2YyZmY2YTkxMTE5ZDM5OTA0OGQ4NmNmNzRkNjU2MWJiMjA3MGIwMmExZTFh
|
11
|
+
YTM5OWE3MjExN2NhOGI2ZWJhYWNmYjFjYTc2Y2Y4OWZkYmQyOGY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
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
|
|
data/lib/ruby2js.rb
CHANGED
@@ -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
|
-
|
67
|
+
|
68
|
+
lines.join("\n").gsub(/^ ( *(case.*|default):$)/, '\1')
|
59
69
|
else
|
60
70
|
ruby2js.to_js
|
61
71
|
end
|
data/lib/ruby2js/converter.rb
CHANGED
@@ -4,17 +4,26 @@ module Ruby2JS
|
|
4
4
|
class Converter
|
5
5
|
LOGICAL = :and, :not, :or
|
6
6
|
OPERATORS = [:[], :[]=], [:not, :!], [:*, :/, :%], [:+, :-], [:>>, :<<],
|
7
|
-
[:<=, :<, :>, :>=], [:==,
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
123
|
-
if
|
124
|
-
|
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
|
-
|
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
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
183
|
-
|
184
|
-
"#{ parse receiver }[#{ parse args.first }]"
|
226
|
+
elsif method == :[]
|
227
|
+
"#{ parse receiver }[#{ args.map {|arg| parse arg}.join(', ') }]"
|
185
228
|
|
186
|
-
|
229
|
+
elsif method == :-@ or method == :+@
|
187
230
|
"#{ method.to_s[0] }#{ parse receiver }"
|
188
|
-
|
189
|
-
|
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
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
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
|
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
|
data/lib/ruby2js/version.rb
CHANGED
data/ruby2js.gemspec
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "ruby2js"
|
5
|
-
s.version = "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-
|
10
|
-
s.description = " The base package maps Ruby syntax to JavaScript semantics.\n Filters may be provided to add Ruby-specific or
|
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.
|
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-
|
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
|
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:
|