riml 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +148 -0
- data/bin/riml +69 -0
- data/config/environment.rb +10 -0
- data/lib/ast_rewriter.rb +268 -0
- data/lib/class_map.rb +46 -0
- data/lib/compiler.rb +579 -0
- data/lib/constants.rb +280 -0
- data/lib/errors.rb +4 -0
- data/lib/grammar.y +485 -0
- data/lib/helper.rb +45 -0
- data/lib/lexer.rb +276 -0
- data/lib/nodes.rb +723 -0
- data/lib/parser.rb +2748 -0
- data/lib/walker.rb +15 -0
- data/version.rb +4 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 by Luke Gruber
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included
|
12
|
+
in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/luke-gru/riml.png)](https://travis-ci.org/luke-gru/riml)
|
2
|
+
|
3
|
+
Riml, a relaxed version of Vimscript
|
4
|
+
====================================
|
5
|
+
|
6
|
+
Riml aims to be a superset of VimL that includes some nice features that I
|
7
|
+
enjoy in other scripting languages, including classes, string interpolation,
|
8
|
+
heredocs, default case-sensitive string comparison and other things most
|
9
|
+
programmers take for granted. Also, Riml takes some liberties and provides
|
10
|
+
some syntactic sugar for lots of VimL constructs. To see how Riml constructs
|
11
|
+
are compiled into VimL, just take a look in this README. The left side is Riml,
|
12
|
+
and the right side is the equivalent VimL after compilation.
|
13
|
+
|
14
|
+
Variables
|
15
|
+
---------
|
16
|
+
|
17
|
+
count = 1 let s:count = 1
|
18
|
+
while count < 5 while s:count < 5
|
19
|
+
source other.vim source other.vim
|
20
|
+
count += 1 let s:count += 1
|
21
|
+
end endwhile
|
22
|
+
|
23
|
+
If you don't specify a scope modifier, it's script local by default in the
|
24
|
+
global namespace. Within a function, variables without scope modifiers are plain
|
25
|
+
old local variables.
|
26
|
+
|
27
|
+
###globally
|
28
|
+
|
29
|
+
a = 3 let s:a = 3
|
30
|
+
|
31
|
+
###locally
|
32
|
+
|
33
|
+
a = 3 let a = 3
|
34
|
+
|
35
|
+
###Freeing memory
|
36
|
+
|
37
|
+
a = nil unlet! a
|
38
|
+
|
39
|
+
###Checking for existence
|
40
|
+
|
41
|
+
unless s:callcount? if !exists("s:callcount")
|
42
|
+
callcount = 0 let s:callcount = 0
|
43
|
+
end endif
|
44
|
+
callcount += 1 let s:callcount += 1
|
45
|
+
puts "called #{callcount} times" echo "called " . s:callcount . " times"
|
46
|
+
|
47
|
+
Comparisons
|
48
|
+
-----------
|
49
|
+
|
50
|
+
a = "hi" == "hi" if ("hi" ==# "hi")
|
51
|
+
let s:a = 1
|
52
|
+
else
|
53
|
+
let s:a = 0
|
54
|
+
endif
|
55
|
+
|
56
|
+
Heredocs
|
57
|
+
--------
|
58
|
+
|
59
|
+
msg = <<EOS let s:msg = "a vim heredoc!\n"
|
60
|
+
a vim heredoc!
|
61
|
+
EOS
|
62
|
+
|
63
|
+
Classes
|
64
|
+
-------
|
65
|
+
|
66
|
+
###Riml example 1
|
67
|
+
|
68
|
+
class MyClass
|
69
|
+
def initialize(arg1, arg2, *args)
|
70
|
+
end
|
71
|
+
|
72
|
+
defm getData
|
73
|
+
return self.data
|
74
|
+
end
|
75
|
+
|
76
|
+
defm getOtherData
|
77
|
+
return self.otherData
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
###Viml example 1
|
82
|
+
|
83
|
+
|
84
|
+
function! g:MyClassConstructor(arg1, arg2, ...)
|
85
|
+
let myClassObj = {}
|
86
|
+
function! myClassObj.getData() dict
|
87
|
+
return self.data
|
88
|
+
endfunction
|
89
|
+
function! myClassObj.getOtherData() dict
|
90
|
+
return self.otherData
|
91
|
+
endfunction
|
92
|
+
return myClassObj
|
93
|
+
endfunction
|
94
|
+
|
95
|
+
###Riml example 2
|
96
|
+
|
97
|
+
class Translation
|
98
|
+
def initialize(input)
|
99
|
+
self.input = input
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class FrenchToEnglishTranslation < Translation
|
104
|
+
defm translate
|
105
|
+
if (self.input == "Bonjour!")
|
106
|
+
echo "Hello!"
|
107
|
+
else
|
108
|
+
echo "Sorry, I don't know that word."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
translation = new FrenchToEnglishTranslation("Bonjour!")
|
114
|
+
translation.translate()
|
115
|
+
|
116
|
+
###VimL example 2
|
117
|
+
|
118
|
+
function! g:TranslationConstructor(input)
|
119
|
+
let translationObj = {}
|
120
|
+
let translationObj.input = a:input
|
121
|
+
return translationObj
|
122
|
+
endfunction
|
123
|
+
|
124
|
+
function! g:FrenchToEnglishTranslationConstructor(input)
|
125
|
+
let frenchToEnglishTranslationObj = {}
|
126
|
+
let translationObj = g:TranslationConstructor(a:input)
|
127
|
+
call extend(frenchToEnglishTranslationObj, translationObj)
|
128
|
+
function! frenchToEnglishTranslationObj.translate() dict
|
129
|
+
if (self.input ==# "Bonjour!")
|
130
|
+
echo "Hello!"
|
131
|
+
else
|
132
|
+
echo "Sorry, I don't know that word."
|
133
|
+
endif
|
134
|
+
endfunction
|
135
|
+
return frenchToEnglishTranslationObj
|
136
|
+
endfunction
|
137
|
+
|
138
|
+
let s:translation = g:FrenchToEnglishTranslationConstructor("Bonjour!")
|
139
|
+
call s:translation.translate()
|
140
|
+
|
141
|
+
Hacking
|
142
|
+
-------
|
143
|
+
|
144
|
+
Make sure to generate the parser before running tests or developing on Riml.
|
145
|
+
Also, make sure to regenerate the parser after modifiying the grammar file.
|
146
|
+
|
147
|
+
1. `bundle install`
|
148
|
+
2. Go to the lib directory and enter `racc -o parser.rb grammar.y`
|
data/bin/riml
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: syntax=ruby
|
3
|
+
|
4
|
+
require File.expand_path("../../config/environment", __FILE__)
|
5
|
+
|
6
|
+
module Riml
|
7
|
+
include Environment
|
8
|
+
require File.join(ROOTDIR, 'version')
|
9
|
+
require File.join(LIBDIR, "helper")
|
10
|
+
|
11
|
+
require 'optparse'
|
12
|
+
require 'ostruct'
|
13
|
+
|
14
|
+
class Options
|
15
|
+
def self.parse(argv)
|
16
|
+
|
17
|
+
# defaults
|
18
|
+
options = OpenStruct.new
|
19
|
+
options.compile = []
|
20
|
+
options.riml_source_path = Dir.getwd
|
21
|
+
|
22
|
+
OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: riml [options]"
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Specific options:"
|
26
|
+
|
27
|
+
opts.on("-c", "--compile FILE", "Compile riml file to VimL") do |file|
|
28
|
+
if File.exists?(file)
|
29
|
+
options.compile << file
|
30
|
+
else
|
31
|
+
warn "Couldn't find file #{file.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-s", "--stdio", "pipe in riml to STDIN and get back VimL on STDOUT") do
|
36
|
+
options.stdio = true
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on_tail("-v", "--version", "Show riml version") do
|
40
|
+
puts VERSION.join('.')
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end.parse!(argv)
|
49
|
+
|
50
|
+
options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Runner
|
55
|
+
class << self
|
56
|
+
def start
|
57
|
+
options = Options.parse(ARGV)
|
58
|
+
if options.stdio
|
59
|
+
puts Riml.compile($stdin.gets)
|
60
|
+
elsif options.compile.any?
|
61
|
+
options.compile.each do |file|
|
62
|
+
Riml.compile_file(file)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
Runner.start
|
69
|
+
end
|
data/lib/ast_rewriter.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require File.expand_path("../constants", __FILE__)
|
2
|
+
require File.expand_path("../class_map", __FILE__)
|
3
|
+
require File.expand_path("../walker", __FILE__)
|
4
|
+
|
5
|
+
module Riml
|
6
|
+
class AST_Rewriter
|
7
|
+
include Riml::Constants
|
8
|
+
|
9
|
+
attr_reader :ast
|
10
|
+
def initialize(ast)
|
11
|
+
@ast = ast
|
12
|
+
end
|
13
|
+
|
14
|
+
# Map of {"ClassName" => ClassDefinitionNode}
|
15
|
+
# Can also query object for superclass of a named class, etc...
|
16
|
+
#
|
17
|
+
# Ex : classes["SomeClass"].superclass_name => "SomeClassBase"
|
18
|
+
def classes
|
19
|
+
@@classes ||= ClassMap.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def rewrite
|
23
|
+
establish_parents(ast)
|
24
|
+
StrictEqualsComparisonOperator.new(ast).rewrite_on_match
|
25
|
+
VarEqualsComparisonOperator.new(ast).rewrite_on_match
|
26
|
+
ClassDefinitionToFunctions.new(ast).rewrite_on_match
|
27
|
+
ObjectInstantiationToCall.new(ast).rewrite_on_match
|
28
|
+
CallToExplicitCall.new(ast).rewrite_on_match
|
29
|
+
ast
|
30
|
+
end
|
31
|
+
|
32
|
+
def establish_parents(node)
|
33
|
+
Walker.walk_node(node, method(:do_establish_parents))
|
34
|
+
end
|
35
|
+
alias reestablish_parents establish_parents
|
36
|
+
|
37
|
+
def do_establish_parents(node)
|
38
|
+
node.children.each do |child|
|
39
|
+
child.parent_node = node if child.respond_to?(:parent_node=)
|
40
|
+
end if node.respond_to?(:children)
|
41
|
+
end
|
42
|
+
|
43
|
+
def rewrite_on_match(node = ast)
|
44
|
+
Walker.walk_node(node, method(:do_rewrite_on_match), lambda {|_| repeatable?})
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_rewrite_on_match(node)
|
48
|
+
replace node if match?(node)
|
49
|
+
end
|
50
|
+
|
51
|
+
def repeatable?
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
class StrictEqualsComparisonOperator < AST_Rewriter
|
56
|
+
def match?(node)
|
57
|
+
BinaryOperatorNode === node && node.operator == '==='
|
58
|
+
end
|
59
|
+
|
60
|
+
def replace(node)
|
61
|
+
node.operator = '=='
|
62
|
+
node.operand1 = ListNode.wrap(node.operand1)
|
63
|
+
node.operand2 = ListNode.wrap(node.operand2)
|
64
|
+
reestablish_parents(node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class VarEqualsComparisonOperator < AST_Rewriter
|
69
|
+
COMPARISON_OPERATOR_MATCH = Regexp.union(COMPARISON_OPERATORS)
|
70
|
+
|
71
|
+
def match?(node)
|
72
|
+
Nodes === node &&
|
73
|
+
AssignNode === node.nodes[0] &&
|
74
|
+
BinaryOperatorNode === (op = node.nodes[0].rhs) &&
|
75
|
+
op.operator =~ COMPARISON_OPERATOR_MATCH
|
76
|
+
end
|
77
|
+
|
78
|
+
def replace(node)
|
79
|
+
binary_op = node.nodes[0].rhs
|
80
|
+
old_set_var = node.nodes[0]
|
81
|
+
assign_true = old_set_var.clone.tap {|assign_t| assign_t.rhs = TrueNode.new }
|
82
|
+
assign_false = old_set_var.clone.tap {|assign_f| assign_f.rhs = FalseNode.new }
|
83
|
+
node.nodes = [
|
84
|
+
IfNode.new(binary_op, Nodes.new([
|
85
|
+
assign_true, ElseNode.new(Nodes.new([
|
86
|
+
assign_false
|
87
|
+
]))
|
88
|
+
]))
|
89
|
+
]
|
90
|
+
reestablish_parents(node)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class ClassDefinitionToFunctions < AST_Rewriter
|
95
|
+
def match?(node)
|
96
|
+
ClassDefinitionNode === node
|
97
|
+
end
|
98
|
+
|
99
|
+
def replace(node)
|
100
|
+
classes[node.name] = node
|
101
|
+
|
102
|
+
name, expressions = node.name, node.expressions
|
103
|
+
InsertInitializeMethod.new(node).rewrite_on_match
|
104
|
+
constructor = node.constructor
|
105
|
+
constructor.scope_modifier = 'g:' unless constructor.scope_modifier
|
106
|
+
constructor.name = node.constructor_name
|
107
|
+
# set up dictionary variable at top of function
|
108
|
+
dict_name = node.constructor_obj_name
|
109
|
+
constructor.expressions.unshift(
|
110
|
+
AssignNode.new('=', GetVariableNode.new(nil, dict_name), DictionaryNode.new({}))
|
111
|
+
)
|
112
|
+
|
113
|
+
SuperToObjectExtension.new(constructor, node).rewrite_on_match
|
114
|
+
MethodToNestedFunction.new(node, constructor, dict_name).rewrite_on_match
|
115
|
+
SelfToDictName.new(dict_name).rewrite_on_match(constructor)
|
116
|
+
|
117
|
+
constructor.expressions.push(
|
118
|
+
ReturnNode.new(GetVariableNode.new(nil, dict_name))
|
119
|
+
)
|
120
|
+
reestablish_parents(constructor)
|
121
|
+
end
|
122
|
+
|
123
|
+
class MethodToNestedFunction < AST_Rewriter
|
124
|
+
attr_reader :constructor, :dict_name
|
125
|
+
def initialize(class_node, constructor, dict_name)
|
126
|
+
super(class_node)
|
127
|
+
@dict_name, @constructor = dict_name, constructor
|
128
|
+
end
|
129
|
+
|
130
|
+
def match?(node)
|
131
|
+
DefMethodNode === node
|
132
|
+
end
|
133
|
+
|
134
|
+
def replace(node)
|
135
|
+
def_node = node.to_def_node
|
136
|
+
node.parent_node = ast.expressions
|
137
|
+
node.remove
|
138
|
+
def_node.name.insert(0, "#{dict_name}.")
|
139
|
+
def_node.parent_node = constructor.expressions
|
140
|
+
constructor.expressions << def_node
|
141
|
+
reestablish_parents(node)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class SelfToDictName < AST_Rewriter
|
146
|
+
attr_reader :dict_name
|
147
|
+
def initialize(dict_name)
|
148
|
+
@dict_name = dict_name
|
149
|
+
end
|
150
|
+
|
151
|
+
def match?(node)
|
152
|
+
AssignNode === node && DictGetNode === node.lhs && node.lhs.dict.name == "self"
|
153
|
+
end
|
154
|
+
|
155
|
+
def replace(node)
|
156
|
+
node.lhs.dict.name = dict_name
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class InsertInitializeMethod < AST_Rewriter
|
161
|
+
# if doesn't have an initialize method, put one at the beginning
|
162
|
+
# of the class definition
|
163
|
+
def match?(class_node)
|
164
|
+
ClassDefinitionNode === class_node && class_node.constructor.nil?
|
165
|
+
end
|
166
|
+
|
167
|
+
def replace(class_node)
|
168
|
+
if class_node.superclass?
|
169
|
+
def_node = DefNode.new(
|
170
|
+
'!', nil, "initialize", superclass_params, nil, Nodes.new([SuperNode.new([], false)])
|
171
|
+
)
|
172
|
+
else
|
173
|
+
def_node = DefNode.new(
|
174
|
+
'!', nil, "initialize", [], nil, Nodes.new([])
|
175
|
+
)
|
176
|
+
end
|
177
|
+
class_node.expressions.unshift(def_node)
|
178
|
+
reestablish_parents(class_node)
|
179
|
+
end
|
180
|
+
|
181
|
+
def superclass_params
|
182
|
+
classes.superclass(ast.name).constructor.parameters
|
183
|
+
end
|
184
|
+
|
185
|
+
def repeatable?
|
186
|
+
false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class SuperToObjectExtension < AST_Rewriter
|
191
|
+
attr_reader :class_node
|
192
|
+
def initialize(constructor, class_node)
|
193
|
+
super(constructor)
|
194
|
+
@class_node = class_node
|
195
|
+
end
|
196
|
+
|
197
|
+
def match?(constructor)
|
198
|
+
DefNode === constructor && constructor.super_node
|
199
|
+
end
|
200
|
+
|
201
|
+
def replace(constructor)
|
202
|
+
superclass = classes.superclass(class_node.name)
|
203
|
+
super_constructor = superclass.constructor
|
204
|
+
|
205
|
+
set_var_node = AssignNode.new('=', GetVariableNode.new(nil, superclass.constructor_obj_name),
|
206
|
+
CallNode.new(
|
207
|
+
super_constructor.scope_modifier,
|
208
|
+
super_constructor.name,
|
209
|
+
super_arguments(constructor.super_node)
|
210
|
+
)
|
211
|
+
)
|
212
|
+
|
213
|
+
constructor.super_node.replace_with(set_var_node)
|
214
|
+
constructor.expressions.insert_after(set_var_node,
|
215
|
+
ExplicitCallNode.new(
|
216
|
+
nil,
|
217
|
+
"extend",
|
218
|
+
[
|
219
|
+
GetVariableNode.new(nil, class_node.constructor_obj_name),
|
220
|
+
GetVariableNode.new(nil, superclass.constructor_obj_name)
|
221
|
+
]
|
222
|
+
)
|
223
|
+
)
|
224
|
+
reestablish_parents(constructor)
|
225
|
+
end
|
226
|
+
|
227
|
+
def super_arguments(super_node)
|
228
|
+
if super_node.use_all_arguments?
|
229
|
+
# here, ast is 'constructor'
|
230
|
+
ast.parameters.map {|p| GetVariableNode.new(nil, p)}
|
231
|
+
else
|
232
|
+
super_node.arguments
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def repeatable?
|
237
|
+
false
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end # ClassDefinitionToFunctions
|
241
|
+
|
242
|
+
class ObjectInstantiationToCall < AST_Rewriter
|
243
|
+
def match?(node)
|
244
|
+
ObjectInstantiationNode === node
|
245
|
+
end
|
246
|
+
|
247
|
+
def replace(node)
|
248
|
+
constructor_name = node.call_node.name
|
249
|
+
class_node = classes[constructor_name]
|
250
|
+
call_node = node.call_node
|
251
|
+
call_node.name = class_node.constructor_name
|
252
|
+
call_node.scope_modifier = class_node.constructor.scope_modifier
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class CallToExplicitCall < AST_Rewriter
|
257
|
+
def match?(node)
|
258
|
+
node.instance_of?(CallNode) && node.must_be_explicit_call?
|
259
|
+
end
|
260
|
+
|
261
|
+
def replace(node)
|
262
|
+
node.replace_with(ExplicitCallNode.new(node[0], node[1], node[2]))
|
263
|
+
reestablish_parents(node)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|
data/lib/class_map.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Riml
|
2
|
+
class ClassNotFound < NameError; end
|
3
|
+
|
4
|
+
class ClassMap
|
5
|
+
def initialize
|
6
|
+
@map = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
ensure_key_is_string!(key)
|
11
|
+
@map[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key, val)
|
15
|
+
ensure_key_is_string!(key)
|
16
|
+
@map[key] = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def superclass(key)
|
20
|
+
ensure_key_is_string!(key)
|
21
|
+
super_key = @map[key].superclass_name
|
22
|
+
raise ClassNotFound.new(super_key) unless @map[super_key]
|
23
|
+
@map[super_key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def classes
|
27
|
+
@map.values
|
28
|
+
end
|
29
|
+
|
30
|
+
def class_names
|
31
|
+
@map.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
@map.clear
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def ensure_key_is_string!(key)
|
40
|
+
unless key.is_a?(String)
|
41
|
+
raise ArgumentError, "key must be name of class (String)"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|