ruby2js 2.1.24 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +91 -50
- data/lib/ruby2js/converter/args.rb +10 -0
- data/lib/ruby2js/converter/block.rb +1 -1
- data/lib/ruby2js/converter/case.rb +1 -1
- data/lib/ruby2js/converter/casgn.rb +6 -1
- data/lib/ruby2js/converter/class.rb +7 -2
- data/lib/ruby2js/converter/class2.rb +167 -0
- data/lib/ruby2js/converter/def.rb +54 -6
- data/lib/ruby2js/converter/defs.rb +11 -5
- data/lib/ruby2js/converter/dstr.rb +26 -0
- data/lib/ruby2js/converter/for.rb +3 -3
- data/lib/ruby2js/converter/hash.rb +31 -6
- data/lib/ruby2js/converter/if.rb +1 -1
- data/lib/ruby2js/converter/ivasgn.rb +5 -2
- data/lib/ruby2js/converter/masgn.rb +41 -4
- data/lib/ruby2js/converter/send.rb +71 -10
- data/lib/ruby2js/converter/super.rb +21 -8
- data/lib/ruby2js/converter/vasgn.rb +5 -1
- data/lib/ruby2js/converter/while.rb +13 -0
- data/lib/ruby2js/converter.rb +32 -5
- data/lib/ruby2js/es2015/strict.rb +3 -0
- data/lib/ruby2js/es2015.rb +5 -0
- data/lib/ruby2js/es2016/strict.rb +3 -0
- data/lib/ruby2js/es2016.rb +5 -0
- data/lib/ruby2js/es2017/strict.rb +3 -0
- data/lib/ruby2js/es2017.rb +5 -0
- data/lib/ruby2js/execjs.rb +1 -1
- data/lib/ruby2js/filter/camelCase.rb +1 -1
- data/lib/ruby2js/filter/functions.rb +49 -2
- data/lib/ruby2js/filter/vue.rb +66 -45
- data/lib/ruby2js/strict.rb +3 -0
- data/lib/ruby2js/version.rb +3 -3
- data/lib/ruby2js.rb +41 -0
- data/ruby2js.gemspec +3 -1
- metadata +10 -6
- data/lib/ruby2js/filter/angular-resource.rb +0 -25
- data/lib/ruby2js/filter/angular-route.rb +0 -44
- data/lib/ruby2js/filter/angularrb.rb +0 -604
- data/lib/ruby2js/filter/strict.rb +0 -20
@@ -56,8 +56,8 @@ module Ruby2JS
|
|
56
56
|
# hoist get/set comments to definition of property
|
57
57
|
if right.type == :hash
|
58
58
|
right.children.each do |pair|
|
59
|
-
|
60
|
-
if pair.children.last.type
|
59
|
+
next unless Parser::AST::Node === pair.children.last
|
60
|
+
if [:block, :def, :async].include? pair.children.last.type
|
61
61
|
if @comments[pair.children.last]
|
62
62
|
(puts ''; singleton = false) if singleton
|
63
63
|
comments(pair.children.last).each do |comment|
|
@@ -68,15 +68,40 @@ module Ruby2JS
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
# check to see if es2015 anonymous function syntax can be used
|
72
|
+
anonfn = (es2015 and right and right.type == :block)
|
73
|
+
if anonfn
|
74
|
+
receiver, method = right.children[0].children
|
75
|
+
if receiver
|
76
|
+
unless method == :new and receiver.children == [nil, :Proc]
|
77
|
+
anonfn = false
|
78
|
+
end
|
79
|
+
elsif not [:lambda, :proc].include? method
|
80
|
+
anonfn = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
71
84
|
if
|
85
|
+
anonfn and
|
72
86
|
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
|
73
87
|
then
|
74
|
-
|
88
|
+
@prop = left.children.first
|
89
|
+
parse right, :method
|
75
90
|
else
|
76
|
-
|
91
|
+
if not [:str, :sym].include? left.type and es2015
|
92
|
+
put '['
|
93
|
+
parse left
|
94
|
+
put ']'
|
95
|
+
elsif
|
96
|
+
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
|
97
|
+
then
|
98
|
+
put left.children.first
|
99
|
+
else
|
100
|
+
parse left
|
101
|
+
end
|
102
|
+
|
103
|
+
put ': '; parse right
|
77
104
|
end
|
78
|
-
|
79
|
-
put ': '; parse right
|
80
105
|
end
|
81
106
|
|
82
107
|
ensure
|
data/lib/ruby2js/converter/if.rb
CHANGED
@@ -50,7 +50,7 @@ module Ruby2JS
|
|
50
50
|
if scope
|
51
51
|
vars = @vars.select {|key, value| value == :pending}.keys
|
52
52
|
unless vars.empty?
|
53
|
-
insert mark, "var #{vars.join(', ')}#{@sep}"
|
53
|
+
insert mark, "#{es2015 ? 'let' : 'var'} #{vars.join(', ')}#{@sep}"
|
54
54
|
vars.each {|var| @vars[var] = true}
|
55
55
|
end
|
56
56
|
end
|
@@ -4,8 +4,11 @@ module Ruby2JS
|
|
4
4
|
# (ivasgn :@a
|
5
5
|
# (int 1))
|
6
6
|
|
7
|
-
handle :ivasgn do |var, expression|
|
8
|
-
put "#{ var.to_s.sub('@', 'this._') }
|
7
|
+
handle :ivasgn do |var, expression=nil|
|
8
|
+
put "#{ var.to_s.sub('@', 'this._') }"
|
9
|
+
if expression
|
10
|
+
put " = "; parse expression
|
11
|
+
end
|
9
12
|
end
|
10
13
|
end
|
11
14
|
end
|
@@ -10,11 +10,48 @@ module Ruby2JS
|
|
10
10
|
# (int 2)))
|
11
11
|
|
12
12
|
handle :masgn do |lhs, rhs|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
if es2015
|
14
|
+
walk = lambda do |node|
|
15
|
+
results = []
|
16
|
+
node.children.each do |var|
|
17
|
+
if var.type == :lvasgn
|
18
|
+
results << var
|
19
|
+
elsif var.type == :mlhs or var.type == :splat
|
20
|
+
results += walk[var]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
results
|
24
|
+
end
|
25
|
+
|
26
|
+
vars = walk[lhs]
|
27
|
+
newvars = vars.select {|var| not @vars.include? var.children[0]}
|
28
|
+
|
29
|
+
if newvars.length > 0
|
30
|
+
if vars == newvars
|
31
|
+
put 'let '
|
32
|
+
else
|
33
|
+
put "let #{newvars.map {|var| var.children.last}.join(', ')}#{@sep}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
newvars.each do |var|
|
38
|
+
@vars[var.children.last] ||= (@scope ? true : :pending)
|
39
|
+
end
|
40
|
+
|
41
|
+
put '['
|
42
|
+
lhs.children.each_with_index do |child, index|
|
43
|
+
put ", " unless index == 0
|
44
|
+
parse child
|
45
|
+
end
|
46
|
+
put "] = "
|
47
|
+
parse rhs
|
48
|
+
else
|
49
|
+
block = []
|
50
|
+
lhs.children.zip rhs.children.zip do |var, val|
|
51
|
+
block << s(var.type, *var.children, *val)
|
52
|
+
end
|
53
|
+
parse s(:begin, *block), @state
|
16
54
|
end
|
17
|
-
parse s(:begin, *block), @state
|
18
55
|
end
|
19
56
|
end
|
20
57
|
end
|
@@ -9,11 +9,11 @@ module Ruby2JS
|
|
9
9
|
# (sendw nil :puts
|
10
10
|
# (int 1))
|
11
11
|
|
12
|
-
# Note: attr and
|
12
|
+
# Note: attr, sendw, and await are only generated by filters. Attr forces
|
13
13
|
# interpretation as an attribute vs a function call with zero parameters.
|
14
14
|
# Sendw forces parameters to be placed on separate lines.
|
15
15
|
|
16
|
-
handle :send, :sendw, :attr, :call do |receiver, method, *args|
|
16
|
+
handle :send, :sendw, :await, :attr, :call do |receiver, method, *args|
|
17
17
|
ast = @ast
|
18
18
|
|
19
19
|
# strip '!' and '?' decorations
|
@@ -21,13 +21,13 @@ module Ruby2JS
|
|
21
21
|
|
22
22
|
# three ways to define anonymous functions
|
23
23
|
if method == :new and receiver and receiver.children == [nil, :Proc]
|
24
|
-
return parse args.first
|
24
|
+
return parse args.first, @state
|
25
25
|
elsif not receiver and [:lambda, :proc].include? method
|
26
26
|
if method == :lambda
|
27
27
|
return parse s(args.first.type, *args.first.children[0..-2],
|
28
|
-
s(:autoreturn, args.first.children[-1]))
|
28
|
+
s(:autoreturn, args.first.children[-1])), @state
|
29
29
|
else
|
30
|
-
return parse args.first
|
30
|
+
return parse args.first, @state
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -35,12 +35,59 @@ module Ruby2JS
|
|
35
35
|
if [:call, :[]].include? method and receiver and receiver.type == :block
|
36
36
|
t2,m2,*args2 = receiver.children.first.children
|
37
37
|
if not t2 and [:lambda, :proc].include? m2 and args2.length == 0
|
38
|
-
(@state == :statement ? group(receiver) : parse(receiver))
|
38
|
+
(es2015 || @state == :statement ? group(receiver) : parse(receiver))
|
39
39
|
put '('; parse_all(*args, join: ', '); put ')'
|
40
40
|
return
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
# async/await support
|
45
|
+
if es2017 and receiver == nil and args.length == 1
|
46
|
+
if method == :async
|
47
|
+
if args.first.type == :def
|
48
|
+
# async def f(x) {...}
|
49
|
+
return parse args.first.updated :async
|
50
|
+
|
51
|
+
elsif args.first.type == :defs
|
52
|
+
# async def o.m(x) {...}
|
53
|
+
return parse args.first.updated :asyncs
|
54
|
+
|
55
|
+
elsif args.first.type == :block
|
56
|
+
block = args.first
|
57
|
+
|
58
|
+
if block.children[0].children.last == :lambda
|
59
|
+
# async lambda {|x| ... }
|
60
|
+
# async -> (x) { ... }
|
61
|
+
return parse block.updated(:async, [nil, block.children[1],
|
62
|
+
s(:autoreturn, block.children[2])])
|
63
|
+
|
64
|
+
elsif block.children[0].children.last == :proc
|
65
|
+
# async proc {|x| ... }
|
66
|
+
return parse block.updated(:async, [nil, *block.children[1..-1]])
|
67
|
+
|
68
|
+
elsif
|
69
|
+
block.children[0].children[1] == :new and
|
70
|
+
block.children[0].children[0] == s(:const, nil, :Proc)
|
71
|
+
then
|
72
|
+
# async Proc.new {|x| ... }
|
73
|
+
return parse block.updated(:async, [nil, *block.children[1..-1]])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
elsif method == :await
|
78
|
+
if args.first.type == :send
|
79
|
+
# await f(x)
|
80
|
+
return parse args.first.updated(:await)
|
81
|
+
|
82
|
+
elsif args.first.type == :block
|
83
|
+
# await f(x) { ... }
|
84
|
+
block = args.first
|
85
|
+
return parse block.updated nil, [block.children[0].updated(:await),
|
86
|
+
*block.children[1..-1]]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
44
91
|
op_index = operator_index method
|
45
92
|
if op_index != -1
|
46
93
|
target = args.first
|
@@ -76,6 +123,13 @@ module Ruby2JS
|
|
76
123
|
parse receiver; put '['; parse_all(*args[0..-2], join: ', '); put '] = '
|
77
124
|
parse args[-1]
|
78
125
|
|
126
|
+
elsif method == :** and not es2016
|
127
|
+
put 'Math.pow('
|
128
|
+
parse receiver
|
129
|
+
put ', '
|
130
|
+
parse args.first
|
131
|
+
put ')'
|
132
|
+
|
79
133
|
elsif [:-@, :+@, :~, '~'].include? method
|
80
134
|
put method.to_s[0]; parse receiver
|
81
135
|
|
@@ -99,7 +153,7 @@ module Ruby2JS
|
|
99
153
|
elsif method =~ /=$/
|
100
154
|
parse receiver
|
101
155
|
put "#{ '.' if receiver }#{ method.to_s.sub(/=$/, ' =') } "
|
102
|
-
parse args.first
|
156
|
+
parse args.first, (@state == :method ? :method : :expression)
|
103
157
|
|
104
158
|
elsif method == :new
|
105
159
|
if receiver
|
@@ -166,6 +220,8 @@ module Ruby2JS
|
|
166
220
|
put 'typeof '; parse args.first
|
167
221
|
|
168
222
|
else
|
223
|
+
put 'await ' if @ast.type == :await
|
224
|
+
|
169
225
|
if not ast.is_method?
|
170
226
|
if receiver
|
171
227
|
(group_receiver ? group(receiver) : parse(receiver))
|
@@ -173,7 +229,7 @@ module Ruby2JS
|
|
173
229
|
else
|
174
230
|
parse ast.updated(:lvasgn, [method]), @state
|
175
231
|
end
|
176
|
-
elsif args.
|
232
|
+
elsif args.any? {|arg| arg.type == :splat} and not es2015
|
177
233
|
parse s(:send, s(:attr, receiver, method), :apply,
|
178
234
|
(receiver || s(:nil)), s(:array, *args))
|
179
235
|
else
|
@@ -206,11 +262,16 @@ module Ruby2JS
|
|
206
262
|
# build up chain of conditional evaluations
|
207
263
|
until stack.empty?
|
208
264
|
node = stack.pop
|
209
|
-
|
210
|
-
|
265
|
+
expr = node.updated(:send, [expr, *node.children[1..-1]])
|
266
|
+
result = s(:and, result, expr)
|
211
267
|
end
|
212
268
|
|
213
269
|
parse result
|
214
270
|
end
|
271
|
+
|
272
|
+
handle :splat do |expr|
|
273
|
+
put '...'
|
274
|
+
parse expr
|
275
|
+
end
|
215
276
|
end
|
216
277
|
end
|
@@ -6,7 +6,7 @@ module Ruby2JS
|
|
6
6
|
# (super ...)
|
7
7
|
|
8
8
|
handle :super, :zsuper do |*args|
|
9
|
-
unless @
|
9
|
+
unless @instance_method and @class_parent
|
10
10
|
raise NotImplementedError, "super outside of a method"
|
11
11
|
end
|
12
12
|
|
@@ -21,15 +21,28 @@ module Ruby2JS
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
if es2015
|
25
|
+
if @instance_method.children[0] == :constructor
|
26
|
+
put 'super'
|
27
|
+
else
|
28
|
+
put 'super.'
|
29
|
+
put @instance_method.children[0]
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
put '('
|
33
|
+
parse s(:args, *args)
|
34
|
+
put ')'
|
35
|
+
else
|
36
|
+
parse @class_parent
|
30
37
|
|
31
|
-
|
32
|
-
|
38
|
+
# what to call
|
39
|
+
if @instance_method.type != :constructor
|
40
|
+
puts ".prototype.#{ @instance_method.children[1].to_s.chomp('=') }"
|
41
|
+
end
|
42
|
+
|
43
|
+
if args
|
44
|
+
put '.call('; parse_all s(:self), *args, join: ', '; put ')'
|
45
|
+
end
|
33
46
|
end
|
34
47
|
end
|
35
48
|
end
|
@@ -9,6 +9,19 @@ module Ruby2JS
|
|
9
9
|
begin
|
10
10
|
next_token, @next_token = @next_token, :continue
|
11
11
|
|
12
|
+
# handle while loops that assign a variable
|
13
|
+
while condition.type == :begin and condition.children.length == 1
|
14
|
+
condition = condition.children.first
|
15
|
+
end
|
16
|
+
|
17
|
+
if condition.type == :lvasgn
|
18
|
+
var = condition.children[0]
|
19
|
+
unless @vars[var]
|
20
|
+
put "#{es2015 ? 'let' : 'var'} #{var}#@sep"
|
21
|
+
@vars[var] = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
12
25
|
put 'while ('; parse condition; puts ') {'; scope block; sput '}'
|
13
26
|
ensure
|
14
27
|
@next_token = next_token
|
data/lib/ruby2js/converter.rb
CHANGED
@@ -5,8 +5,9 @@ module Ruby2JS
|
|
5
5
|
attr_accessor :ast
|
6
6
|
|
7
7
|
LOGICAL = :and, :not, :or
|
8
|
-
OPERATORS = [:[], :[]=], [:not, :!], [:*, :/, :%], [:+, :-],
|
9
|
-
[:&], [:^, :|], [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="],
|
8
|
+
OPERATORS = [:[], :[]=], [:not, :!], [:**], [:*, :/, :%], [:+, :-],
|
9
|
+
[:>>, :<<], [:&], [:^, :|], [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="],
|
10
|
+
[:and, :or]
|
10
11
|
|
11
12
|
INVERT_OP = {
|
12
13
|
:< => :>=,
|
@@ -44,6 +45,9 @@ module Ruby2JS
|
|
44
45
|
@prototype = nil
|
45
46
|
@class_parent = nil
|
46
47
|
@class_name = nil
|
48
|
+
|
49
|
+
@eslevel = :es5
|
50
|
+
@strict = false
|
47
51
|
end
|
48
52
|
|
49
53
|
def width=(width)
|
@@ -52,6 +56,14 @@ module Ruby2JS
|
|
52
56
|
|
53
57
|
def convert
|
54
58
|
parse( @ast, :statement )
|
59
|
+
|
60
|
+
if @strict
|
61
|
+
if @sep == '; '
|
62
|
+
@lines.first.unshift "\"use strict\"#@sep"
|
63
|
+
else
|
64
|
+
@lines.unshift Line.new('"use strict";')
|
65
|
+
end
|
66
|
+
end
|
55
67
|
end
|
56
68
|
|
57
69
|
def operator_index op
|
@@ -73,6 +85,20 @@ module Ruby2JS
|
|
73
85
|
Parser::AST::Node.new(type, args)
|
74
86
|
end
|
75
87
|
|
88
|
+
attr_accessor :strict, :eslevel
|
89
|
+
|
90
|
+
def es2015
|
91
|
+
@eslevel >= 2015
|
92
|
+
end
|
93
|
+
|
94
|
+
def es2016
|
95
|
+
@eslevel >= 2016
|
96
|
+
end
|
97
|
+
|
98
|
+
def es2017
|
99
|
+
@eslevel >= 2017
|
100
|
+
end
|
101
|
+
|
76
102
|
@@handlers = []
|
77
103
|
def self.handle(*types, &block)
|
78
104
|
types.each do |type|
|
@@ -132,9 +158,9 @@ module Ruby2JS
|
|
132
158
|
end
|
133
159
|
|
134
160
|
def parse_all(*args)
|
135
|
-
options = (Hash === args.last) ? args.pop : {}
|
136
|
-
sep = options[:join].to_s
|
137
|
-
state = options[:state] || :expression
|
161
|
+
@options = (Hash === args.last) ? args.pop : {}
|
162
|
+
sep = @options[:join].to_s
|
163
|
+
state = @options[:state] || :expression
|
138
164
|
|
139
165
|
args.each_with_index do |arg, index|
|
140
166
|
put sep unless index == 0
|
@@ -211,6 +237,7 @@ require 'ruby2js/converter/break'
|
|
211
237
|
require 'ruby2js/converter/case'
|
212
238
|
require 'ruby2js/converter/casgn'
|
213
239
|
require 'ruby2js/converter/class'
|
240
|
+
require 'ruby2js/converter/class2'
|
214
241
|
require 'ruby2js/converter/const'
|
215
242
|
require 'ruby2js/converter/cvar'
|
216
243
|
require 'ruby2js/converter/cvasgn'
|
data/lib/ruby2js/execjs.rb
CHANGED
@@ -17,7 +17,7 @@ module Ruby2JS
|
|
17
17
|
if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
|
18
18
|
super
|
19
19
|
elsif node.children[1] =~ /_.*\w$/
|
20
|
-
super S(
|
20
|
+
super S(:send , node.children[0],
|
21
21
|
camelCase(node.children[1]), *node.children[2..-1])
|
22
22
|
else
|
23
23
|
super
|
@@ -47,8 +47,12 @@ module Ruby2JS
|
|
47
47
|
process S(:call, target, :toString, *args)
|
48
48
|
|
49
49
|
elsif method == :Array and target == nil
|
50
|
-
|
51
|
-
:
|
50
|
+
if es2015
|
51
|
+
process S(:send, s(:const, nil, :Array), :from, *args)
|
52
|
+
else
|
53
|
+
process S(:send, s(:attr, s(:attr, s(:const, nil, :Array),
|
54
|
+
:prototype), :slice), :call, *args)
|
55
|
+
end
|
52
56
|
|
53
57
|
elsif method == :to_i
|
54
58
|
process node.updated :send, [nil, :parseInt, target, *args]
|
@@ -368,6 +372,49 @@ module Ruby2JS
|
|
368
372
|
:step, step])
|
369
373
|
process node.updated(nil, [call, *node.children[1..-1]])
|
370
374
|
|
375
|
+
elsif
|
376
|
+
# (a..b).each {|v| ...}
|
377
|
+
call.children[1] == :each and
|
378
|
+
call.children[0].type == :begin and
|
379
|
+
call.children[0].children.length == 1 and
|
380
|
+
[:irange, :erange].include? call.children[0].children[0].type and
|
381
|
+
node.children[1].children.length == 1
|
382
|
+
then
|
383
|
+
s(:for, s(:lvasgn, node.children[1].children[0].children[0]),
|
384
|
+
call.children[0].children[0], node.children[2])
|
385
|
+
|
386
|
+
elsif
|
387
|
+
[:each, :each_value].include? call.children[1] and
|
388
|
+
node.children[1].children.length == 1
|
389
|
+
then
|
390
|
+
if es2015
|
391
|
+
process node.updated(:for_of,
|
392
|
+
[s(:lvasgn, node.children[1].children[0].children[0]),
|
393
|
+
node.children[0].children[0], node.children[2]])
|
394
|
+
else
|
395
|
+
process node.updated(nil, [s(:send, call.children[0],
|
396
|
+
:forEach), *node.children[1..2]])
|
397
|
+
end
|
398
|
+
|
399
|
+
elsif
|
400
|
+
call.children[1] == :each_key and
|
401
|
+
[:each, :each_key].include? call.children[1] and
|
402
|
+
node.children[1].children.length == 1
|
403
|
+
then
|
404
|
+
process node.updated(:for,
|
405
|
+
[s(:lvasgn, node.children[1].children[0].children[0]),
|
406
|
+
node.children[0].children[0], node.children[2]])
|
407
|
+
|
408
|
+
elsif es2015 and call.children[1] == :inject
|
409
|
+
process node.updated(:send, [call.children[0], :reduce,
|
410
|
+
s(:block, s(:send, nil, :lambda), *node.children[1..2]),
|
411
|
+
*call.children[2..-1]])
|
412
|
+
|
413
|
+
elsif es2017 and call.children[1] == :each_pair
|
414
|
+
process node.updated(nil, [s(:send, s(:send, s(:const, nil, :Object),
|
415
|
+
:entries, call.children[0]), :forEach), s(:args, s(:mlhs,
|
416
|
+
*node.children[1].children)), node.children[2]])
|
417
|
+
|
371
418
|
else
|
372
419
|
super
|
373
420
|
end
|