ruby2js 3.5.1 → 4.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 +5 -662
- data/lib/ruby2js.rb +61 -10
- data/lib/ruby2js/converter.rb +10 -4
- data/lib/ruby2js/converter/assign.rb +159 -0
- data/lib/ruby2js/converter/begin.rb +7 -2
- data/lib/ruby2js/converter/case.rb +7 -2
- data/lib/ruby2js/converter/class.rb +77 -21
- data/lib/ruby2js/converter/class2.rb +102 -31
- data/lib/ruby2js/converter/def.rb +7 -3
- data/lib/ruby2js/converter/dstr.rb +8 -3
- data/lib/ruby2js/converter/hash.rb +9 -5
- data/lib/ruby2js/converter/hide.rb +13 -0
- data/lib/ruby2js/converter/if.rb +10 -2
- data/lib/ruby2js/converter/import.rb +35 -4
- data/lib/ruby2js/converter/kwbegin.rb +9 -2
- data/lib/ruby2js/converter/literal.rb +14 -2
- data/lib/ruby2js/converter/module.rb +41 -4
- data/lib/ruby2js/converter/opasgn.rb +8 -0
- data/lib/ruby2js/converter/send.rb +45 -5
- data/lib/ruby2js/converter/vasgn.rb +5 -0
- data/lib/ruby2js/converter/xstr.rb +1 -1
- data/lib/ruby2js/demo.rb +53 -0
- data/lib/ruby2js/es2022.rb +5 -0
- data/lib/ruby2js/es2022/strict.rb +3 -0
- data/lib/ruby2js/filter.rb +9 -1
- data/lib/ruby2js/filter/active_functions.rb +44 -0
- data/lib/ruby2js/filter/camelCase.rb +4 -3
- data/lib/ruby2js/filter/cjs.rb +2 -0
- data/lib/ruby2js/filter/esm.rb +133 -7
- data/lib/ruby2js/filter/functions.rb +107 -98
- data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
- data/lib/ruby2js/filter/node.rb +95 -74
- data/lib/ruby2js/filter/nokogiri.rb +15 -41
- data/lib/ruby2js/filter/react.rb +191 -56
- data/lib/ruby2js/filter/require.rb +100 -5
- data/lib/ruby2js/filter/return.rb +15 -1
- data/lib/ruby2js/filter/securerandom.rb +33 -0
- data/lib/ruby2js/filter/stimulus.rb +185 -0
- data/lib/ruby2js/filter/vue.rb +9 -0
- data/lib/ruby2js/jsx.rb +291 -0
- data/lib/ruby2js/namespace.rb +75 -0
- data/lib/ruby2js/rails.rb +15 -9
- data/lib/ruby2js/serializer.rb +3 -1
- data/lib/ruby2js/version.rb +3 -3
- data/ruby2js.gemspec +1 -1
- metadata +14 -5
- data/lib/ruby2js/filter/esm_migration.rb +0 -72
- data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
@@ -10,6 +10,11 @@ module Ruby2JS
|
|
10
10
|
# (...))
|
11
11
|
|
12
12
|
handle :dstr, :dsym do |*children|
|
13
|
+
if @state == :expression and children.empty?
|
14
|
+
puts '""'
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
13
18
|
if es2015
|
14
19
|
# gather length of string parts; if long enough, newlines will
|
15
20
|
# not be escaped (poor man's HEREDOC)
|
@@ -26,7 +31,7 @@ module Ruby2JS
|
|
26
31
|
else
|
27
32
|
put str
|
28
33
|
end
|
29
|
-
|
34
|
+
elsif child != s(:begin)
|
30
35
|
put '${'
|
31
36
|
parse child
|
32
37
|
put '}'
|
@@ -40,8 +45,8 @@ module Ruby2JS
|
|
40
45
|
children.each_with_index do |child, index|
|
41
46
|
put ' + ' unless index == 0
|
42
47
|
|
43
|
-
if child.type == :begin and child.children.length
|
44
|
-
child = child.children.first
|
48
|
+
if child.type == :begin and child.children.length <= 1
|
49
|
+
child = child.children.first || s(:str, '')
|
45
50
|
end
|
46
51
|
|
47
52
|
if child.type == :send
|
@@ -73,7 +73,7 @@ module Ruby2JS
|
|
73
73
|
if right.type == :hash
|
74
74
|
right.children.each do |pair|
|
75
75
|
next unless Parser::AST::Node === pair.children.last
|
76
|
-
if [
|
76
|
+
if %i[block def defm async].include? pair.children.last.type
|
77
77
|
if @comments[pair.children.last]
|
78
78
|
(puts ''; singleton = false) if singleton
|
79
79
|
comments(pair.children.last).each do |comment|
|
@@ -120,22 +120,26 @@ module Ruby2JS
|
|
120
120
|
|
121
121
|
if \
|
122
122
|
anonfn and
|
123
|
-
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\
|
123
|
+
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\z/
|
124
124
|
then
|
125
125
|
@prop = left.children.first
|
126
126
|
parse right, :method
|
127
127
|
elsif \
|
128
|
-
es2015 and left.type == :sym and right.type == :lvar
|
129
|
-
|
128
|
+
es2015 and left.type == :sym and (right.type == :lvar or
|
129
|
+
(right.type == :send and right.children.first == nil)) and
|
130
|
+
left.children.last == right.children.last
|
130
131
|
then
|
131
132
|
parse right
|
133
|
+
elsif right.type == :defm and %i[sym str].include? left.type and es2015
|
134
|
+
@prop = left.children.first.to_s
|
135
|
+
parse right
|
132
136
|
else
|
133
137
|
if not [:str, :sym].include? left.type and es2015
|
134
138
|
put '['
|
135
139
|
parse left
|
136
140
|
put ']'
|
137
141
|
elsif \
|
138
|
-
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\
|
142
|
+
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\z/
|
139
143
|
then
|
140
144
|
put left.children.first
|
141
145
|
else
|
data/lib/ruby2js/converter/if.rb
CHANGED
@@ -64,10 +64,18 @@ module Ruby2JS
|
|
64
64
|
if else_block.type == :begin
|
65
65
|
else_block = s(:xnode, '', *else_block.children)
|
66
66
|
end
|
67
|
+
else
|
68
|
+
if then_block.type == :begin
|
69
|
+
then_block = s(:kwbegin, then_block)
|
70
|
+
end
|
71
|
+
|
72
|
+
if else_block.type == :begin
|
73
|
+
else_block = s(:kwbegin, else_block)
|
74
|
+
end
|
67
75
|
end
|
68
76
|
|
69
|
-
parse condition; put ' ? '; parse then_block
|
70
|
-
put ' : '; parse else_block
|
77
|
+
parse condition; put ' ? '; parse then_block, @state
|
78
|
+
put ' : '; parse else_block, @state
|
71
79
|
end
|
72
80
|
end
|
73
81
|
end
|
@@ -6,20 +6,51 @@ module Ruby2JS
|
|
6
6
|
# NOTE: import is a synthetic
|
7
7
|
|
8
8
|
handle :import do |path, *args|
|
9
|
+
if module_type == :cjs
|
10
|
+
# only the subset of import syntaxes generated by filters are handled here
|
11
|
+
if Parser::AST::Node === args.first and args.first.type == :attr
|
12
|
+
return parse s(:casgn, *args.first.children,
|
13
|
+
s(:send, nil, :require, s(:str, Array(path).first))), :statement
|
14
|
+
elsif Array === args.first and args.first.length == 1
|
15
|
+
target = args.first.first
|
16
|
+
if Parser::AST::Node === target and target.type == :attr and target.children.first == nil
|
17
|
+
return parse s(:casgn, *target.children,
|
18
|
+
s(:attr, s(:send, nil, :require, s(:str, Array(path).first)), target.children.last)),
|
19
|
+
:statement
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
9
24
|
put 'import '
|
25
|
+
|
10
26
|
if args.length == 0
|
11
27
|
# import "file.css"
|
12
28
|
put path.inspect
|
13
29
|
else
|
14
30
|
# import (x) from "file.js"
|
15
|
-
default_import = !args.first.is_a?(Array) && [
|
31
|
+
default_import = !args.first.is_a?(Array) && %i[const send attr str].include?(args.first.type)
|
32
|
+
|
33
|
+
if default_import and args.length > 1
|
34
|
+
parse args.shift
|
35
|
+
put ', '
|
36
|
+
default_import = false
|
37
|
+
end
|
38
|
+
|
16
39
|
args = args.first if args.first.is_a?(Array)
|
17
40
|
|
41
|
+
if args.first.type == :array
|
42
|
+
args = args.first.children
|
43
|
+
end
|
44
|
+
|
18
45
|
# handle the default name or { ConstA, Const B } portion
|
19
46
|
put "{ " unless default_import
|
20
47
|
args.each_with_index do |arg, index|
|
21
48
|
put ', ' unless index == 0
|
22
|
-
|
49
|
+
if arg.type == :str
|
50
|
+
put arg.children.first # useful for '*'
|
51
|
+
else
|
52
|
+
parse arg
|
53
|
+
end
|
23
54
|
end
|
24
55
|
put " }" unless default_import
|
25
56
|
|
@@ -27,7 +58,7 @@ module Ruby2JS
|
|
27
58
|
|
28
59
|
# should there be an as clause? e.g., import React as *
|
29
60
|
if path.is_a?(Array) && !path[0].is_a?(String) && path[0].type == :pair && path[0].children[0].children[0] == :as
|
30
|
-
put " as #{path[0].children[1].children
|
61
|
+
put " as #{path[0].children[1].children.last}"
|
31
62
|
|
32
63
|
# advance to the next kwarg, aka from
|
33
64
|
from_kwarg_position = 1
|
@@ -66,7 +97,7 @@ module Ruby2JS
|
|
66
97
|
elsif node.respond_to?(:type) && node.children[1] == :default
|
67
98
|
put 'default '
|
68
99
|
args[0] = node.children[2]
|
69
|
-
elsif node.respond_to?(:type) && node.type
|
100
|
+
elsif node.respond_to?(:type) && [:lvasgn, :casgn].include?(node.type)
|
70
101
|
if node.children[0] == :default
|
71
102
|
put 'default '
|
72
103
|
args[0] = node.children[1]
|
@@ -6,7 +6,7 @@ module Ruby2JS
|
|
6
6
|
# (resbody nil nil
|
7
7
|
# (send nil :b)) nil)
|
8
8
|
handle :rescue do |*statements|
|
9
|
-
|
9
|
+
parse s(:kwbegin, s(:rescue, *statements)), @state
|
10
10
|
end
|
11
11
|
|
12
12
|
# (kwbegin
|
@@ -19,7 +19,14 @@ module Ruby2JS
|
|
19
19
|
|
20
20
|
handle :kwbegin do |*children|
|
21
21
|
block = children.first
|
22
|
-
|
22
|
+
|
23
|
+
if @state == :expression
|
24
|
+
parse s(:send, s(:block, s(:send, nil, :proc), s(:args),
|
25
|
+
s(:begin, s(:autoreturn, *children))), :[])
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
if block&.type == :ensure
|
23
30
|
block, finally = block.children
|
24
31
|
else
|
25
32
|
finally = nil
|
@@ -5,12 +5,24 @@ module Ruby2JS
|
|
5
5
|
# (float 1.1)
|
6
6
|
# (str "1"))
|
7
7
|
|
8
|
-
handle :
|
8
|
+
handle :str do |value|
|
9
9
|
put value.inspect
|
10
10
|
end
|
11
11
|
|
12
|
+
handle :int, :float do |value|
|
13
|
+
put number_format(value)
|
14
|
+
end
|
15
|
+
|
12
16
|
handle :octal do |value|
|
13
|
-
put '0' + value.to_s(8)
|
17
|
+
put '0' + number_format(value.to_s(8))
|
18
|
+
end
|
19
|
+
|
20
|
+
def number_format(number)
|
21
|
+
return number.to_s unless es2021
|
22
|
+
parts = number.to_s.split('.')
|
23
|
+
parts[0] = parts[0].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_")
|
24
|
+
parts[1] = parts[1].gsub(/(\d\d\d)(?=\d)/, "\\1_") if parts[1]
|
25
|
+
parts.join('.')
|
14
26
|
end
|
15
27
|
end
|
16
28
|
end
|
@@ -4,14 +4,43 @@ module Ruby2JS
|
|
4
4
|
# (module
|
5
5
|
# (const nil :A)
|
6
6
|
# (...)
|
7
|
+
#
|
8
|
+
# Note: modules_hash is an anonymous modules as a value in a hash; the
|
9
|
+
# name has already been output so should be ignored other than
|
10
|
+
# in determining the namespace.
|
11
|
+
|
12
|
+
handle :module, :module_hash do |name, *body|
|
13
|
+
extend = @namespace.enter(name)
|
14
|
+
|
15
|
+
if body == [nil]
|
16
|
+
if @ast.type == :module and not extend
|
17
|
+
parse @ast.updated(:casgn, [*name.children, s(:hash)])
|
18
|
+
else
|
19
|
+
parse @ast.updated(:hash, [])
|
20
|
+
end
|
21
|
+
|
22
|
+
@namespace.leave
|
23
|
+
return
|
24
|
+
end
|
7
25
|
|
8
|
-
handle :module do |name, *body|
|
9
26
|
while body.length == 1 and body.first.type == :begin
|
10
27
|
body = body.first.children
|
11
28
|
end
|
12
29
|
|
13
|
-
if body.length > 0 and body.all? {|child|
|
14
|
-
|
30
|
+
if body.length > 0 and body.all? {|child|
|
31
|
+
%i[def module].include? child.type or
|
32
|
+
(es2015 and child.type == :class and child.children[1] == nil)}
|
33
|
+
|
34
|
+
if extend
|
35
|
+
parse s(:assign, name, @ast.updated(:class_module,
|
36
|
+
[nil, nil, *body])), :statement
|
37
|
+
elsif @ast.type == :module_hash
|
38
|
+
parse @ast.updated(:class_module, [nil, nil, *body])
|
39
|
+
else
|
40
|
+
parse @ast.updated(:class_module, [name, nil, *body])
|
41
|
+
end
|
42
|
+
|
43
|
+
@namespace.leave
|
15
44
|
return
|
16
45
|
end
|
17
46
|
|
@@ -42,6 +71,8 @@ module Ruby2JS
|
|
42
71
|
symbols << node.children.first
|
43
72
|
elsif node.type == :class and node.children.first.children.first == nil
|
44
73
|
symbols << node.children.first.children.last
|
74
|
+
elsif node.type == :module
|
75
|
+
symbols << node.children.first.children.last
|
45
76
|
end
|
46
77
|
end
|
47
78
|
|
@@ -50,11 +81,17 @@ module Ruby2JS
|
|
50
81
|
|
51
82
|
body = s(:send, s(:block, s(:send, nil, :proc), s(:args),
|
52
83
|
s(:begin, *body)), :[])
|
53
|
-
if name
|
84
|
+
if not name
|
85
|
+
parse body
|
86
|
+
elsif extend
|
87
|
+
parse s(:assign, name, body)
|
88
|
+
elsif name.children.first == nil
|
54
89
|
parse s(:lvasgn, name.children.last, body)
|
55
90
|
else
|
56
91
|
parse s(:send, name.children.first, "#{name.children.last}=", body)
|
57
92
|
end
|
93
|
+
|
94
|
+
@namespace.leave
|
58
95
|
end
|
59
96
|
end
|
60
97
|
end
|
@@ -12,6 +12,14 @@ module Ruby2JS
|
|
12
12
|
var = s(:lvar, var.children.first) if var.type == :lvasgn
|
13
13
|
var = s(:cvar, var.children.first) if var.type == :cvasgn
|
14
14
|
|
15
|
+
if var.type == :lvar
|
16
|
+
name = var.children.first
|
17
|
+
receiver = @rbstack.map {|rb| rb[name]}.compact.last
|
18
|
+
if receiver
|
19
|
+
var = s(:attr, nil, name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
15
23
|
if \
|
16
24
|
[:+, :-].include?(op) and value.type==:int and
|
17
25
|
(value.children==[1] or value.children==[-1])
|
@@ -9,11 +9,12 @@ module Ruby2JS
|
|
9
9
|
# (sendw nil :puts
|
10
10
|
# (int 1))
|
11
11
|
|
12
|
-
# Note: attr, sendw, and await are only generated by filters. Attr forces
|
12
|
+
# Note: attr, sendw, send!, and await are only generated by filters. Attr forces
|
13
13
|
# interpretation as an attribute vs a function call with zero parameters.
|
14
|
+
# send! forces interpretation as a method call even with zero parameters.
|
14
15
|
# Sendw forces parameters to be placed on separate lines.
|
15
16
|
|
16
|
-
handle :send, :sendw, :await, :attr, :call do |receiver, method, *args|
|
17
|
+
handle :send, :sendw, :send!, :await, :attr, :call do |receiver, method, *args|
|
17
18
|
ast = @ast
|
18
19
|
|
19
20
|
if \
|
@@ -40,6 +41,20 @@ module Ruby2JS
|
|
40
41
|
# strip '!' and '?' decorations
|
41
42
|
method = method.to_s[0..-2] if method =~ /\w[!?]$/
|
42
43
|
|
44
|
+
# anonymous class
|
45
|
+
if method == :new and receiver and receiver.children == [nil, :Class] and
|
46
|
+
args.last.type == :def and args.last.children.first == nil
|
47
|
+
|
48
|
+
parent = (args.length > 1) ? args.first : nil
|
49
|
+
|
50
|
+
if es2015
|
51
|
+
return parse s(:class2, nil, parent, *args.last.children[2..-1])
|
52
|
+
else
|
53
|
+
return parse s(:kwbegin, s(:class, s(:const, nil, :$$), parent,
|
54
|
+
*args.last.children[2..-1]), s(:const, nil, :$$))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
43
58
|
# three ways to define anonymous functions
|
44
59
|
if method == :new and receiver and receiver.children == [nil, :Proc]
|
45
60
|
return parse args.first, @state
|
@@ -117,8 +132,13 @@ module Ruby2JS
|
|
117
132
|
|
118
133
|
# resolve anonymous receivers against rbstack
|
119
134
|
receiver ||= @rbstack.map {|rb| rb[method]}.compact.last
|
135
|
+
autobind = nil
|
120
136
|
|
121
137
|
if receiver
|
138
|
+
if receiver.type == :autobind
|
139
|
+
autobind = receiver = receiver.children.first
|
140
|
+
end
|
141
|
+
|
122
142
|
group_receiver = receiver.type == :send &&
|
123
143
|
op_index < operator_index( receiver.children[1] ) if receiver
|
124
144
|
group_receiver ||= GROUP_OPERATORS.include? receiver.type
|
@@ -175,9 +195,15 @@ module Ruby2JS
|
|
175
195
|
receiver.type == :send and
|
176
196
|
receiver.children[1] == :+@ and
|
177
197
|
Parser::AST::Node === receiver.children[0] and
|
178
|
-
receiver.children[0].type
|
198
|
+
%i(class module).include? receiver.children[0].type
|
179
199
|
then
|
180
|
-
|
200
|
+
if receiver.children[0].type == :class
|
201
|
+
parse receiver.children[0].updated(:class_extend)
|
202
|
+
else
|
203
|
+
mod = receiver.children[0]
|
204
|
+
parse s(:assign, mod.children[0],
|
205
|
+
mod.updated(nil, [nil, *mod.children[1..-1]]))
|
206
|
+
end
|
181
207
|
else
|
182
208
|
put method.to_s[0]; parse receiver
|
183
209
|
end
|
@@ -280,7 +306,13 @@ module Ruby2JS
|
|
280
306
|
else
|
281
307
|
put 'await ' if @ast.type == :await
|
282
308
|
|
283
|
-
if
|
309
|
+
if method == :bind and receiver&.type == :send
|
310
|
+
if receiver.children.length == 2 and receiver.children.first == nil
|
311
|
+
receiver = receiver.updated(:attr) # prevent autobind
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
if not ast.is_method? and ast.type != :send!
|
284
316
|
if receiver
|
285
317
|
(group_receiver ? group(receiver) : parse(receiver))
|
286
318
|
put ".#{ method }"
|
@@ -302,6 +334,14 @@ module Ruby2JS
|
|
302
334
|
compact { puts "("; parse_all(*args, join: ",#@ws"); sput ')' }
|
303
335
|
end
|
304
336
|
end
|
337
|
+
|
338
|
+
if autobind and not ast.is_method? and ast.type != :attr
|
339
|
+
if @state == :statement
|
340
|
+
put '()'
|
341
|
+
else
|
342
|
+
put '.bind('; parse(autobind); put ')'
|
343
|
+
end
|
344
|
+
end
|
305
345
|
end
|
306
346
|
end
|
307
347
|
|
@@ -8,6 +8,11 @@ module Ruby2JS
|
|
8
8
|
# (int 1))
|
9
9
|
|
10
10
|
handle :lvasgn, :gvasgn do |name, value=nil|
|
11
|
+
if @ast.type == :lvasgn and value
|
12
|
+
receiver = @rbstack.map {|rb| rb[name]}.compact.last
|
13
|
+
return parse s(:attr, receiver, "#{name}=", value) if receiver
|
14
|
+
end
|
15
|
+
|
11
16
|
state = @state
|
12
17
|
begin
|
13
18
|
if value and value.type == :lvasgn and @state == :statement
|
data/lib/ruby2js/demo.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# helper methods shared between MRI CGI/server and Opal implementations
|
2
|
+
require 'ruby2js'
|
3
|
+
|
4
|
+
module Ruby2JS
|
5
|
+
module Demo
|
6
|
+
def self.parse_autoimports(mappings)
|
7
|
+
autoimports = {}
|
8
|
+
|
9
|
+
mappings = mappings.gsub(/\s+|"|'/, '')
|
10
|
+
|
11
|
+
while mappings and not mappings.empty?
|
12
|
+
if mappings =~ /^(\w+):([^,]+)(,(.*))?$/
|
13
|
+
# symbol: module
|
14
|
+
autoimports[$1.to_sym] = $2
|
15
|
+
mappings = $4
|
16
|
+
elsif mappings =~ /^\[([\w,]+)\]:([^,]+)(,(.*))?$/
|
17
|
+
# [symbol, symbol]: module
|
18
|
+
mname, mappings = $2, $4
|
19
|
+
autoimports[$1.split(/,/).map(&:to_sym)] = mname
|
20
|
+
elsif mappings =~ /^(\w+)(,(.*))?$/
|
21
|
+
# symbol
|
22
|
+
autoimports[$1.to_sym] = $1
|
23
|
+
mappings = $3
|
24
|
+
elsif not mappings.empty?
|
25
|
+
$load_error = "unsupported autoimports mapping: #{mappings}"
|
26
|
+
mappings = ''
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# if nothing is listed, provide a mapping for everything
|
31
|
+
autoimports = proc {|name| name.to_s} if autoimports.empty?
|
32
|
+
|
33
|
+
autoimports
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.parse_defs(mappings)
|
37
|
+
defs = {}
|
38
|
+
|
39
|
+
mappings = mappings.gsub(/\s+|"|'/, '')
|
40
|
+
|
41
|
+
while mappings =~ /^(\w+):\[(:?@?\w+(,:?@?\w+)*)\](,(.*))?$/
|
42
|
+
mappings = $5
|
43
|
+
defs[$1.to_sym] = $2.gsub(':', '').split(',').map(&:to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
if mappings and not mappings.empty?
|
47
|
+
$load_error = "unsupported defs: #{mappings}"
|
48
|
+
end
|
49
|
+
|
50
|
+
defs
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|