ruby2js 3.0.15 → 3.3.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 +92 -7
- data/lib/ruby2js.rb +30 -3
- data/lib/ruby2js/converter.rb +23 -3
- data/lib/ruby2js/converter/args.rb +31 -1
- data/lib/ruby2js/converter/block.rb +2 -2
- data/lib/ruby2js/converter/casgn.rb +3 -2
- data/lib/ruby2js/converter/class.rb +2 -2
- data/lib/ruby2js/converter/class2.rb +117 -13
- data/lib/ruby2js/converter/cvar.rb +5 -3
- data/lib/ruby2js/converter/cvasgn.rb +5 -3
- data/lib/ruby2js/converter/def.rb +2 -2
- data/lib/ruby2js/converter/for.rb +8 -1
- data/lib/ruby2js/converter/hash.rb +22 -6
- data/lib/ruby2js/converter/if.rb +13 -2
- data/lib/ruby2js/converter/import.rb +38 -0
- data/lib/ruby2js/converter/ivar.rb +2 -0
- data/lib/ruby2js/converter/ivasgn.rb +1 -1
- data/lib/ruby2js/converter/kwbegin.rb +16 -2
- data/lib/ruby2js/converter/logical.rb +46 -1
- data/lib/ruby2js/converter/opasgn.rb +5 -2
- data/lib/ruby2js/converter/regexp.rb +27 -2
- data/lib/ruby2js/converter/return.rb +1 -1
- data/lib/ruby2js/converter/send.rb +160 -69
- data/lib/ruby2js/converter/super.rb +15 -9
- data/lib/ruby2js/converter/xnode.rb +89 -0
- data/lib/ruby2js/es2018.rb +5 -0
- data/lib/ruby2js/es2018/strict.rb +3 -0
- data/lib/ruby2js/es2019.rb +5 -0
- data/lib/ruby2js/es2019/strict.rb +3 -0
- data/lib/ruby2js/es2020.rb +5 -0
- data/lib/ruby2js/es2020/strict.rb +3 -0
- data/lib/ruby2js/es2021.rb +5 -0
- data/lib/ruby2js/es2021/strict.rb +3 -0
- data/lib/ruby2js/filter.rb +1 -0
- data/lib/ruby2js/filter/cjs.rb +2 -2
- data/lib/ruby2js/filter/esm.rb +72 -0
- data/lib/ruby2js/filter/functions.rb +218 -34
- data/lib/ruby2js/filter/matchAll.rb +49 -0
- data/lib/ruby2js/filter/node.rb +18 -10
- data/lib/ruby2js/filter/nokogiri.rb +13 -13
- data/lib/ruby2js/filter/react.rb +190 -30
- data/lib/ruby2js/filter/require.rb +1 -4
- data/lib/ruby2js/filter/rubyjs.rb +4 -4
- data/lib/ruby2js/filter/vue.rb +45 -17
- data/lib/ruby2js/filter/wunderbar.rb +63 -0
- data/lib/ruby2js/serializer.rb +25 -11
- data/lib/ruby2js/version.rb +2 -2
- metadata +16 -4
@@ -33,21 +33,99 @@ module Ruby2JS
|
|
33
33
|
class_name, @class_name = @class_name, name
|
34
34
|
class_parent, @class_parent = @class_parent, inheritance
|
35
35
|
@rbstack.push({})
|
36
|
+
constructor = []
|
37
|
+
index = 0
|
36
38
|
|
37
|
-
# capture method names for automatic self referencing
|
38
|
-
body.
|
39
|
+
# capture constructor, method names for automatic self referencing
|
40
|
+
body.each do |m|
|
39
41
|
if m.type == :def
|
40
42
|
prop = m.children.first
|
41
|
-
|
43
|
+
if prop == :initialize
|
44
|
+
constructor = m.children[2..-1]
|
45
|
+
elsif not prop.to_s.end_with? '='
|
42
46
|
@rbstack.last[prop] = s(:self)
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
51
|
+
# private variable declarations
|
52
|
+
if es2020
|
53
|
+
ivars = Set.new
|
54
|
+
cvars = Set.new
|
55
|
+
|
56
|
+
# find ivars and cvars
|
57
|
+
walk = proc do |ast|
|
58
|
+
ivars << ast.children.first if ast.type === :ivar
|
59
|
+
ivars << ast.children.first if ast.type === :ivasgn
|
60
|
+
cvars << ast.children.first if ast.type === :cvar
|
61
|
+
cvars << ast.children.first if ast.type === :cvasgn
|
62
|
+
|
63
|
+
ast.children.each do |child|
|
64
|
+
walk[child] if child.is_a? Parser::AST::Node
|
65
|
+
end
|
66
|
+
|
67
|
+
if ast.type == :send and ast.children.first == nil
|
68
|
+
if ast.children[1] == :attr_accessor
|
69
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
70
|
+
ivars << :"@#{child_sym.children.first}"
|
71
|
+
end
|
72
|
+
elsif ast.children[1] == :attr_reader
|
73
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
74
|
+
ivars << :"@#{child_sym.children.first}"
|
75
|
+
end
|
76
|
+
elsif ast.children[1] == :attr_writer
|
77
|
+
ast.children[2..-1].each_with_index do |child_sym, index2|
|
78
|
+
ivars << :"@#{child_sym.children.first}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
walk[@ast]
|
85
|
+
|
86
|
+
# process leading initializers in constructor
|
87
|
+
while constructor.length == 1 and constructor.first.type == :begin
|
88
|
+
constructor = constructor.first.children.dup
|
89
|
+
end
|
90
|
+
|
91
|
+
# emit additional class declarations
|
92
|
+
unless cvars.empty?
|
93
|
+
body.each do |m|
|
94
|
+
cvars.delete m.children.first if m.type == :cvasgn
|
95
|
+
end
|
96
|
+
end
|
97
|
+
cvars.to_a.sort.each do |cvar|
|
98
|
+
put(index == 0 ? @nl : @sep)
|
99
|
+
index += 1
|
100
|
+
put 'static #$' + cvar.to_s[2..-1]
|
101
|
+
end
|
102
|
+
|
103
|
+
while constructor.length > 0 and constructor.first.type == :ivasgn
|
104
|
+
put(index == 0 ? @nl : @sep)
|
105
|
+
index += 1
|
106
|
+
statement = constructor.shift
|
107
|
+
put '#'
|
108
|
+
put statement.children.first.to_s[1..-1]
|
109
|
+
put ' = '
|
110
|
+
parse statement.children.last
|
111
|
+
|
112
|
+
ivars.delete statement.children.first
|
113
|
+
end
|
114
|
+
|
115
|
+
# emit additional instance declarations
|
116
|
+
ivars.to_a.sort.each do |ivar|
|
117
|
+
put(index == 0 ? @nl : @sep)
|
118
|
+
index += 1
|
119
|
+
put '#' + ivar.to_s[1..-1]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# process class definition
|
47
124
|
post = []
|
48
125
|
skipped = false
|
49
|
-
body.
|
126
|
+
body.each do |m|
|
50
127
|
put(index == 0 ? @nl : @sep) unless skipped
|
128
|
+
index += 1
|
51
129
|
comments = comments(m)
|
52
130
|
location = output_location
|
53
131
|
skipped = false
|
@@ -67,7 +145,13 @@ module Ruby2JS
|
|
67
145
|
|
68
146
|
if @prop == :initialize
|
69
147
|
@prop = :constructor
|
70
|
-
|
148
|
+
|
149
|
+
if constructor == [] or constructor == [(:super)]
|
150
|
+
skipped = true
|
151
|
+
next
|
152
|
+
end
|
153
|
+
|
154
|
+
m = m.updated(m.type, [@prop, m.children[1], s(:begin, *constructor)])
|
71
155
|
elsif not m.is_method?
|
72
156
|
@prop = "get #{@prop}"
|
73
157
|
m = m.updated(m.type, [*m.children[0..1],
|
@@ -83,12 +167,13 @@ module Ruby2JS
|
|
83
167
|
|
84
168
|
begin
|
85
169
|
@instance_method = m
|
86
|
-
|
170
|
+
@class_method = nil
|
171
|
+
parse m # unless skipped
|
87
172
|
ensure
|
88
173
|
@instance_method = nil
|
89
174
|
end
|
90
175
|
|
91
|
-
elsif
|
176
|
+
elsif \
|
92
177
|
[:defs, :asyncs].include? m.type and m.children.first.type == :self
|
93
178
|
then
|
94
179
|
|
@@ -108,27 +193,35 @@ module Ruby2JS
|
|
108
193
|
@prop.sub! 'static', 'static async' if m.type == :asyncs
|
109
194
|
|
110
195
|
m = m.updated(:def, m.children[1..3])
|
111
|
-
|
196
|
+
begin
|
197
|
+
@instance_method = nil
|
198
|
+
@class_method = m
|
199
|
+
parse m # unless skipped
|
200
|
+
ensure
|
201
|
+
@instance_method = nil
|
202
|
+
end
|
112
203
|
|
113
204
|
elsif m.type == :send and m.children.first == nil
|
205
|
+
p = es2020 ? '#' : '_'
|
206
|
+
|
114
207
|
if m.children[1] == :attr_accessor
|
115
208
|
m.children[2..-1].each_with_index do |child_sym, index2|
|
116
209
|
put @sep unless index2 == 0
|
117
210
|
var = child_sym.children.first
|
118
|
-
put "get #{var}() {#{@nl}return this
|
119
|
-
put "set #{var}(#{var}) {#{@nl}this
|
211
|
+
put "get #{var}() {#{@nl}return this.#{p}#{var}#@nl}#@sep"
|
212
|
+
put "set #{var}(#{var}) {#{@nl}this.#{p}#{var} = #{var}#@nl}"
|
120
213
|
end
|
121
214
|
elsif m.children[1] == :attr_reader
|
122
215
|
m.children[2..-1].each_with_index do |child_sym, index2|
|
123
216
|
put @sep unless index2 == 0
|
124
217
|
var = child_sym.children.first
|
125
|
-
put "get #{var}() {#{@nl}return this
|
218
|
+
put "get #{var}() {#{@nl}return this.#{p}#{var}#@nl}"
|
126
219
|
end
|
127
220
|
elsif m.children[1] == :attr_writer
|
128
221
|
m.children[2..-1].each_with_index do |child_sym, index2|
|
129
222
|
put @sep unless index2 == 0
|
130
223
|
var = child_sym.children.first
|
131
|
-
put "set #{var}(#{var}) {#{@nl}this
|
224
|
+
put "set #{var}(#{var}) {#{@nl}this.#{p}#{var} = #{var}#@nl}"
|
132
225
|
end
|
133
226
|
elsif [:private, :protected, :public].include? m.children[1]
|
134
227
|
raise Error.new("class #{m.children[1]} is not supported", @ast)
|
@@ -143,10 +236,21 @@ module Ruby2JS
|
|
143
236
|
end
|
144
237
|
|
145
238
|
else
|
146
|
-
|
239
|
+
if m.type == :cvasgn and es2020
|
240
|
+
put 'static #$'; put m.children[0].to_s[2..-1]; put ' = '
|
241
|
+
parse m.children[1]
|
242
|
+
else
|
243
|
+
skipped = true
|
244
|
+
end
|
147
245
|
|
148
246
|
if m.type == :casgn and m.children[0] == nil
|
149
247
|
@rbstack.last[m.children[1]] = name
|
248
|
+
|
249
|
+
if es2020
|
250
|
+
put 'static '; put m.children[1].to_s; put ' = '
|
251
|
+
parse m.children[2]
|
252
|
+
skipped = false
|
253
|
+
end
|
150
254
|
elsif m.type == :alias
|
151
255
|
@rbstack.last[m.children[0]] = name
|
152
256
|
end
|
@@ -4,14 +4,16 @@ module Ruby2JS
|
|
4
4
|
# (cvar :@@a)
|
5
5
|
|
6
6
|
handle :cvar do |var|
|
7
|
+
prefix = es2020 ? '#$' : '_'
|
8
|
+
|
7
9
|
@class_name ||= nil
|
8
10
|
if @class_name
|
9
11
|
parse @class_name
|
10
|
-
put var.to_s.sub('@@', "
|
12
|
+
put var.to_s.sub('@@', ".#{prefix}")
|
11
13
|
elsif @prototype
|
12
|
-
put var.to_s.sub('@@',
|
14
|
+
put var.to_s.sub('@@', "this.#{prefix}")
|
13
15
|
else
|
14
|
-
put var.to_s.sub('@@',
|
16
|
+
put var.to_s.sub('@@', "this.constructor.#{prefix}")
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -7,13 +7,15 @@ module Ruby2JS
|
|
7
7
|
handle :cvasgn do |var, expression=nil|
|
8
8
|
multi_assign_declarations if @state == :statement
|
9
9
|
|
10
|
+
prefix = es2020 ? '#$' : '_'
|
11
|
+
|
10
12
|
if @class_name
|
11
13
|
parse @class_name
|
12
|
-
put var.to_s.sub('@@', "
|
14
|
+
put var.to_s.sub('@@', ".#{prefix}")
|
13
15
|
elsif @prototype
|
14
|
-
put var.to_s.sub('@@',
|
16
|
+
put var.to_s.sub('@@', "this.#{prefix}")
|
15
17
|
else
|
16
|
-
put var.to_s.sub('@@',
|
18
|
+
put var.to_s.sub('@@', "this.constructor.#{prefix}")
|
17
19
|
end
|
18
20
|
|
19
21
|
if expression
|
@@ -88,7 +88,7 @@ module Ruby2JS
|
|
88
88
|
put 'async ' if @ast.type == :async
|
89
89
|
|
90
90
|
# es2015 fat arrow support
|
91
|
-
if
|
91
|
+
if \
|
92
92
|
not name and es2015 and @state != :method and @ast.type != :defm and
|
93
93
|
not @prop
|
94
94
|
then
|
@@ -105,7 +105,7 @@ module Ruby2JS
|
|
105
105
|
else
|
106
106
|
style = :expression
|
107
107
|
end
|
108
|
-
elsif
|
108
|
+
elsif \
|
109
109
|
expr.type == :if and expr.children[1] and expr.children[2] and
|
110
110
|
EXPRESSIONS.include? expr.children[1].type and
|
111
111
|
EXPRESSIONS.include? expr.children[2].type
|
@@ -10,11 +10,18 @@ module Ruby2JS
|
|
10
10
|
# (...)
|
11
11
|
|
12
12
|
handle :for, :for_of do |var, expression, block|
|
13
|
+
if @jsx and @ast.type == :for_of
|
14
|
+
parse s(:block, s(:send, expression, :map),
|
15
|
+
s(:args, s(:arg, var.children[0])),
|
16
|
+
s(:autoreturn, block))
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
13
20
|
begin
|
14
21
|
vars = @vars.dup
|
15
22
|
next_token, @next_token = @next_token, :continue
|
16
23
|
put "for (#{es2015 ? 'let' : 'var'} "; parse var
|
17
|
-
if [:irange, :erange].include? expression.type
|
24
|
+
if expression and [:irange, :erange].include? expression.type
|
18
25
|
put ' = '; parse expression.children.first; put '; '; parse var
|
19
26
|
(expression.type == :erange ? put(' < ') : put(' <= '))
|
20
27
|
parse expression.children.last; put '; '; parse var; put '++'
|
@@ -12,10 +12,26 @@ module Ruby2JS
|
|
12
12
|
|
13
13
|
(singleton ? put('{') : puts('{'))
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
index = 0
|
16
|
+
while pairs.length > 0
|
17
|
+
node = pairs.shift
|
18
18
|
(singleton ? put(', ') : put(",#@ws")) unless index == 0
|
19
|
+
index += 1
|
20
|
+
|
21
|
+
if node.type == :kwsplat
|
22
|
+
if es2018
|
23
|
+
if node.children.first.type == :hash
|
24
|
+
pairs.unshift(*node.children.first.children)
|
25
|
+
index = 0
|
26
|
+
else
|
27
|
+
puts '...'; parse node.children.first
|
28
|
+
end
|
29
|
+
|
30
|
+
next
|
31
|
+
else
|
32
|
+
raise Error.new("kwsplat", @ast)
|
33
|
+
end
|
34
|
+
end
|
19
35
|
|
20
36
|
if not @comments[node].empty?
|
21
37
|
(puts ''; singleton = false) if singleton
|
@@ -81,13 +97,13 @@ module Ruby2JS
|
|
81
97
|
end
|
82
98
|
end
|
83
99
|
|
84
|
-
if
|
100
|
+
if \
|
85
101
|
anonfn and
|
86
102
|
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
|
87
103
|
then
|
88
104
|
@prop = left.children.first
|
89
105
|
parse right, :method
|
90
|
-
elsif
|
106
|
+
elsif \
|
91
107
|
es2015 and left.type == :sym and right.type == :lvar and
|
92
108
|
left.children == right.children
|
93
109
|
then
|
@@ -97,7 +113,7 @@ module Ruby2JS
|
|
97
113
|
put '['
|
98
114
|
parse left
|
99
115
|
put ']'
|
100
|
-
elsif
|
116
|
+
elsif \
|
101
117
|
left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
|
102
118
|
then
|
103
119
|
put left.children.first
|
data/lib/ruby2js/converter/if.rb
CHANGED
@@ -55,8 +55,19 @@ module Ruby2JS
|
|
55
55
|
end
|
56
56
|
else
|
57
57
|
else_block ||= s(:nil)
|
58
|
-
|
59
|
-
|
58
|
+
|
59
|
+
if @jsx
|
60
|
+
if then_block.type == :begin
|
61
|
+
then_block = s(:xnode, '', *then_block.children)
|
62
|
+
end
|
63
|
+
|
64
|
+
if else_block.type == :begin
|
65
|
+
else_block = s(:xnode, '', *else_block.children)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
parse condition; put ' ? '; parse then_block
|
70
|
+
put ' : '; parse else_block
|
60
71
|
end
|
61
72
|
end
|
62
73
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Ruby2JS
|
2
|
+
class Converter
|
3
|
+
|
4
|
+
# (import str const)
|
5
|
+
|
6
|
+
# NOTE: import is a synthetic
|
7
|
+
|
8
|
+
handle :import do |path, *args|
|
9
|
+
put 'import '
|
10
|
+
|
11
|
+
args.each_with_index do |arg, index|
|
12
|
+
put ', ' unless index == 0
|
13
|
+
parse arg
|
14
|
+
end
|
15
|
+
|
16
|
+
put ' from '
|
17
|
+
put path.inspect
|
18
|
+
end
|
19
|
+
|
20
|
+
# (export const)
|
21
|
+
|
22
|
+
# NOTE: export is a synthetic
|
23
|
+
|
24
|
+
handle :export do |*args|
|
25
|
+
put 'export '
|
26
|
+
|
27
|
+
if args.first == :default
|
28
|
+
put 'default '
|
29
|
+
args.shift
|
30
|
+
end
|
31
|
+
|
32
|
+
args.each_with_index do |arg, index|
|
33
|
+
put ', ' unless index == 0
|
34
|
+
parse arg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -7,7 +7,7 @@ module Ruby2JS
|
|
7
7
|
handle :ivasgn do |var, expression=nil|
|
8
8
|
multi_assign_declarations if @state == :statement
|
9
9
|
|
10
|
-
put "#{ var.to_s.sub('@', 'this._') }"
|
10
|
+
put "#{ var.to_s.sub('@', 'this.' + (es2020 ? '#' : '_')) }"
|
11
11
|
if expression
|
12
12
|
put " = "; parse expression
|
13
13
|
end
|
@@ -51,13 +51,27 @@ module Ruby2JS
|
|
51
51
|
puts "try {"; scope body; sput '}'
|
52
52
|
|
53
53
|
if recovers
|
54
|
-
var ||= s(:gvar, :$EXCEPTION)
|
55
54
|
|
56
55
|
if recovers.length == 1 and not recovers.first.children.first
|
56
|
+
# find reference to exception ($!)
|
57
|
+
walk = proc do |ast|
|
58
|
+
result = ast if ast.type === :gvar and ast.children.first == :$!
|
59
|
+
ast.children.each do |child|
|
60
|
+
result ||= walk[child] if child.is_a? Parser::AST::Node
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
57
65
|
# single catch with no exception named
|
58
|
-
|
66
|
+
if es2019 and not var and not walk[@ast]
|
67
|
+
puts " catch {"
|
68
|
+
else
|
69
|
+
var ||= s(:gvar, :$EXCEPTION)
|
70
|
+
put " catch ("; parse var; puts ") {"
|
71
|
+
end
|
59
72
|
scope recovers.first.children.last; sput '}'
|
60
73
|
else
|
74
|
+
var ||= s(:gvar, :$EXCEPTION)
|
61
75
|
put " catch ("; parse var; puts ') {'
|
62
76
|
|
63
77
|
first = true
|
@@ -14,6 +14,17 @@ module Ruby2JS
|
|
14
14
|
|
15
15
|
handle :and, :or do |left, right|
|
16
16
|
type = @ast.type
|
17
|
+
|
18
|
+
|
19
|
+
if es2020 and type == :and
|
20
|
+
node = rewrite(left, right)
|
21
|
+
if node.type == :csend
|
22
|
+
return parse right.updated(node.type, node.children)
|
23
|
+
else
|
24
|
+
left, right = node.children
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
17
28
|
op_index = operator_index type
|
18
29
|
|
19
30
|
lgroup = LOGICAL.include?( left.type ) &&
|
@@ -25,7 +36,7 @@ module Ruby2JS
|
|
25
36
|
rgroup = true if right.type == :begin
|
26
37
|
|
27
38
|
put '(' if lgroup; parse left; put ')' if lgroup
|
28
|
-
put (type==:and ? ' && ' : ' || ')
|
39
|
+
put (type==:and ? ' && ' : ((@or == :nullish and es2020) ? ' ?? ' : ' || '))
|
29
40
|
put '(' if rgroup; parse right; put ')' if rgroup
|
30
41
|
end
|
31
42
|
|
@@ -51,5 +62,39 @@ module Ruby2JS
|
|
51
62
|
put '!'; put '(' if group; parse expr; put ')' if group
|
52
63
|
end
|
53
64
|
end
|
65
|
+
|
66
|
+
# rewrite a && a.b to a&.b
|
67
|
+
def rewrite(left, right)
|
68
|
+
if left && left.type == :and
|
69
|
+
left = rewrite(*left.children)
|
70
|
+
end
|
71
|
+
|
72
|
+
if right.type != :send
|
73
|
+
s(:and, left, right)
|
74
|
+
elsif conditionally_equals(left, right.children.first)
|
75
|
+
# a && a.b => a&.b
|
76
|
+
right.updated(:csend, [left, right.children.last])
|
77
|
+
elsif conditionally_equals(left.children.last, right.children.first)
|
78
|
+
# a && b && b.c => a && b&.c
|
79
|
+
left.updated(:and, [left.children.first,
|
80
|
+
left.children.last.updated(:csend,
|
81
|
+
[left.children.last, right.children.last])])
|
82
|
+
else
|
83
|
+
s(:and, left, right)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# determine if two trees are identical, modulo conditionalilties
|
88
|
+
# in other words a.b == a&.b
|
89
|
+
def conditionally_equals(left, right)
|
90
|
+
if left == right
|
91
|
+
true
|
92
|
+
elsif !left or !right or left.type != :csend or right.type != :send
|
93
|
+
false
|
94
|
+
else
|
95
|
+
conditionally_equals(left.children.first, right.children.first) &&
|
96
|
+
conditionally_equals(left.children.last, right.children.last)
|
97
|
+
end
|
98
|
+
end
|
54
99
|
end
|
55
100
|
end
|