ruby2js 1.15.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/lib/ruby2js.rb +36 -36
  2. data/lib/ruby2js/converter.rb +59 -20
  3. data/lib/ruby2js/converter/arg.rb +1 -1
  4. data/lib/ruby2js/converter/args.rb +1 -1
  5. data/lib/ruby2js/converter/array.rb +3 -4
  6. data/lib/ruby2js/converter/begin.rb +15 -1
  7. data/lib/ruby2js/converter/block.rb +6 -5
  8. data/lib/ruby2js/converter/boolean.rb +1 -1
  9. data/lib/ruby2js/converter/break.rb +1 -1
  10. data/lib/ruby2js/converter/case.rb +27 -7
  11. data/lib/ruby2js/converter/casgn.rb +5 -2
  12. data/lib/ruby2js/converter/class.rb +41 -11
  13. data/lib/ruby2js/converter/const.rb +1 -1
  14. data/lib/ruby2js/converter/cvar.rb +4 -3
  15. data/lib/ruby2js/converter/cvasgn.rb +5 -6
  16. data/lib/ruby2js/converter/def.rb +15 -3
  17. data/lib/ruby2js/converter/defined.rb +1 -1
  18. data/lib/ruby2js/converter/defs.rb +7 -3
  19. data/lib/ruby2js/converter/dstr.rb +3 -3
  20. data/lib/ruby2js/converter/for.rb +7 -10
  21. data/lib/ruby2js/converter/hash.rb +70 -34
  22. data/lib/ruby2js/converter/if.rb +35 -13
  23. data/lib/ruby2js/converter/in.rb +1 -1
  24. data/lib/ruby2js/converter/ivasgn.rb +1 -1
  25. data/lib/ruby2js/converter/kwbegin.rb +20 -20
  26. data/lib/ruby2js/converter/literal.rb +1 -1
  27. data/lib/ruby2js/converter/logical.rb +4 -8
  28. data/lib/ruby2js/converter/next.rb +1 -1
  29. data/lib/ruby2js/converter/nil.rb +1 -1
  30. data/lib/ruby2js/converter/nthref.rb +1 -1
  31. data/lib/ruby2js/converter/opasgn.rb +3 -3
  32. data/lib/ruby2js/converter/regexp.rb +12 -9
  33. data/lib/ruby2js/converter/return.rb +3 -3
  34. data/lib/ruby2js/converter/self.rb +2 -2
  35. data/lib/ruby2js/converter/send.rb +31 -30
  36. data/lib/ruby2js/converter/super.rb +8 -11
  37. data/lib/ruby2js/converter/sym.rb +1 -1
  38. data/lib/ruby2js/converter/undef.rb +9 -2
  39. data/lib/ruby2js/converter/var.rb +1 -1
  40. data/lib/ruby2js/converter/vasgn.rb +13 -5
  41. data/lib/ruby2js/converter/while.rb +2 -1
  42. data/lib/ruby2js/converter/whilepost.rb +2 -1
  43. data/lib/ruby2js/converter/xstr.rb +4 -3
  44. data/lib/ruby2js/execjs.rb +3 -3
  45. data/lib/ruby2js/filter/camelCase.rb +8 -8
  46. data/lib/ruby2js/filter/functions.rb +64 -65
  47. data/lib/ruby2js/filter/react.rb +44 -16
  48. data/lib/ruby2js/filter/require.rb +4 -1
  49. data/lib/ruby2js/filter/underscore.rb +21 -21
  50. data/lib/ruby2js/serializer.rb +347 -0
  51. data/lib/ruby2js/version.rb +3 -3
  52. data/ruby2js.gemspec +3 -3
  53. metadata +3 -2
@@ -45,7 +45,10 @@ module Ruby2JS
45
45
  end
46
46
 
47
47
  @options[:file2] = filename
48
- process Ruby2JS.parse(File.read(filename), filename)
48
+ ast, comments = Ruby2JS.parse(File.read(filename), filename)
49
+ @comments.merge! Parser::Source::Comment.associate(ast, comments)
50
+ @comments[node] += @comments[ast]
51
+ process ast
49
52
  ensure
50
53
  if file2
51
54
  @options[:file2] = file2
@@ -11,42 +11,42 @@ module Ruby2JS
11
11
  if [:clone, :shuffle, :size, :compact, :flatten, :invert, :values,
12
12
  :uniq].include? node.children[1]
13
13
  if node.is_method? and node.children.length == 2
14
- process s(:send, s(:lvar, :_), node.children[1], node.children[0])
14
+ process S(:send, s(:lvar, :_), node.children[1], node.children[0])
15
15
  else
16
16
  super
17
17
  end
18
18
  elsif node.children[1] == :sample and node.children.length <= 3
19
- process s(:send, s(:lvar, :_), :sample, node.children[0],
19
+ process S(:send, s(:lvar, :_), :sample, node.children[0],
20
20
  *node.children[2..-1])
21
21
  elsif node.children[1] == :has_key? and node.children.length == 3
22
- process s(:send, s(:lvar, :_), :has, node.children[0],
22
+ process S(:send, s(:lvar, :_), :has, node.children[0],
23
23
  node.children[2])
24
24
  elsif node.children[1] == :sort and node.children.length == 2
25
25
  if node.is_method?
26
- process s(:send, s(:lvar, :_), :sortBy, node.children[0],
26
+ process S(:send, s(:lvar, :_), :sortBy, node.children[0],
27
27
  s(:attr, s(:lvar, :_), :identity))
28
28
  else
29
29
  super
30
30
  end
31
31
  elsif node.children[1] == :map
32
32
  if node.children.length == 3 and node.children[2].type == :block_pass
33
- process s(:send, s(:lvar, :_), :pluck, node.children[0],
33
+ process S(:send, s(:lvar, :_), :pluck, node.children[0],
34
34
  node.children[2].children.first)
35
35
  else
36
36
  super
37
37
  end
38
38
  elsif node.children[1] == :merge and node.children.length >= 3
39
- process s(:send, s(:lvar, :_), :extend, s(:hash), node.children[0],
39
+ process S(:send, s(:lvar, :_), :extend, s(:hash), node.children[0],
40
40
  *node.children[2..-1])
41
41
  elsif node.children[1] == :merge! and node.children.length >= 3
42
- process s(:send, s(:lvar, :_), :extend, node.children[0],
42
+ process S(:send, s(:lvar, :_), :extend, node.children[0],
43
43
  *node.children[2..-1])
44
44
  elsif node.children[1] == :zip and node.children.length >= 3
45
- process s(:send, s(:lvar, :_), :zip, node.children[0],
45
+ process S(:send, s(:lvar, :_), :zip, node.children[0],
46
46
  *node.children[2..-1])
47
47
  elsif node.children[1] == :invoke
48
48
  if node.children.length >= 3 and node.children.last.type==:block_pass
49
- process s(:send, s(:lvar, :_), :invoke, node.children[0],
49
+ process S(:send, s(:lvar, :_), :invoke, node.children[0],
50
50
  node.children.last.children.first,
51
51
  *node.children[2..-2])
52
52
  else
@@ -54,7 +54,7 @@ module Ruby2JS
54
54
  end
55
55
  elsif [:where, :find_by].include? node.children[1]
56
56
  method = node.children[1] == :where ? :where : :findWhere
57
- process s(:send, s(:lvar, :_), method, node.children[0],
57
+ process S(:send, s(:lvar, :_), method, node.children[0],
58
58
  *node.children[2..-1])
59
59
  elsif node.children[1] == :reduce
60
60
  if node.children.length == 3 and node.children[2].type == :sym
@@ -62,7 +62,7 @@ module Ruby2JS
62
62
  # output: _.reduce(_.rest(a),
63
63
  # proc {|memo, item| return memo+item},
64
64
  # a[0])
65
- process s(:send, s(:lvar, :_), :reduce,
65
+ process S(:send, s(:lvar, :_), :reduce,
66
66
  s(:send, s(:lvar, :_), :rest, node.children.first),
67
67
  s(:block, s(:send, nil, :proc),
68
68
  s(:args, s(:arg, :memo), s(:arg, :item)),
@@ -75,7 +75,7 @@ module Ruby2JS
75
75
  elsif node.children.length == 4 and node.children[3].type == :sym
76
76
  # input: a.reduce(n, :+)
77
77
  # output: _.reduce(a, proc {|memo, item| return memo+item}, n)
78
- process s(:send, s(:lvar, :_), :reduce, node.children.first,
78
+ process S(:send, s(:lvar, :_), :reduce, node.children.first,
79
79
  s(:block, s(:send, nil, :proc),
80
80
  s(:args, s(:arg, :memo), s(:arg, :item)),
81
81
  s(:autoreturn, s(:send, s(:lvar, :memo),
@@ -90,7 +90,7 @@ module Ruby2JS
90
90
  # input: a.compact!
91
91
  # output: a.splice(0, a.length, *a.compact)
92
92
  target = node.children.first
93
- process s(:send, target, :splice, s(:int, 0),
93
+ process S(:send, target, :splice, s(:int, 0),
94
94
  s(:attr, target, :length), s(:splat, s(:send, target,
95
95
  :"#{node.children[1].to_s[0..-2]}", *node.children[2..-1])))
96
96
  else
@@ -104,14 +104,14 @@ module Ruby2JS
104
104
  # input: a.sort_by {}
105
105
  # output: _.sortBy {return expression}
106
106
  method = call.children[1].to_s.sub(/\_by$/,'By').to_sym
107
- process s(:block, s(:send, s(:lvar, :_), method,
107
+ process S(:block, s(:send, s(:lvar, :_), method,
108
108
  call.children.first), node.children[1],
109
109
  s(:autoreturn, node.children[2]))
110
110
  elsif [:find, :reject].include? call.children[1]
111
111
  if call.children.length == 2
112
112
  # input: a.find {|item| item > 0}
113
113
  # output: _.find(a) {|item| return item > 0}
114
- process s(:block, s(:send, s(:lvar, :_), call.children[1],
114
+ process S(:block, s(:send, s(:lvar, :_), call.children[1],
115
115
  call.children.first), node.children[1],
116
116
  s(:autoreturn, node.children[2]))
117
117
  else
@@ -121,7 +121,7 @@ module Ruby2JS
121
121
  elsif call.children[1] == :times and call.children.length == 2
122
122
  # input: 5.times {|i| console.log i}
123
123
  # output: _.find(5) {|i| console.log(i)}
124
- process s(:block, s(:send, s(:lvar, :_), call.children[1],
124
+ process S(:block, s(:send, s(:lvar, :_), call.children[1],
125
125
  call.children.first), node.children[1], node.children[2])
126
126
 
127
127
  elsif call.children[1] == :reduce
@@ -130,7 +130,7 @@ module Ruby2JS
130
130
  # output: _.reduce(_.rest(a),
131
131
  # proc {|memo, item| return memo+item},
132
132
  # a[0])
133
- process s(:send, s(:lvar, :_), :reduce,
133
+ process S(:call, s(:lvar, :_), :reduce,
134
134
  s(:send, s(:lvar, :_), :rest, call.children.first),
135
135
  s(:block, s(:send, nil, :proc),
136
136
  node.children[1], s(:autoreturn, node.children[2])),
@@ -138,7 +138,7 @@ module Ruby2JS
138
138
  elsif call.children.length == 3
139
139
  # input: a.reduce(n) {|memo, item| memo+item}
140
140
  # output: _.reduce(a, proc {|memo, item| return memo+item}, n)
141
- process s(:send, s(:lvar, :_), :reduce, call.children.first,
141
+ process S(:call, s(:lvar, :_), :reduce, call.children.first,
142
142
  s(:block, s(:send, nil, :proc),
143
143
  node.children[1], s(:autoreturn, node.children[2])),
144
144
  call.children[2])
@@ -149,7 +149,7 @@ module Ruby2JS
149
149
  # output: a.splice(0, a.length, *a.map {expression})
150
150
  method = :"#{call.children[1].to_s[0..-2]}"
151
151
  target = call.children.first
152
- process s(:send, target, :splice, s(:splat, s(:send, s(:array,
152
+ process S(:call, target, :splice, s(:splat, s(:send, s(:array,
153
153
  s(:int, 0), s(:attr, target, :length)), :concat,
154
154
  s(:block, s(:send, target, method, *call.children[2..-1]),
155
155
  *node.children[1..-1]))))
@@ -165,10 +165,10 @@ module Ruby2JS
165
165
 
166
166
  def on_irange(node)
167
167
  if node.children.last.type == :int
168
- process s(:send, s(:lvar, :_), :range, node.children.first,
168
+ process S(:call, s(:lvar, :_), :range, node.children.first,
169
169
  s(:int, node.children.last.children.last+1))
170
170
  else
171
- process s(:send, s(:lvar, :_), :range, node.children.first,
171
+ process S(:call, s(:lvar, :_), :range, node.children.first,
172
172
  s(:send, node.children.last, :+, s(:int, 1)))
173
173
  end
174
174
  end
@@ -0,0 +1,347 @@
1
+ module Ruby2JS
2
+ class Token < String
3
+ attr_accessor :loc
4
+ attr_accessor :ast
5
+
6
+ def initialize(string, ast)
7
+ super(string.to_s)
8
+ @ast = ast
9
+ @loc = ast.location if ast
10
+ end
11
+ end
12
+
13
+ class Line < Array
14
+ attr_accessor :indent
15
+
16
+ def initialize(*args)
17
+ super(args)
18
+ @indent = 0
19
+ end
20
+
21
+ def comment?
22
+ first = find {|token| !token.empty?}
23
+ first and first.start_with? '//'
24
+ end
25
+
26
+ def empty?
27
+ all? {|line| line.empty?}
28
+ end
29
+
30
+ def to_s
31
+ if empty?
32
+ ''
33
+ elsif ['case ', 'default:'].include? self[0]
34
+ ' ' * ([0,indent-2].max) + join
35
+ else
36
+ ' ' * indent + join
37
+ end
38
+ end
39
+ end
40
+
41
+ class Serializer
42
+ attr_reader :timestamps
43
+
44
+ def initialize
45
+ @sep = '; '
46
+ @nl = ''
47
+ @ws = ' '
48
+
49
+ @width = 80
50
+ @indent = 0
51
+
52
+ @lines = [Line.new]
53
+ @line = @lines.last
54
+ @timestamps = {}
55
+ end
56
+
57
+ def timestamp(file)
58
+ @timestamps[file] = File.mtime(file) if file and File.exist?(file)
59
+ end
60
+
61
+ def uptodate?
62
+ return false if @timestamps.empty?
63
+ return @timestamps.all? {|file, mtime| File.mtime(file) == mtime}
64
+ end
65
+
66
+ def mtime
67
+ return Time.now if @timestamps.empty?
68
+ return @timestamps.values.max
69
+ end
70
+
71
+ def enable_vertical_whitespace
72
+ @sep = ";\n"
73
+ @nl = "\n"
74
+ @ws = @nl
75
+ @indent = 2
76
+ end
77
+
78
+ # indent multi-line parameter lists, array constants, blocks
79
+ def reindent(lines)
80
+ indent = 0
81
+ lines.each do |line|
82
+ first = line.find {|token| !token.empty?}
83
+ if first
84
+ last = line[line.rindex {|token| !token.empty?}]
85
+ indent -= @indent if ')}]'.include? first[0]
86
+ line.indent = indent
87
+ indent += @indent if '({['.include? last[-1]
88
+ else
89
+ line.indent = indent
90
+ end
91
+ end
92
+ end
93
+
94
+ # add horizontal (indentation) and vertical (blank lines) whitespace
95
+ def respace
96
+ return if @indent == 0
97
+ reindent @lines
98
+
99
+ (@lines.length-3).downto(0) do |i|
100
+ if
101
+ @lines[i].length == 0
102
+ then
103
+ @lines.delete i
104
+ elsif
105
+ @lines[i+1].comment? and not @lines[i].comment? and
106
+ @lines[i].indent == @lines[i+1].indent
107
+ then
108
+ # before a comment
109
+ @lines.insert i+1, Line.new
110
+ elsif
111
+ @lines[i].indent == @lines[i+1].indent and
112
+ @lines[i+1].indent < @lines[i+2].indent and
113
+ not @lines[i].comment?
114
+ then
115
+ # start of indented block
116
+ @lines.insert i+1, Line.new
117
+ elsif
118
+ @lines[i].indent > @lines[i+1].indent and
119
+ @lines[i+1].indent == @lines[i+2].indent and
120
+ not @lines[i+2].empty?
121
+ then
122
+ # end of indented block
123
+ @lines.insert i+2, Line.new
124
+ end
125
+ end
126
+ end
127
+
128
+ # add a single token to the current line
129
+ def put(string)
130
+ unless String === string and string.include? "\n"
131
+ @line << Token.new(string, @ast)
132
+ else
133
+ parts = string.split("\n")
134
+ @line << Token.new(parts.shift, @ast)
135
+ @lines += parts.map {|part| Line.new(Token.new(part, @ast))}
136
+ @lines << Line.new if string.end_with?("\n")
137
+ @line = @lines.last
138
+ end
139
+ end
140
+
141
+ # add a single token to the current line and then advance to next line
142
+ def puts(string)
143
+ unless String === string and string.include? "\n"
144
+ @line << Token.new(string, @ast)
145
+ else
146
+ put string
147
+ end
148
+
149
+ @line = Line.new
150
+ @lines << @line
151
+ end
152
+
153
+ # advance to next line and then add a single token to the current line
154
+ def sput(string)
155
+ unless String === string and string.include? "\n"
156
+ @line = Line.new(Token.new(string, @ast))
157
+ @lines << @line
158
+ else
159
+ @line = Line.new
160
+ @lines << @line
161
+ put string
162
+ end
163
+ end
164
+
165
+ # current location: [line number, token number]
166
+ def output_location
167
+ [@lines.length-1, @line.length]
168
+ end
169
+
170
+ # insert a line into the output
171
+ def insert(mark, line)
172
+ if mark.last == 0
173
+ @lines.insert(mark.first, Line.new(Token.new(line.chomp, @ast)))
174
+ else
175
+ @lines[mark.first].insert(mark.last, Token.new(line, @ast))
176
+ end
177
+ end
178
+
179
+ # capture (and remove) tokens from the output stream
180
+ def capture(&block)
181
+ mark = output_location
182
+ block.call
183
+ lines = @lines.slice!(mark.first+1..-1)
184
+ @line = @lines.last
185
+
186
+ if lines.empty?
187
+ lines = [@line.slice!(mark.last..-1)]
188
+ elsif @line.length != mark.last
189
+ lines.unshift @line.slice!(mark.last..-1)
190
+ end
191
+
192
+ lines.map(&:join).join(@ws)
193
+ end
194
+
195
+ # wrap long statements in curly braces
196
+ def wrap
197
+ puts '{'
198
+ mark = output_location
199
+ yield
200
+
201
+ if
202
+ @lines.length > mark.first+1 or
203
+ @lines[mark.first-1].join.length + @line.join.length >= @width
204
+ then
205
+ sput '}'
206
+ else
207
+ @line = @lines[mark.first-1]
208
+ @line[-1..-1] = @lines.pop
209
+ end
210
+ end
211
+
212
+ # compact small expressions into a single line
213
+ def compact
214
+ mark = output_location
215
+ yield
216
+ return unless @lines.length - mark.first > 1
217
+ return if @indent == 0
218
+
219
+ # survey what we have to work with, keeping track of a possible
220
+ # split of the last argument or value
221
+ work = []
222
+ indent = len = 0
223
+ trail = split = nil
224
+ slice = @lines[mark.first..-1]
225
+ reindent(slice)
226
+ slice.each_with_index do |line, index|
227
+ if line.first.start_with? '//'
228
+ len += @width # comments are a deal breaker
229
+ else
230
+ (work.push ' '; len += 1) if trail == line.indent and @indent > 0
231
+ len += line.map(&:length).inject(&:+)
232
+ work += line
233
+
234
+ if trail == @indent and line.indent == @indent
235
+ split = [len, work.length, index]
236
+ break if len >= @width - 10
237
+ end
238
+ trail = line.indent
239
+ end
240
+ end
241
+
242
+ if len < @width - 10
243
+ # full collapse
244
+ @lines[mark.first..-1] = [Line.new(*work)]
245
+ @line = @lines.last
246
+ elsif split and split[0] < @width-10
247
+ if slice[split[2]].indent < slice[split[2]+1].indent
248
+ # collapse all but the last argument (typically a hash or function)
249
+ close = slice.pop
250
+ slice[-1].push *close
251
+ @lines[mark.first] = Line.new(*work[0..split[1]-1])
252
+ @lines[mark.first+1..-1] = slice[split[2]+1..-1]
253
+ @line = @lines.last
254
+ end
255
+ end
256
+ end
257
+
258
+ # return the output as a string
259
+ def to_s
260
+ return @str if @str
261
+ respace
262
+ @lines.map(&:to_s).join(@nl)
263
+ end
264
+
265
+ def to_str
266
+ @str ||= to_s
267
+ end
268
+
269
+ def +(value)
270
+ to_s+value
271
+ end
272
+
273
+ BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
274
+
275
+ # https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
276
+ def vlq(*mark)
277
+ if @mark[0] == mark[0]
278
+ return if @mark[-3..-1] == mark[-3..-1]
279
+ @mappings << ',' unless @mappings == ''
280
+ end
281
+
282
+ while @mark[0] < mark[0]
283
+ @mappings << ';'
284
+ @mark[0] += 1
285
+ @mark[1] = 0
286
+ end
287
+
288
+ diffs = mark.zip(@mark).map {|a,b| a-b}
289
+ @mark = mark
290
+
291
+ diffs[1..4].each do |diff|
292
+ if diff < 0
293
+ data = (-diff << 1) + 1
294
+ else
295
+ data = diff << 1
296
+ end
297
+
298
+ encoded = ''
299
+
300
+ begin
301
+ digit = data & 0b11111
302
+ data >>= 5
303
+ digit |= 0b100000 if data > 0
304
+ encoded << BASE64[digit]
305
+ end while data > 0
306
+
307
+ @mappings << encoded
308
+ end
309
+ end
310
+
311
+ def sourcemap
312
+ respace
313
+
314
+ @mappings = ''
315
+ sources = []
316
+ @mark = [0, 0, 0, 0, 0]
317
+
318
+ @lines.each_with_index do |line, row|
319
+ col = line.indent
320
+ line.each do |token|
321
+ if token != ' ' and token.loc
322
+ pos = token.loc.expression.begin_pos
323
+
324
+ buffer = token.loc.expression.source_buffer
325
+ source_index = sources.index(buffer)
326
+ if not source_index
327
+ source_index = sources.length
328
+ timestamp buffer.name
329
+ sources << buffer
330
+ end
331
+
332
+ split = buffer.source[0...pos].split("\n")
333
+ vlq row, col, source_index, split.length-1, split.last.to_s.length
334
+ end
335
+ col += token.length
336
+ end
337
+ end
338
+
339
+ @sourcemap = {
340
+ version: 3,
341
+ file: @ast.loc.expression.source_buffer.name,
342
+ sources: sources.map(&:name),
343
+ mappings: @mappings
344
+ }
345
+ end
346
+ end
347
+ end