riml 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.
- 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
|
+
[](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
|