ruby2js 0.1.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 +15 -0
- data/README.md +52 -0
- data/lib/ruby2js.rb +79 -0
- data/lib/ruby2js/converter.rb +281 -0
- data/lib/ruby2js/filter/angularrb.rb +167 -0
- data/lib/ruby2js/filter/functions.rb +41 -0
- data/lib/ruby2js/filter/return.rb +63 -0
- data/lib/ruby2js/version.rb +10 -0
- data/ruby2js.gemspec +29 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MThiMDkwZGYwN2EwNTUzNmM3ODY3NGExMzcwMDllMTUzODJmNWQ2MQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MzA3ODMzOWM1ZDcwMmI2YzhiNzFmZjdlNDE4NTU2ZGQ5OTkwZmIwNw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZWI1ODMwODg3YzgxODhhZDZjMjRmYzAxM2RmMDU5MGUzZmQ1OTUyNDY5NDdi
|
10
|
+
NDgyODdkNjFlNjZmMWU3MDE1ZTYyZGY5YTJmM2NmN2Y3NjVhNDRmMjVkMjYw
|
11
|
+
NmFhMDVjZWMwNGEwZDVhNWE3MzQyNmRlNjI2YjI4YmQ5MzgyNzY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NGU1YTAwOWZiODUyMWJmOTM3YTYyMjNmNTEwNzkwNDExNjBmZjAwMjI2ODky
|
14
|
+
YzhmNmRhMjQxYjk0ZGJjZjZlODFkZDc1MjVjNzIzZTExMThkNDBlZTRhMjM3
|
15
|
+
ZDgyZDBiZjQ2OGIxM2NmNmMwZmQ3ZjUyYWVmY2JjNzM3NzA3M2M=
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
ruby2js
|
2
|
+
=======
|
3
|
+
|
4
|
+
Minimal yet extensible Ruby to JavaScript conversion.
|
5
|
+
|
6
|
+
Description
|
7
|
+
---
|
8
|
+
|
9
|
+
The base package maps Ruby syntax to JavaScript semantics. For example,
|
10
|
+
a Ruby Hash literal becomes a JavaScript Object literal. Ruby symbols
|
11
|
+
become JavaScript strings. Ruby method calls become JavaScript function
|
12
|
+
calls IF there are either one or more arguments passed OR parenthesis are
|
13
|
+
used, otherwise Ruby method calls become JavaScript property accesses.
|
14
|
+
By default, methods, lambdas, and procs return `undefined`.
|
15
|
+
|
16
|
+
Filters may be provided to add Ruby-specific or Framework specific
|
17
|
+
behavior. Filters are essentially macro facilities that operate on
|
18
|
+
an AST representation of the code.
|
19
|
+
|
20
|
+
Synopsis
|
21
|
+
---
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'ruby2js/filter/functions'
|
25
|
+
puts Ruby2JS.convert('"2A".to_i(16)', filters: [Ruby2JS::Filter::Functions])
|
26
|
+
```
|
27
|
+
|
28
|
+
License
|
29
|
+
---
|
30
|
+
|
31
|
+
(The MIT License)
|
32
|
+
|
33
|
+
Copyright (c) 2009,2013 Macario Ortega, Sam Ruby
|
34
|
+
|
35
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
36
|
+
a copy of this software and associated documentation files (the
|
37
|
+
'Software'), to deal in the Software without restriction, including
|
38
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
39
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
40
|
+
permit persons to whom the Software is furnished to do so, subject to
|
41
|
+
the following conditions:
|
42
|
+
|
43
|
+
The above copyright notice and this permission notice shall be
|
44
|
+
included in all copies or substantial portions of the Software.
|
45
|
+
|
46
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
47
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
48
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
49
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
50
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
51
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
52
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/ruby2js.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
require 'ruby2js/converter'
|
3
|
+
|
4
|
+
module Ruby2JS
|
5
|
+
def self.convert(source, options={})
|
6
|
+
|
7
|
+
if Proc === source
|
8
|
+
file,line = source.source_location
|
9
|
+
source = File.read(file)
|
10
|
+
ast = find_block( parse(source), line )
|
11
|
+
elsif Parser::AST::Node === source
|
12
|
+
ast = source
|
13
|
+
source = ast.loc.expression.source_buffer.source
|
14
|
+
else
|
15
|
+
ast = parse( source )
|
16
|
+
end
|
17
|
+
|
18
|
+
if options[:filters]
|
19
|
+
filter = Parser::AST::Processor
|
20
|
+
options[:filters].reverse.each do |mod|
|
21
|
+
filter = Class.new(filter) {include mod}
|
22
|
+
end
|
23
|
+
ast = filter.new.process(ast)
|
24
|
+
end
|
25
|
+
|
26
|
+
ruby2js = Ruby2JS::Converter.new( ast )
|
27
|
+
|
28
|
+
if source.include? "\n"
|
29
|
+
ruby2js.enable_vertical_whitespace
|
30
|
+
lines = ruby2js.to_js.split("\n")
|
31
|
+
pre = ''
|
32
|
+
pending = false
|
33
|
+
blank = true
|
34
|
+
lines.each do |line|
|
35
|
+
if line.start_with? '}' or line.start_with? ']'
|
36
|
+
pre.sub!(/^ /,'')
|
37
|
+
line.sub!(/;$/,";\n")
|
38
|
+
pending = true
|
39
|
+
else
|
40
|
+
pending = false
|
41
|
+
end
|
42
|
+
|
43
|
+
line.sub! /^/, pre
|
44
|
+
if line.end_with? '{' or line.end_with? '['
|
45
|
+
pre += ' '
|
46
|
+
line.sub!(/^/,"\n") unless blank or pending
|
47
|
+
pending = true
|
48
|
+
end
|
49
|
+
|
50
|
+
blank = pending
|
51
|
+
end
|
52
|
+
lines.join("\n")
|
53
|
+
else
|
54
|
+
ruby2js.to_js
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.parse(source)
|
59
|
+
# workaround for https://github.com/whitequark/parser/issues/112
|
60
|
+
buffer = Parser::Source::Buffer.new('__SOURCE__')
|
61
|
+
buffer.raw_source = source.encode('utf-8')
|
62
|
+
Parser::CurrentRuby.new.parse(buffer)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.find_block(ast, line)
|
66
|
+
if ast.type == :block and ast.loc.expression.line == line
|
67
|
+
return ast.children.last
|
68
|
+
end
|
69
|
+
|
70
|
+
ast.children.each do |child|
|
71
|
+
if Parser::AST::Node === child
|
72
|
+
block = find_block child, line
|
73
|
+
return block if block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
module Ruby2JS
|
4
|
+
class Converter
|
5
|
+
LOGICAL = :and, :not, :or
|
6
|
+
OPERATORS = [:[], :[]=], [:not, :!], [:*, :/, :%], [:+, :-], [:>>, :<<],
|
7
|
+
[:<=, :<, :>, :>=], [:==, :!=], [:and, :or]
|
8
|
+
|
9
|
+
def initialize( ast, vars = {} )
|
10
|
+
@ast, @vars = ast, vars.dup
|
11
|
+
@sep = '; '
|
12
|
+
@nl = ''
|
13
|
+
end
|
14
|
+
|
15
|
+
def enable_vertical_whitespace
|
16
|
+
@sep = ";\n"
|
17
|
+
@nl = "\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_js
|
21
|
+
parse( @ast, :statement )
|
22
|
+
end
|
23
|
+
|
24
|
+
def operator_index op
|
25
|
+
OPERATORS.index( OPERATORS.find{ |el| el.include? op } ) || -1
|
26
|
+
end
|
27
|
+
|
28
|
+
def scope( ast )
|
29
|
+
frame = self.class.new( nil, @vars )
|
30
|
+
frame.enable_vertical_whitespace if @nl == "\n"
|
31
|
+
frame.parse( ast, :statement )
|
32
|
+
end
|
33
|
+
|
34
|
+
def s(type, *args)
|
35
|
+
Parser::AST::Node.new(type, args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_method?(node)
|
39
|
+
return false unless node.type == :send
|
40
|
+
return true unless node.loc
|
41
|
+
selector = node.loc.selector
|
42
|
+
return true unless selector.source_buffer
|
43
|
+
selector.source_buffer.source[selector.end_pos] == '('
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(ast, state=:expression)
|
47
|
+
return ast unless Parser::AST::Node === ast
|
48
|
+
|
49
|
+
case ast.type
|
50
|
+
|
51
|
+
when :int, :float, :str
|
52
|
+
ast.children.first.inspect
|
53
|
+
|
54
|
+
when :sym
|
55
|
+
ast.children.first.to_s.inspect
|
56
|
+
|
57
|
+
when :lvar, :gvar
|
58
|
+
ast.children.first
|
59
|
+
|
60
|
+
when :true, :false
|
61
|
+
ast.type.to_s
|
62
|
+
|
63
|
+
when :nil
|
64
|
+
'null'
|
65
|
+
|
66
|
+
when :lvasgn
|
67
|
+
var, value = ast.children
|
68
|
+
output = value ? "#{ 'var ' unless @vars.keys.include? var }#{ var } = #{ parse value }" : var
|
69
|
+
@vars[var] = true
|
70
|
+
output
|
71
|
+
|
72
|
+
when :op_asgn
|
73
|
+
var, op, value = ast.children
|
74
|
+
|
75
|
+
if [:+, :-].include?(op) and value.type==:int and value.children==[1]
|
76
|
+
if state == :statement
|
77
|
+
"#{ parse var }#{ op }#{ op }"
|
78
|
+
else
|
79
|
+
"#{ op }#{ op }#{ parse var }"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
"#{ parse var } #{ op }= #{ parse value }"
|
83
|
+
end
|
84
|
+
|
85
|
+
when :casgn
|
86
|
+
cbase, var, value = ast.children
|
87
|
+
var = "#{cbase}.var" if cbase
|
88
|
+
output = "const #{ var } = #{ parse value }"
|
89
|
+
@vars[var] = true
|
90
|
+
output
|
91
|
+
|
92
|
+
when :gvasgn
|
93
|
+
name, value = ast.children
|
94
|
+
"#{ name } = #{ parse value }"
|
95
|
+
|
96
|
+
when :ivasgn
|
97
|
+
name, expression = ast.children
|
98
|
+
"#{ name.to_s.sub('@', 'this._') } = #{ parse expression }"
|
99
|
+
|
100
|
+
when :or_asgn
|
101
|
+
var, value = ast.children
|
102
|
+
"#{ parse var } = #{parse var} || #{ parse value }"
|
103
|
+
|
104
|
+
when :and_asgn
|
105
|
+
var, value = ast.children
|
106
|
+
"#{ parse var } = #{parse var} && #{ parse value }"
|
107
|
+
|
108
|
+
when :ivar
|
109
|
+
name = ast.children.first
|
110
|
+
name.to_s.sub('@', 'this._')
|
111
|
+
|
112
|
+
when :hash
|
113
|
+
hashy = ast.children.map do |node|
|
114
|
+
left, right = node.children
|
115
|
+
key = parse left
|
116
|
+
key = $1 if key =~ /\A"([a-zA-Z_$][a-zA-Z_$0-9]*)"\Z/
|
117
|
+
"#{key}: #{parse right}"
|
118
|
+
end
|
119
|
+
"{#{ hashy.join(', ') }}"
|
120
|
+
|
121
|
+
when :array
|
122
|
+
list = ast.children.map { |a| parse a }
|
123
|
+
if list.join(', ').length < 80
|
124
|
+
"[#{ list.join(', ') }]"
|
125
|
+
else
|
126
|
+
"[\n#{ list.join(",\n") }\n]"
|
127
|
+
end
|
128
|
+
|
129
|
+
when :begin
|
130
|
+
ast.children.map{ |e| parse e, :statement }.join(@sep)
|
131
|
+
|
132
|
+
when :return
|
133
|
+
"return #{ parse ast.children.first }"
|
134
|
+
|
135
|
+
when *LOGICAL
|
136
|
+
left, right = ast.children
|
137
|
+
left = left.children.first if left and left.type == :begin
|
138
|
+
right = right.children.first if right.type == :begin
|
139
|
+
op_index = operator_index ast.type
|
140
|
+
lgroup = LOGICAL.include?( left.type ) && op_index <= operator_index( left.type )
|
141
|
+
left = parse left
|
142
|
+
left = "(#{ left })" if lgroup
|
143
|
+
rgroup = LOGICAL.include?( right.type ) && op_index <= operator_index( right.type ) if right.children.length > 0
|
144
|
+
right = parse right
|
145
|
+
right = "(#{ right })" if rgroup
|
146
|
+
|
147
|
+
case ast.type
|
148
|
+
when :and
|
149
|
+
"#{ left } && #{ right }"
|
150
|
+
when :or
|
151
|
+
"#{ left } || #{ right }"
|
152
|
+
else
|
153
|
+
"!#{ left }"
|
154
|
+
end
|
155
|
+
|
156
|
+
when :send, :attr
|
157
|
+
receiver, method, *args = ast.children
|
158
|
+
if method == :new and receiver and receiver.children == [nil, :Proc]
|
159
|
+
return parse args.first
|
160
|
+
elsif method == :lambda and not receiver
|
161
|
+
return parse args.first
|
162
|
+
end
|
163
|
+
|
164
|
+
op_index = operator_index method
|
165
|
+
if op_index != -1
|
166
|
+
target = args.first
|
167
|
+
target = target.children.first if target and target.type == :begin
|
168
|
+
receiver = receiver.children.first if receiver.type == :begin
|
169
|
+
end
|
170
|
+
|
171
|
+
group_receiver = receiver.type == :send && op_index <= operator_index( receiver.children[1] ) if receiver
|
172
|
+
group_target = target.type == :send && op_index <= operator_index( target.children[1] ) if target
|
173
|
+
|
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 })"
|
181
|
+
|
182
|
+
when :[]
|
183
|
+
raise 'parse error' unless receiver
|
184
|
+
"#{ parse receiver }[#{ parse args.first }]"
|
185
|
+
|
186
|
+
when :-@, :+@
|
187
|
+
"#{ method.to_s[0] }#{ parse receiver }"
|
188
|
+
|
189
|
+
when *OPERATORS.flatten
|
190
|
+
"#{ group_receiver ? group(receiver) : parse(receiver) } #{ method } #{ group_target ? group(target) : parse(target) }"
|
191
|
+
|
192
|
+
when /=$/
|
193
|
+
"#{ parse receiver }#{ '.' if receiver }#{ method.to_s.sub(/=$/, ' =') } #{ parse args.first }"
|
194
|
+
|
195
|
+
else
|
196
|
+
if args.length == 0 and not is_method?(ast)
|
197
|
+
"#{ parse receiver }#{ '.' if receiver }#{ method }"
|
198
|
+
else
|
199
|
+
args = args.map {|a| parse a}.join(', ')
|
200
|
+
"#{ parse receiver }#{ '.' if receiver }#{ method }(#{ args })"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
when :const
|
205
|
+
receiver, name = ast.children
|
206
|
+
"#{ parse receiver }#{ '.' if receiver }#{ name }"
|
207
|
+
|
208
|
+
when :masgn
|
209
|
+
lhs, rhs = ast.children
|
210
|
+
block = []
|
211
|
+
lhs.children.zip rhs.children.zip do |var, val|
|
212
|
+
block << s(var.type, *var.children, *val)
|
213
|
+
end
|
214
|
+
parse s(:begin, *block)
|
215
|
+
|
216
|
+
when :if
|
217
|
+
condition, true_block, else_block = ast.children
|
218
|
+
if state == :statement
|
219
|
+
output = "if (#{ parse condition }) {#@nl#{ scope true_block }#@nl}"
|
220
|
+
while else_block and else_block.type == :if
|
221
|
+
condition, true_block, else_block = else_block.children
|
222
|
+
output << " else if (#{ parse condition }) {#@nl#{ scope true_block }#@nl}"
|
223
|
+
end
|
224
|
+
output << " else {#@nl#{ scope else_block }#@nl}" if else_block
|
225
|
+
output
|
226
|
+
else
|
227
|
+
"(#{ parse condition } ? #{ parse true_block } : #{ parse else_block })"
|
228
|
+
end
|
229
|
+
|
230
|
+
when :while
|
231
|
+
condition, block = ast.children
|
232
|
+
"while (#{ parse condition }) {#@nl#{ scope block }#@nl}"
|
233
|
+
|
234
|
+
when :block
|
235
|
+
call, args, block = ast.children
|
236
|
+
block ||= s(:begin)
|
237
|
+
function = s(:def, name, args, block)
|
238
|
+
parse s(:send, *call.children, function)
|
239
|
+
|
240
|
+
when :def
|
241
|
+
name, args, body = ast.children
|
242
|
+
body ||= s(:begin)
|
243
|
+
body = s(:scope, body) unless body.type == :scope
|
244
|
+
body = parse body
|
245
|
+
body.sub! /return var (\w+) = ([^;]+)\z/, "var \\1 = \\2#{@sep}return \\1"
|
246
|
+
"function#{ " #{name}" if name }(#{ parse args }) {#@nl#{ body }#@nl}"
|
247
|
+
|
248
|
+
when :scope
|
249
|
+
body = ast.children.first
|
250
|
+
body = s(:begin, body) unless body.type == :begin
|
251
|
+
block = body.children
|
252
|
+
scope body
|
253
|
+
|
254
|
+
when :class
|
255
|
+
name, inheritance, *body = ast.children
|
256
|
+
body.compact!
|
257
|
+
body = body.first.children.dup if body.length == 1 and body.first.type == :begin
|
258
|
+
methods = body.select { |a| a.type == :def }
|
259
|
+
init = (body.delete methods.find { |m| m.children.first == :initialize }) || s(:def, :initialize)
|
260
|
+
block = body.collect { |m| parse( m ).sub(/function (\w+)/, "#{ parse name }.prototype.\\1 = function") }.join @sep
|
261
|
+
"#{ parse( s(:def, parse(name), init.children[1], init.children[2]) ).sub(/return (?:null|(.*))\}\z/, '\1}') }#{ @sep if block and not block.empty?}#{ block }"
|
262
|
+
|
263
|
+
when :args
|
264
|
+
ast.children.map { |a| a.children.first }.join(', ')
|
265
|
+
|
266
|
+
when :dstr
|
267
|
+
ast.children.map{ |s| parse s }.join(' + ')
|
268
|
+
|
269
|
+
when :self
|
270
|
+
'this'
|
271
|
+
|
272
|
+
else
|
273
|
+
raise "unknown AST type #{ ast.type }"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def group( ast )
|
278
|
+
"(#{ parse ast })"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
require 'ruby2js'
|
3
|
+
|
4
|
+
module Ruby2JS
|
5
|
+
module Filter
|
6
|
+
module AngularRB
|
7
|
+
def initialize(*args)
|
8
|
+
@ngApp = nil
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# input:
|
13
|
+
# module Angular::AppName
|
14
|
+
# use :Dependency
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# output:
|
19
|
+
# AppName = angular.module("AppName", ["Dependency"])
|
20
|
+
# ...
|
21
|
+
|
22
|
+
def on_module(node)
|
23
|
+
module_name = node.children[0]
|
24
|
+
parent_name = module_name.children[0]
|
25
|
+
|
26
|
+
return super unless parent_name and parent_name.type == :const
|
27
|
+
return super unless parent_name.children == [nil, :Angular]
|
28
|
+
|
29
|
+
@ngApp = module_name.children[1]
|
30
|
+
|
31
|
+
# find the block
|
32
|
+
block = process_all(node.children[1..-1])
|
33
|
+
while block.length == 1 and block.first.type == :begin
|
34
|
+
block = block.first.children.dup
|
35
|
+
end
|
36
|
+
|
37
|
+
# find use class method calls
|
38
|
+
uses = block.find_all do |node|
|
39
|
+
node.type == :send and node.children[0..1] == [nil, :use]
|
40
|
+
end
|
41
|
+
|
42
|
+
# convert use calls into dependencies
|
43
|
+
depends = []
|
44
|
+
uses.each do |use|
|
45
|
+
pending = []
|
46
|
+
use.children[2..-1].each do |node|
|
47
|
+
break unless [:str, :sym].include? node.type
|
48
|
+
pending << node
|
49
|
+
end
|
50
|
+
depends += pending
|
51
|
+
block.delete use
|
52
|
+
end
|
53
|
+
|
54
|
+
# build constant assignment statement
|
55
|
+
casgn = s(:casgn, nil, @ngApp, s(:send,
|
56
|
+
s(:lvar, :angular),
|
57
|
+
:module,
|
58
|
+
s(:str, @ngApp.to_s),
|
59
|
+
s(:array, *depends)))
|
60
|
+
|
61
|
+
@ngApp = nil
|
62
|
+
|
63
|
+
# replace module with a constant assign followed by the module contents
|
64
|
+
node.updated :begin, [casgn, *block]
|
65
|
+
end
|
66
|
+
|
67
|
+
# input:
|
68
|
+
# class Name < Angular::Controller
|
69
|
+
# use :$service
|
70
|
+
# ...
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# output:
|
74
|
+
# AppName.controller :Name do |$service|
|
75
|
+
# ...
|
76
|
+
# end
|
77
|
+
|
78
|
+
def on_class(node)
|
79
|
+
return super unless @ngApp
|
80
|
+
return super unless node.children.length == 3
|
81
|
+
|
82
|
+
# (const nil :Name)
|
83
|
+
name = node.children.first
|
84
|
+
return super unless name.type == :const and name.children.first == nil
|
85
|
+
|
86
|
+
# (const (const nil :Angular) :Controller)
|
87
|
+
parent = node.children[1]
|
88
|
+
return super unless parent and parent.children.length == 2
|
89
|
+
return super unless parent.children[0]
|
90
|
+
return super unless parent.children[0].type == :const
|
91
|
+
return super unless parent.children[0].children == [nil, :Angular]
|
92
|
+
return super unless [:Controller].include? parent.children[1]
|
93
|
+
|
94
|
+
# find the block
|
95
|
+
block = process_all(node.children[2..-1])
|
96
|
+
while block.length == 1 and block.first.type == :begin
|
97
|
+
block = block.first.children.dup
|
98
|
+
end
|
99
|
+
|
100
|
+
# find use class method calls
|
101
|
+
uses = block.find_all do |node|
|
102
|
+
node.type == :send and node.children[0..1] == [nil, :use]
|
103
|
+
end
|
104
|
+
|
105
|
+
# convert use calls into args
|
106
|
+
args = []
|
107
|
+
uses.each do |use|
|
108
|
+
pending = []
|
109
|
+
use.children[2..-1].each do |node|
|
110
|
+
break unless [:str, :sym].include? node.type
|
111
|
+
pending << s(:arg, *node.children)
|
112
|
+
end
|
113
|
+
args += pending
|
114
|
+
block.delete use
|
115
|
+
end
|
116
|
+
|
117
|
+
# build Appname.controller call statement
|
118
|
+
call = s(:send,
|
119
|
+
s(:const, nil, @ngApp),
|
120
|
+
:controller,
|
121
|
+
s(:sym, name.children.last))
|
122
|
+
|
123
|
+
# replace class with a block
|
124
|
+
node.updated :block, [call, s(:args, *args), s(:begin, *block)]
|
125
|
+
end
|
126
|
+
|
127
|
+
# input:
|
128
|
+
# filter :name do |input|
|
129
|
+
# ...
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# output:
|
133
|
+
# AppName.filter :name do
|
134
|
+
# return lambda {|input| return ... }
|
135
|
+
# end
|
136
|
+
def on_block(node)
|
137
|
+
return super unless @ngApp
|
138
|
+
|
139
|
+
call = node.children.first
|
140
|
+
return super unless call.children[0..1] == [nil, :filter]
|
141
|
+
|
142
|
+
# insert return
|
143
|
+
children = process_all(node.children[1..-1])
|
144
|
+
block = [children.pop || s(:nil)]
|
145
|
+
while block.length == 1 and block.first.type == :begin
|
146
|
+
block = block.first.children.dup
|
147
|
+
end
|
148
|
+
block.push s(:return, block.pop) unless block.last.type == :return
|
149
|
+
children.push (block.length == 1 ? block.first : s(:begin, *block))
|
150
|
+
|
151
|
+
# construct a function returning a function
|
152
|
+
inner = s(:block, s(:send, nil, :lambda), *children)
|
153
|
+
outer = s(:send, s(:lvar, @ngApp), :filter, *call.children[2..-1])
|
154
|
+
|
155
|
+
node.updated nil, [outer, s(:args), s(:return, inner)]
|
156
|
+
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
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'ruby2js'
|
2
|
+
|
3
|
+
module Ruby2JS
|
4
|
+
module Filter
|
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
|
15
|
+
|
16
|
+
def on_send(node)
|
17
|
+
if node.children[1] == :to_s
|
18
|
+
s(:send, node.children[0], :toString, *node.children[2..-1])
|
19
|
+
|
20
|
+
elsif node.children[1] == :to_i
|
21
|
+
node.updated nil, [nil, :parseInt, node.children[0],
|
22
|
+
*node.children[2..-1]]
|
23
|
+
|
24
|
+
elsif node.children[1] == :to_f
|
25
|
+
node.updated nil, [nil, :parseFloat, node.children[0],
|
26
|
+
*node.children[2..-1]]
|
27
|
+
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# construct an AST Node
|
36
|
+
def s(type, *args)
|
37
|
+
Parser::AST::Node.new type, args
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ruby2js'
|
2
|
+
|
3
|
+
module Ruby2JS
|
4
|
+
module Filter
|
5
|
+
module Return
|
6
|
+
EXPRESSIONS = [ :array, :float, :hash, :if, :int, :lvar, :nil, :send ]
|
7
|
+
|
8
|
+
def on_block(node)
|
9
|
+
children = process_all(node.children)
|
10
|
+
|
11
|
+
# find the block
|
12
|
+
block = [children.pop || s(:nil)]
|
13
|
+
while block.length == 1 and block.first.type == :begin
|
14
|
+
block = block.first.children.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
if EXPRESSIONS.include? block.last.type
|
18
|
+
block.push s(:return, block.pop)
|
19
|
+
else
|
20
|
+
p block.last.type
|
21
|
+
end
|
22
|
+
|
23
|
+
if block.length == 1
|
24
|
+
children.push block.first
|
25
|
+
else
|
26
|
+
children.push s(:begin, *block)
|
27
|
+
end
|
28
|
+
|
29
|
+
node.updated nil, children
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_def(node)
|
33
|
+
children = process_all(node.children[1..-1])
|
34
|
+
children.unshift node.children.first
|
35
|
+
|
36
|
+
# find the block
|
37
|
+
block = [children.pop || s(:nil)]
|
38
|
+
while block.length == 1 and block.first.type == :begin
|
39
|
+
block = block.first.children.dup
|
40
|
+
end
|
41
|
+
|
42
|
+
if EXPRESSIONS.include? block.last.type
|
43
|
+
block.push s(:return, block.pop)
|
44
|
+
end
|
45
|
+
|
46
|
+
if block.length == 1
|
47
|
+
children.push block.first
|
48
|
+
else
|
49
|
+
children.push s(:begin, *block)
|
50
|
+
end
|
51
|
+
|
52
|
+
node.updated nil, children
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# construct an AST Node
|
58
|
+
def s(type, *args)
|
59
|
+
Parser::AST::Node.new type, args
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/ruby2js.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "ruby2js"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Sam Ruby"]
|
9
|
+
s.date = "2013-11-11"
|
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
|
+
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"]
|
13
|
+
s.homepage = "http://github.com/rubys/ruby2js"
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.rubygems_version = "2.0.7"
|
16
|
+
s.summary = "Minimal yet extensible Ruby to JavaScript conversion."
|
17
|
+
|
18
|
+
if s.respond_to? :specification_version then
|
19
|
+
s.specification_version = 4
|
20
|
+
|
21
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
22
|
+
s.add_runtime_dependency(%q<parser>, [">= 0"])
|
23
|
+
else
|
24
|
+
s.add_dependency(%q<parser>, [">= 0"])
|
25
|
+
end
|
26
|
+
else
|
27
|
+
s.add_dependency(%q<parser>, [">= 0"])
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby2js
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sam Ruby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
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"
|
29
|
+
email: rubys@intertwingly.net
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ruby2js.gemspec
|
35
|
+
- README.md
|
36
|
+
- lib/ruby2js/version.rb
|
37
|
+
- lib/ruby2js/converter.rb
|
38
|
+
- lib/ruby2js/filter/return.rb
|
39
|
+
- lib/ruby2js/filter/angularrb.rb
|
40
|
+
- lib/ruby2js/filter/functions.rb
|
41
|
+
- lib/ruby2js.rb
|
42
|
+
homepage: http://github.com/rubys/ruby2js
|
43
|
+
licenses: []
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.0.7
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Minimal yet extensible Ruby to JavaScript conversion.
|
65
|
+
test_files: []
|