emfrp 0.1.2 → 0.1.3
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 +4 -4
- data/README.md +45 -12
- data/bin/emfrp +4 -1
- data/examples/LCDClock/LCDClock.mfrp +93 -93
- data/examples/LCDClock/LCDClock_LPC1768.bin +0 -0
- data/examples/LCDClock/README.md +24 -24
- data/examples/LCDPositioner/LCDPositioner.mfrp +30 -30
- data/examples/LCDPositioner/LCDPositionerMain.c +15 -15
- data/examples/MostDistantPoint/MostDistantPoint.mfrp +25 -25
- data/examples/MostDistantPoint/MostDistantPointMain.c +14 -14
- data/lib/emfrp/compile/c/alloc.rb +200 -200
- data/lib/emfrp/compile/c/codegen.rb +18 -18
- data/lib/emfrp/compile/c/codegen_context.rb +218 -218
- data/lib/emfrp/compile/c/monofy.rb +185 -185
- data/lib/emfrp/compile/c/syntax_codegen.rb +364 -364
- data/lib/emfrp/compile/c/syntax_exp_codegen.rb +119 -119
- data/lib/emfrp/compile/graphviz/graphviz.rb +53 -53
- data/lib/emfrp/compile_error.rb +95 -95
- data/lib/emfrp/interpreter/command_manager.rb +367 -367
- data/lib/emfrp/interpreter/evaluater.rb +146 -146
- data/lib/emfrp/interpreter/file_loader.rb +52 -52
- data/lib/emfrp/interpreter/interpreter.rb +200 -195
- data/lib/emfrp/parser/expression.rb +386 -386
- data/lib/emfrp/parser/misc.rb +184 -184
- data/lib/emfrp/parser/newnode_convert.rb +72 -72
- data/lib/emfrp/parser/operator.rb +25 -25
- data/lib/emfrp/parser/parser.rb +150 -150
- data/lib/emfrp/parser/parsing_error.rb +49 -49
- data/lib/emfrp/parser/toplevel.rb +555 -555
- data/lib/emfrp/pre_convert/pre_convert.rb +32 -32
- data/lib/emfrp/syntax.rb +171 -171
- data/lib/emfrp/typing/typing_error.rb +47 -47
- data/lib/emfrp/typing/union_type.rb +197 -197
- data/lib/emfrp/version.rb +1 -1
- data/mfrp_include/Std.mfrp +122 -122
- data/tests/Rakefile +8 -8
- data/tests/Rakefile.common +27 -27
- data/tests/command/Rakefile +2 -2
- data/tests/command/ReplaceNode.mfrp +39 -39
- data/tests/compiler/ComplexDataType/ComplexDataType.mfrp +14 -14
- data/tests/compiler/ComplexDataType/ComplexDataTypeMain.c +15 -15
- data/tests/compiler/ComplexDataType/Rakefile +2 -2
- data/tests/compiler/ComplexDataType/expected_out.txt +0 -0
- data/tests/compiler/ComplexDataType/in.txt +5 -5
- data/tests/compiler/LCDClock/LCDClock.mfrp +90 -90
- data/tests/compiler/LCDClock/LCDClockMain.c +0 -0
- data/tests/compiler/LCDClock/Rakefile +2 -2
- data/tests/compiler/LCDClock/expected_out.txt +0 -0
- data/tests/compiler/LCDClock/in.txt +0 -0
- data/tests/compiler/LCDPositioner/LCDPositioner.mfrp +30 -30
- data/tests/compiler/LCDPositioner/LCDPositionerMain.c +15 -15
- data/tests/compiler/LCDPositioner/Rakefile +2 -2
- data/tests/compiler/LCDPositioner/graph.dot +0 -0
- data/tests/compiler/LCDPositioner/graph.png +0 -0
- data/tests/compiler/Rakefile +8 -8
- data/tests/compiler/Rakefile.common +23 -23
- data/tests/compiler/UseData/Rakefile +2 -2
- data/tests/compiler/UseData/UseData.mfrp +8 -8
- data/tests/compiler/UseSubModule/Rakefile +2 -2
- data/tests/compiler/UseSubModule/SubModule.mfrp +8 -8
- data/tests/compiler/UseSubModule/SubModule2.mfrp +5 -5
- data/tests/compiler/UseSubModule/UseSubModule.mfrp +11 -11
- data/tests/core/FromAnnotation.mfrp +18 -18
- data/tests/core/Last.mfrp +10 -10
- data/tests/core/Rakefile +2 -2
- data/tests/core/TypingTest.mfrp +11 -11
- data/tests/core/WithoutInputs.mfrp +19 -19
- data/tests/load_time_error/Rakefile +32 -32
- data/tests/load_time_error/TypeMismatch.mfrp +4 -4
- metadata +3 -3
@@ -1,200 +1,200 @@
|
|
1
|
-
module Emfrp
|
2
|
-
class AllocRequirement
|
3
|
-
def initialize(top)
|
4
|
-
@top = top
|
5
|
-
@alloc_table = AllocTable.new(top)
|
6
|
-
@sorted_nodes = @top[:dict][:sorted_nodes].map{|x| x.get}
|
7
|
-
end
|
8
|
-
|
9
|
-
def requirement
|
10
|
-
sorted_datas = @top[:dict][:sorted_datas].map{|x| x.get}
|
11
|
-
init_nodes = @sorted_nodes.select{|n| n[:init_exp]}
|
12
|
-
max_amount = Alloc.empty
|
13
|
-
max_amount = data_requirement(sorted_datas, max_amount)
|
14
|
-
max_amount = node_init_requirement(init_nodes, max_amount, sorted_datas)
|
15
|
-
max_amount = node_loop_requirement(max_amount, init_nodes, sorted_datas)
|
16
|
-
return max_amount
|
17
|
-
end
|
18
|
-
|
19
|
-
def data_requirement(sorted_datas, max_amount)
|
20
|
-
retains = []
|
21
|
-
sorted_datas.each do |d|
|
22
|
-
max_amount |= type_alloc_sum(retains) & exp_alloc(d[:exp])
|
23
|
-
end
|
24
|
-
return max_amount
|
25
|
-
end
|
26
|
-
|
27
|
-
def node_init_requirement(init_nodes, max_amount, datas)
|
28
|
-
retains = datas.clone
|
29
|
-
init_nodes.each do |n|
|
30
|
-
max_amount |= type_alloc_sum(retains) & exp_alloc(n[:init_exp])
|
31
|
-
retains << n
|
32
|
-
end
|
33
|
-
return max_amount
|
34
|
-
end
|
35
|
-
|
36
|
-
def node_loop_requirement(max_amount, init_nodes, datas)
|
37
|
-
lrefs = init_nodes
|
38
|
-
crefs = []
|
39
|
-
@sorted_nodes.each_with_index do |n, i|
|
40
|
-
max_amount |= type_alloc_sum(datas + lrefs + crefs) & exp_alloc(n[:exp])
|
41
|
-
crefs << n
|
42
|
-
lrefs.reject! do |x|
|
43
|
-
i >= ref_pos_last(x)
|
44
|
-
end
|
45
|
-
crefs.reject! do |x|
|
46
|
-
i >= ref_pos_current(x)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
return max_amount
|
50
|
-
end
|
51
|
-
|
52
|
-
def ref_pos_last(node)
|
53
|
-
res = -1
|
54
|
-
@sorted_nodes.each_with_index do |n, i|
|
55
|
-
if n[:params].any?{|param| param[:last] && param[:name] == node[:name]}
|
56
|
-
res = i
|
57
|
-
end
|
58
|
-
end
|
59
|
-
return res
|
60
|
-
end
|
61
|
-
|
62
|
-
def ref_pos_current(node)
|
63
|
-
res = -1
|
64
|
-
@sorted_nodes.each_with_index do |n, i|
|
65
|
-
if n[:params].any?{|param| !param[:last] && param[:name] == node[:name]}
|
66
|
-
res = i
|
67
|
-
end
|
68
|
-
end
|
69
|
-
return res
|
70
|
-
end
|
71
|
-
|
72
|
-
def life_point(node)
|
73
|
-
self_position = @sorted_nodes.index{|x| x == node}
|
74
|
-
distance_to_end = @sorted_nodes.size - self_position
|
75
|
-
res = []
|
76
|
-
@sorted_nodes.each_with_index do |x, i|
|
77
|
-
x[:params].each do |param|
|
78
|
-
if param[:name] == node[:name]
|
79
|
-
if param[:last]
|
80
|
-
res << distance_to_end + i
|
81
|
-
else
|
82
|
-
res << i - self_position
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
if res == []
|
88
|
-
raise "Assertion error"
|
89
|
-
else
|
90
|
-
return res.max
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def type_alloc_sum(defs)
|
95
|
-
defs.inject(Alloc.empty) do |acc, d|
|
96
|
-
type_def = @alloc_table.utype_to_type_def(d[:typing])
|
97
|
-
acc & @alloc_table.type_alloc(type_def)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def exp_alloc(exp)
|
102
|
-
@alloc_table.exp_alloc(exp)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
class AllocTable
|
107
|
-
def initialize(top)
|
108
|
-
@top = top
|
109
|
-
@tbl = {}
|
110
|
-
@type_tbl = {}
|
111
|
-
end
|
112
|
-
|
113
|
-
def utype_to_type_def(utype)
|
114
|
-
if t = @top[:dict][:itype_space][utype.to_uniq_str]
|
115
|
-
t.get
|
116
|
-
elsif t = @top[:dict][:type_space][utype.typename]
|
117
|
-
t.get
|
118
|
-
else
|
119
|
-
raise "Assertion error"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def type_alloc(type_def)
|
124
|
-
return @type_tbl[type_def] if @type_tbl[type_def]
|
125
|
-
case type_def
|
126
|
-
when TypeDef
|
127
|
-
tvalue_type_max_allocs = type_def[:tvalues].map do |tval|
|
128
|
-
param_type_max_allocs = tval[:params].map do |param|
|
129
|
-
type_alloc(utype_to_type_def(param[:typing]))
|
130
|
-
end
|
131
|
-
param_type_max_allocs.inject(Alloc.empty, &:&)
|
132
|
-
end
|
133
|
-
self_type_alloc = type_def[:static] ? Alloc.empty : Alloc.one(Link.new(type_def))
|
134
|
-
@type_tbl[type_def] = tvalue_type_max_allocs.inject(&:|) & self_type_alloc
|
135
|
-
when PrimTypeDef
|
136
|
-
@type_tbl[type_def] = Alloc.empty
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def exp_alloc(exp)
|
141
|
-
return @tbl[exp] if @tbl[exp]
|
142
|
-
case exp
|
143
|
-
when MatchExp
|
144
|
-
@tbl[exp] = exp[:cases].map{|c| exp_alloc(c[:exp])}.inject(&:|) & exp_alloc(exp[:exp])
|
145
|
-
when FuncCall
|
146
|
-
args_alloc = exp[:args].map{|x| exp_alloc(x)}.inject(&:&)
|
147
|
-
key = ([exp] + exp[:args]).map{|x| x[:typing].to_uniq_str} + [exp[:name][:desc]]
|
148
|
-
if @top[:dict][:ifunc_space][key]
|
149
|
-
f = @top[:dict][:ifunc_space][key].get
|
150
|
-
@tbl[exp] = args_alloc & exp_alloc(f[:exp])
|
151
|
-
elsif f = @top[:dict][:func_space][exp[:name][:desc]]
|
152
|
-
if f.get.is_a?(PrimFuncDef)
|
153
|
-
@tbl[exp] = args_alloc
|
154
|
-
else
|
155
|
-
raise "Assertion error"
|
156
|
-
end
|
157
|
-
else
|
158
|
-
raise "Assertion error"
|
159
|
-
end
|
160
|
-
when ValueConst
|
161
|
-
args_alloc = exp[:args].map{|x| exp_alloc(x)}.inject(Alloc.empty, &:&)
|
162
|
-
key = exp[:typing].to_uniq_str
|
163
|
-
raise "Assertion error" unless @top[:dict][:itype_space][key]
|
164
|
-
type_def = @top[:dict][:itype_space][key].get
|
165
|
-
@tbl[exp] = args_alloc & Alloc.one(Link.new(type_def))
|
166
|
-
when Syntax
|
167
|
-
@tbl[exp] = Alloc.empty
|
168
|
-
else
|
169
|
-
raise "Assertion error"
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
class Alloc
|
175
|
-
attr_reader :h
|
176
|
-
def initialize(hash)
|
177
|
-
@h = hash
|
178
|
-
end
|
179
|
-
|
180
|
-
def self.empty
|
181
|
-
self.new({})
|
182
|
-
end
|
183
|
-
|
184
|
-
def self.one(k)
|
185
|
-
self.new(k => 1)
|
186
|
-
end
|
187
|
-
|
188
|
-
def |(other)
|
189
|
-
Alloc.new self.h.merge(other.h){|k, v1, v2| [v1, v2].max}
|
190
|
-
end
|
191
|
-
|
192
|
-
def &(other)
|
193
|
-
Alloc.new self.h.merge(other.h){|k, v1, v2| v1 + v2}
|
194
|
-
end
|
195
|
-
|
196
|
-
def each(&block)
|
197
|
-
@h.each(&block)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
1
|
+
module Emfrp
|
2
|
+
class AllocRequirement
|
3
|
+
def initialize(top)
|
4
|
+
@top = top
|
5
|
+
@alloc_table = AllocTable.new(top)
|
6
|
+
@sorted_nodes = @top[:dict][:sorted_nodes].map{|x| x.get}
|
7
|
+
end
|
8
|
+
|
9
|
+
def requirement
|
10
|
+
sorted_datas = @top[:dict][:sorted_datas].map{|x| x.get}
|
11
|
+
init_nodes = @sorted_nodes.select{|n| n[:init_exp]}
|
12
|
+
max_amount = Alloc.empty
|
13
|
+
max_amount = data_requirement(sorted_datas, max_amount)
|
14
|
+
max_amount = node_init_requirement(init_nodes, max_amount, sorted_datas)
|
15
|
+
max_amount = node_loop_requirement(max_amount, init_nodes, sorted_datas)
|
16
|
+
return max_amount
|
17
|
+
end
|
18
|
+
|
19
|
+
def data_requirement(sorted_datas, max_amount)
|
20
|
+
retains = []
|
21
|
+
sorted_datas.each do |d|
|
22
|
+
max_amount |= type_alloc_sum(retains) & exp_alloc(d[:exp])
|
23
|
+
end
|
24
|
+
return max_amount
|
25
|
+
end
|
26
|
+
|
27
|
+
def node_init_requirement(init_nodes, max_amount, datas)
|
28
|
+
retains = datas.clone
|
29
|
+
init_nodes.each do |n|
|
30
|
+
max_amount |= type_alloc_sum(retains) & exp_alloc(n[:init_exp])
|
31
|
+
retains << n
|
32
|
+
end
|
33
|
+
return max_amount
|
34
|
+
end
|
35
|
+
|
36
|
+
def node_loop_requirement(max_amount, init_nodes, datas)
|
37
|
+
lrefs = init_nodes
|
38
|
+
crefs = []
|
39
|
+
@sorted_nodes.each_with_index do |n, i|
|
40
|
+
max_amount |= type_alloc_sum(datas + lrefs + crefs) & exp_alloc(n[:exp])
|
41
|
+
crefs << n
|
42
|
+
lrefs.reject! do |x|
|
43
|
+
i >= ref_pos_last(x)
|
44
|
+
end
|
45
|
+
crefs.reject! do |x|
|
46
|
+
i >= ref_pos_current(x)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
return max_amount
|
50
|
+
end
|
51
|
+
|
52
|
+
def ref_pos_last(node)
|
53
|
+
res = -1
|
54
|
+
@sorted_nodes.each_with_index do |n, i|
|
55
|
+
if n[:params].any?{|param| param[:last] && param[:name] == node[:name]}
|
56
|
+
res = i
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return res
|
60
|
+
end
|
61
|
+
|
62
|
+
def ref_pos_current(node)
|
63
|
+
res = -1
|
64
|
+
@sorted_nodes.each_with_index do |n, i|
|
65
|
+
if n[:params].any?{|param| !param[:last] && param[:name] == node[:name]}
|
66
|
+
res = i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return res
|
70
|
+
end
|
71
|
+
|
72
|
+
def life_point(node)
|
73
|
+
self_position = @sorted_nodes.index{|x| x == node}
|
74
|
+
distance_to_end = @sorted_nodes.size - self_position
|
75
|
+
res = []
|
76
|
+
@sorted_nodes.each_with_index do |x, i|
|
77
|
+
x[:params].each do |param|
|
78
|
+
if param[:name] == node[:name]
|
79
|
+
if param[:last]
|
80
|
+
res << distance_to_end + i
|
81
|
+
else
|
82
|
+
res << i - self_position
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
if res == []
|
88
|
+
raise "Assertion error"
|
89
|
+
else
|
90
|
+
return res.max
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def type_alloc_sum(defs)
|
95
|
+
defs.inject(Alloc.empty) do |acc, d|
|
96
|
+
type_def = @alloc_table.utype_to_type_def(d[:typing])
|
97
|
+
acc & @alloc_table.type_alloc(type_def)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def exp_alloc(exp)
|
102
|
+
@alloc_table.exp_alloc(exp)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class AllocTable
|
107
|
+
def initialize(top)
|
108
|
+
@top = top
|
109
|
+
@tbl = {}
|
110
|
+
@type_tbl = {}
|
111
|
+
end
|
112
|
+
|
113
|
+
def utype_to_type_def(utype)
|
114
|
+
if t = @top[:dict][:itype_space][utype.to_uniq_str]
|
115
|
+
t.get
|
116
|
+
elsif t = @top[:dict][:type_space][utype.typename]
|
117
|
+
t.get
|
118
|
+
else
|
119
|
+
raise "Assertion error"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def type_alloc(type_def)
|
124
|
+
return @type_tbl[type_def] if @type_tbl[type_def]
|
125
|
+
case type_def
|
126
|
+
when TypeDef
|
127
|
+
tvalue_type_max_allocs = type_def[:tvalues].map do |tval|
|
128
|
+
param_type_max_allocs = tval[:params].map do |param|
|
129
|
+
type_alloc(utype_to_type_def(param[:typing]))
|
130
|
+
end
|
131
|
+
param_type_max_allocs.inject(Alloc.empty, &:&)
|
132
|
+
end
|
133
|
+
self_type_alloc = type_def[:static] ? Alloc.empty : Alloc.one(Link.new(type_def))
|
134
|
+
@type_tbl[type_def] = tvalue_type_max_allocs.inject(&:|) & self_type_alloc
|
135
|
+
when PrimTypeDef
|
136
|
+
@type_tbl[type_def] = Alloc.empty
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def exp_alloc(exp)
|
141
|
+
return @tbl[exp] if @tbl[exp]
|
142
|
+
case exp
|
143
|
+
when MatchExp
|
144
|
+
@tbl[exp] = exp[:cases].map{|c| exp_alloc(c[:exp])}.inject(&:|) & exp_alloc(exp[:exp])
|
145
|
+
when FuncCall
|
146
|
+
args_alloc = exp[:args].map{|x| exp_alloc(x)}.inject(&:&)
|
147
|
+
key = ([exp] + exp[:args]).map{|x| x[:typing].to_uniq_str} + [exp[:name][:desc]]
|
148
|
+
if @top[:dict][:ifunc_space][key]
|
149
|
+
f = @top[:dict][:ifunc_space][key].get
|
150
|
+
@tbl[exp] = args_alloc & exp_alloc(f[:exp])
|
151
|
+
elsif f = @top[:dict][:func_space][exp[:name][:desc]]
|
152
|
+
if f.get.is_a?(PrimFuncDef)
|
153
|
+
@tbl[exp] = args_alloc
|
154
|
+
else
|
155
|
+
raise "Assertion error"
|
156
|
+
end
|
157
|
+
else
|
158
|
+
raise "Assertion error"
|
159
|
+
end
|
160
|
+
when ValueConst
|
161
|
+
args_alloc = exp[:args].map{|x| exp_alloc(x)}.inject(Alloc.empty, &:&)
|
162
|
+
key = exp[:typing].to_uniq_str
|
163
|
+
raise "Assertion error" unless @top[:dict][:itype_space][key]
|
164
|
+
type_def = @top[:dict][:itype_space][key].get
|
165
|
+
@tbl[exp] = args_alloc & Alloc.one(Link.new(type_def))
|
166
|
+
when Syntax
|
167
|
+
@tbl[exp] = Alloc.empty
|
168
|
+
else
|
169
|
+
raise "Assertion error"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Alloc
|
175
|
+
attr_reader :h
|
176
|
+
def initialize(hash)
|
177
|
+
@h = hash
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.empty
|
181
|
+
self.new({})
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.one(k)
|
185
|
+
self.new(k => 1)
|
186
|
+
end
|
187
|
+
|
188
|
+
def |(other)
|
189
|
+
Alloc.new self.h.merge(other.h){|k, v1, v2| [v1, v2].max}
|
190
|
+
end
|
191
|
+
|
192
|
+
def &(other)
|
193
|
+
Alloc.new self.h.merge(other.h){|k, v1, v2| v1 + v2}
|
194
|
+
end
|
195
|
+
|
196
|
+
def each(&block)
|
197
|
+
@h.each(&block)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
|
-
require 'emfrp/compile/c/monofy'
|
2
|
-
require 'emfrp/compile/c/alloc'
|
3
|
-
require 'emfrp/compile/c/codegen_context'
|
4
|
-
require 'emfrp/compile/c/syntax_codegen'
|
5
|
-
|
6
|
-
module Emfrp
|
7
|
-
module Codegen
|
8
|
-
extend self
|
9
|
-
|
10
|
-
def codegen(top, c_output, h_output, main_output, name)
|
11
|
-
Monofy.monofy(top)
|
12
|
-
ct = CodegenContext.new(top)
|
13
|
-
ar = AllocRequirement.new(top)
|
14
|
-
top.codegen(ct, ar)
|
15
|
-
ct.code_generate(c_output, h_output, main_output, name)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
1
|
+
require 'emfrp/compile/c/monofy'
|
2
|
+
require 'emfrp/compile/c/alloc'
|
3
|
+
require 'emfrp/compile/c/codegen_context'
|
4
|
+
require 'emfrp/compile/c/syntax_codegen'
|
5
|
+
|
6
|
+
module Emfrp
|
7
|
+
module Codegen
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def codegen(top, c_output, h_output, main_output, name)
|
11
|
+
Monofy.monofy(top)
|
12
|
+
ct = CodegenContext.new(top)
|
13
|
+
ar = AllocRequirement.new(top)
|
14
|
+
top.codegen(ct, ar)
|
15
|
+
ct.code_generate(c_output, h_output, main_output, name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|