endlessruby 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +1 -0
- data/Manifest.txt +3 -2
- data/README.rdoc +28 -5
- data/bin/endlessruby +1 -1
- data/lib/endlessruby.rb +221 -159
- data/lib/endlessruby/{extensions.rb → custom_require.rb} +28 -41
- data/lib/endlessruby/main.rb +9 -15
- data/spec/build_self_spec.rb +2 -2
- data/spec/endlessruby_spec.rb +2 -0
- data/spec/require_spec.rb +8 -0
- data/spec/semicolon_spec.rb +11 -2
- data/spec/simply_spec.rb +16 -0
- data/spec/use_end_case_spec.rb +27 -0
- data/src/endlessruby.er +213 -147
- data/src/endlessruby/{extensions.er → custom_require.er} +27 -37
- data/src/endlessruby/main.er +9 -11
- metadata +12 -45
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -4,10 +4,10 @@ README.rdoc
|
|
4
4
|
Rakefile
|
5
5
|
bin/endlessruby
|
6
6
|
lib/endlessruby.rb
|
7
|
-
lib/endlessruby/
|
7
|
+
lib/endlessruby/custom_require.rb
|
8
8
|
lib/endlessruby/main.rb
|
9
9
|
src/endlessruby.er
|
10
|
-
src/endlessruby/
|
10
|
+
src/endlessruby/custom_require.er
|
11
11
|
src/endlessruby/main.er
|
12
12
|
src/er.er
|
13
13
|
spec/blank_line_spec.rb
|
@@ -23,6 +23,7 @@ spec/semicolon_spec.rb
|
|
23
23
|
spec/simply_spec.rb
|
24
24
|
spec/spec.opts
|
25
25
|
spec/spec_helper.rb
|
26
|
+
spec/use_end_case_spec.rb
|
26
27
|
spec/test_data/er_omission.er
|
27
28
|
spec/test_data/file.er
|
28
29
|
spec/test_data/rb_omission.rb
|
data/README.rdoc
CHANGED
@@ -2,9 +2,15 @@
|
|
2
2
|
|
3
3
|
* http://github.com/pasberth/EndlessRuby
|
4
4
|
|
5
|
+
== UPDATE:
|
6
|
+
EndlessRuby 0.0.1 からのアップデート
|
7
|
+
* 正規表現ベースからirbに付属しているRubyLexベースに(かなり安定したと思います)
|
8
|
+
* def method; a; end みたいな書式に対応
|
9
|
+
* テストしてないけどRubyLexベースなのでたぶんけっこうな数の構文に対応している気がする
|
10
|
+
|
5
11
|
== DESCRIPTION:
|
6
12
|
|
7
|
-
EndlessRuby は Ruby を end
|
13
|
+
EndlessRuby は Ruby を end なしで代わりにインデントで書けるプリプロセッサまたはコンパイラです。
|
8
14
|
* EndlessRuby で書かれたソースコードを Ruby プログラムから require
|
9
15
|
* EndlessRuby で書かれたソースコードを ピュア Ruby にコンパイル
|
10
16
|
ができます。
|
@@ -12,6 +18,9 @@ EndlessRuby は Ruby を end なしで書けるプリプロセッサまたはコ
|
|
12
18
|
基本的にRubyの構文からendを取り除いただけで書けます。endを取り除かなくても実行可能です。
|
13
19
|
EndlessRubyの独自な拡張的な構文はありません。
|
14
20
|
ただ行単位で処理しているので def method; a; end みたいな書式できません。
|
21
|
+
# 0.1.0 からできるようになりました
|
22
|
+
|
23
|
+
endを取り除かなくても実行可能なので
|
15
24
|
|
16
25
|
コンパイルする場合は
|
17
26
|
$ endlessruby -c src/example.er src/more.er -o lib
|
@@ -21,6 +30,10 @@ src/example.er => lib/example.rb
|
|
21
30
|
src/more.er => lib/more.rb
|
22
31
|
として書き出されます。
|
23
32
|
|
33
|
+
デコンパイル
|
34
|
+
$ endlessruby -d lib/example.rb lib/more.rb -o src
|
35
|
+
すべてのendを取り除きます
|
36
|
+
|
24
37
|
実行する場合は
|
25
38
|
$ endlessruby src/example.er
|
26
39
|
|
@@ -29,13 +42,23 @@ $ endlessruby src/example.er
|
|
29
42
|
require 'endlessruby'
|
30
43
|
require 'example.er'
|
31
44
|
|
32
|
-
|
45
|
+
= BE CAREFUL
|
33
46
|
|
34
|
-
|
47
|
+
インデントを合わせる箇所に注意してください。インデントは行頭ではなくてキーワードに合わせてください。
|
35
48
|
|
36
|
-
|
49
|
+
たとえばcaseの場合は
|
50
|
+
|
51
|
+
YES:
|
52
|
+
res = case x
|
53
|
+
when A then a
|
54
|
+
when B then b
|
55
|
+
|
56
|
+
NO:
|
57
|
+
res = case x
|
58
|
+
when A then a
|
59
|
+
when B then b
|
37
60
|
|
38
|
-
|
61
|
+
複数行のブロックを渡す場合は {} ではなくて do end を使ってください。 {} の場合は閉じカッコを省略できません。
|
39
62
|
|
40
63
|
= REQUIREMENTs
|
41
64
|
|
data/bin/endlessruby
CHANGED
data/lib/endlessruby.rb
CHANGED
@@ -4,7 +4,9 @@
|
|
4
4
|
$:.unshift(File.dirname(__FILE__)) unless
|
5
5
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
6
6
|
|
7
|
-
require 'endlessruby/
|
7
|
+
require 'endlessruby/custom_require'
|
8
|
+
require "tempfile"
|
9
|
+
require "irb"
|
8
10
|
|
9
11
|
# EndlessRubyはRubyをendを取り除いて書けます。
|
10
12
|
#
|
@@ -34,207 +36,267 @@ require 'endlessruby/extensions'
|
|
34
36
|
module EndlessRuby
|
35
37
|
|
36
38
|
# EndlessRuby のバージョンです
|
37
|
-
VERSION = "0.0
|
39
|
+
VERSION = "0.1.0"
|
38
40
|
|
39
41
|
extend self
|
40
42
|
|
41
|
-
private
|
42
|
-
|
43
|
-
# 内部で使用します。空業かどうか判定します。
|
44
|
-
def blank_line? line
|
45
|
-
return true unless line
|
46
|
-
(line.chomp.gsub /\s+?/, '') == ""
|
47
|
-
end
|
48
|
-
|
49
|
-
# 内部で使用します。インデントを取り除きます
|
50
|
-
def unindent line
|
51
|
-
line =~ /^\s*?(\S.*?)$/
|
52
|
-
$1
|
53
|
-
end
|
54
|
-
# 内部で使用します。インデントします
|
55
|
-
def indent line, level, indent=" "
|
56
|
-
"#{indent * level}#{line}"
|
57
|
-
end
|
58
|
-
|
59
|
-
# 内部で使用します。インデントの数を数えます。
|
60
|
-
def indent_count line, indent=" "
|
61
|
-
return 0 unless line
|
62
|
-
if line =~ /^#{indent}(.*?)$/
|
63
|
-
1 + (indent_count $1, indent)
|
64
|
-
else
|
65
|
-
0
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# 内部で使用します。ブロックを作るキーワード群です。
|
70
|
-
BLOCK_KEYWORDS = [
|
71
|
-
[/^if(:?\s|\().*?$/, /^elsif(:?\s|\().*?$/, /^else(?:$|\s+)/],
|
72
|
-
[/^unless(:?\s|\().*?$/, /^elsif(:?\s|\().*?$/, /^else(?:$|\s+)/],
|
73
|
-
[/^while(:?\s|\().*?$/],
|
74
|
-
[/^until(:?\s|\().*?$/],
|
75
|
-
[/^case(:?\s|\().*?$/, /^when(:?\s|\().*?$/, /^else(?:$|\s+)/],
|
76
|
-
[/^def\s.*?$/, /^rescue(:?$|\s|\().*?$/, /^else(?:$|\s+)/, /^ensure(?:$|\s+)/],
|
77
|
-
[/^class\s.*?$/],
|
78
|
-
[/^module\s.*?$/],
|
79
|
-
[/^begin(?:$|\s+)/, /^rescue(:?$|\s|\().*?$/, /^else(?:$|\s+)/, /^ensure(?:$|\s+)/],
|
80
|
-
[/^.*?\s+do(:?$|\s|\|)/]
|
81
|
-
]
|
82
|
-
|
83
43
|
public
|
84
44
|
|
85
45
|
# 文字列をEndlessRubyの構文として実行します。引数の意味はKernel#evalと同じです
|
86
46
|
def ereval(src, binding=TOPLEVEL_BINDING, filename=__FILE__, lineno=1)
|
87
|
-
at = caller
|
88
47
|
eval(ER2PR(src), binding, filename, lineno)
|
89
|
-
rescue Exception => e
|
90
|
-
$@ = at
|
91
|
-
raise e
|
92
48
|
end
|
93
49
|
|
94
|
-
# Ruby
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
50
|
+
# EndlessRubyの構文をピュアなRubyの構文に変換します。<br />
|
51
|
+
# options: オプションを表すHashまたはto_hashを実装したオブジェクト、構文を読み出すIOまたはto_ioを実装したオブジェクト、EndlessRubyの構文を表すStringまたはto_strを実装したオブジェクト、またはファイルのパス<br />
|
52
|
+
# optionsが文字列ならばそれをピュアなRubyの構文にします。それがIOならばIOから読み出してそれをピュアなRubyの構文にします。<br />
|
53
|
+
# ファイルのパスならばそのファイルかあ読み込みます
|
54
|
+
# それがHashならばそれはオプションです。それぞれHashを指定します。<br />
|
55
|
+
# options: {
|
56
|
+
# in: {
|
57
|
+
# io: optionsにIOを指定した場合と同じです
|
58
|
+
# any: それが存在するファイルのパスを表す文字列ならばそのファイルから読み出します。この場合のanyはoptionsにそのような文字列を指定するのと同じ意味です。
|
59
|
+
# そうでない文字列ならばそれ自体をEndlessRubyの構文として読み出します。この場合のanyはoptionsに文字列を指定するのと同じ意味です。
|
60
|
+
# それがIOならばそのIOから読み出します。この場合のany はin.ioを直接指定するのと同じです。
|
61
|
+
# }
|
62
|
+
# out: {
|
63
|
+
# io: このIOに結果を書き出します。
|
64
|
+
# any: ファイルのパスかIOです。ファイルのパスならばそのファイルに書き出します。IOならばそのIOに書き出します。
|
65
|
+
# }
|
66
|
+
# decompile: trueならばコンパイルではなくてでコンパイルします。
|
67
|
+
# }
|
68
|
+
#
|
69
|
+
# opts[:out][:io] には書き出すioを指定します。<br />
|
70
|
+
# from a file on disk:<br />
|
71
|
+
# EndlessRuby.ER2RB("filename.er")
|
72
|
+
# <br />
|
73
|
+
# from string that is source code:<br />
|
74
|
+
# EndlessRuby.ER2RB(<<DEFINE)
|
75
|
+
# # endlessruby syntax
|
76
|
+
# DEFINE
|
77
|
+
# <br />
|
78
|
+
# from IO:<br />
|
79
|
+
# file = open 'filename.er'
|
80
|
+
# EndlessRuby.ER2RB(file)
|
81
|
+
#
|
82
|
+
# appoint input file and output file:
|
83
|
+
# ER2PR({ :in => { :any => 'filename.er' }, :out => { :any => 'filename.rb' } })
|
84
|
+
# ER2PR({ :in => { :io => in_io }, :out => { :io => out_io } })
|
85
|
+
def endless_ruby_to_pure_ruby options
|
86
|
+
converting_helper options
|
100
87
|
end
|
101
88
|
|
89
|
+
alias to_pure_ruby endless_ruby_to_pure_ruby
|
90
|
+
alias ER2PR endless_ruby_to_pure_ruby
|
91
|
+
alias ER2RB endless_ruby_to_pure_ruby
|
92
|
+
|
93
|
+
# Rubyの構文をEndlessRubyの構文に変換します。optionsはendlessruby_to_pure_rubyと同じです。
|
94
|
+
def pure_ruby_to_endless_ruby options
|
95
|
+
options = merge_options options
|
96
|
+
options[:decompile] = true
|
97
|
+
converting_helper options
|
98
|
+
end
|
102
99
|
|
103
100
|
alias RB2ER pure_ruby_to_endless_ruby
|
104
101
|
alias PR2ER pure_ruby_to_endless_ruby
|
105
102
|
|
106
|
-
|
107
|
-
|
108
|
-
|
103
|
+
private
|
104
|
+
|
105
|
+
def merge_options options
|
109
106
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
pure << (currently_line = endless[i])
|
107
|
+
opts = {
|
108
|
+
:in => {}, :out => {}
|
109
|
+
}
|
114
110
|
|
115
|
-
|
116
|
-
|
111
|
+
if options.respond_to? :to_hash
|
112
|
+
opts = opts.merge options.to_hash
|
113
|
+
elsif options.respond_to?(:to_io) || options.respond_to?(:to_str)
|
114
|
+
opts[:in][:any] = options
|
115
|
+
else
|
116
|
+
raise ArgumentError, "options is IO, String, or Hash"
|
117
|
+
end
|
118
|
+
|
119
|
+
if opts[:in][:any]
|
120
|
+
if opts[:in][:any].respond_to?(:to_str) && File.exist?(opts[:in][:any].to_str) # from a file on the disk.
|
121
|
+
opts[:in] = {
|
122
|
+
:io => (in_io = open opts[:in][:any].to_str),
|
123
|
+
:ensure => proc { in_io.close }
|
124
|
+
}
|
125
|
+
elsif opts[:in][:any].respond_to? :to_io # from IO that can read source code.
|
126
|
+
opts[:in] = {
|
127
|
+
:io => options.to_io,
|
128
|
+
:ensure => proc {}
|
129
|
+
}
|
130
|
+
elsif opts[:in][:any].respond_to? :to_str # from String that is source code.
|
131
|
+
in_io = Tempfile.new "endlessruby from temp file"
|
132
|
+
in_io.write options.to_str
|
133
|
+
in_io.seek 0
|
134
|
+
opts[:in] = {
|
135
|
+
:io => in_io,
|
136
|
+
:ensure => proc { in_io.close }
|
137
|
+
}
|
138
|
+
else
|
139
|
+
raise ArgumentError, "options[:in][:any] is IO, String which is path, or String which is source code"
|
117
140
|
end
|
141
|
+
end
|
118
142
|
|
119
|
-
|
120
|
-
|
121
|
-
|
143
|
+
opts[:in][:any] = nil
|
144
|
+
|
145
|
+
if opts[:out][:any]
|
146
|
+
if opts[:out][:any].respond_to?(:to_str) # to a file on the disk.
|
147
|
+
opts[:out] = {
|
148
|
+
:io => (out_io = open opts[:out][:any].to_str, "w+"),
|
149
|
+
:ensure => proc { out_io.close }
|
150
|
+
}
|
151
|
+
elsif opts[:out][:any].respond_to? :to_io # to IO that can read source code.
|
152
|
+
opts[:out] = {
|
153
|
+
:io => options.to_io,
|
154
|
+
:ensure => proc {}
|
155
|
+
}
|
156
|
+
else
|
157
|
+
raise ArgumentError, "options[:out][:any] is IO, or String which is Path"
|
122
158
|
end
|
159
|
+
elsif !opts[:out][:io]
|
160
|
+
opts[:out] = { :io => (out_io = Tempfile.new("endlessruby pure temp file")), :ensure => proc { out_io.close } }
|
161
|
+
end
|
123
162
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# ブロックに入る
|
128
|
-
keyword = BLOCK_KEYWORDS.each { |k| break k if k[0] =~ unindent(currently_line) }
|
129
|
-
|
130
|
-
currently_indent_depth = indent_count currently_line
|
131
|
-
base_indent_depth = currently_indent_depth
|
132
|
-
|
133
|
-
inner_statements = []
|
134
|
-
# def method1
|
135
|
-
# statemetns
|
136
|
-
# # document of method2
|
137
|
-
# def method2
|
138
|
-
# statements
|
139
|
-
# のような場合にコメントの部分はmethod1内に含まないようにする。
|
140
|
-
# def method1
|
141
|
-
# statemetns
|
142
|
-
# # comment
|
143
|
-
# return
|
144
|
-
# のような場合と区別するため。
|
145
|
-
comment_count = 0
|
146
|
-
in_here_document = nil
|
147
|
-
while i < endless.length
|
148
|
-
|
149
|
-
inner_currently_line = endless[i + 1]
|
150
|
-
|
151
|
-
if inner_currently_line =~ /(.*)(?:^|(?:(?!\\).))\#(?!\{).*$/
|
152
|
-
if blank_line?($1) && currently_indent_depth >= indent_count(inner_currently_line)
|
153
|
-
comment_count += 1
|
154
|
-
end
|
155
|
-
inner_currently_line = $1
|
156
|
-
elsif blank_line? inner_currently_line
|
157
|
-
comment_count += 1
|
158
|
-
end
|
163
|
+
opts[:out][:any] = nil
|
164
|
+
opts
|
165
|
+
end
|
159
166
|
|
160
|
-
|
161
|
-
|
162
|
-
i += 1
|
163
|
-
next
|
164
|
-
end
|
167
|
+
def converting_helper options
|
168
|
+
opts = merge_options options
|
165
169
|
|
166
|
-
|
170
|
+
io = opts[:in][:io]
|
167
171
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
172
|
+
r = RubyLex.new
|
173
|
+
r.set_input io
|
174
|
+
r.skip_space = false
|
172
175
|
|
173
|
-
|
174
|
-
comment_count = 0
|
175
|
-
end
|
176
|
+
pure = opts[:out][:io]
|
176
177
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
i += 1
|
183
|
-
next
|
184
|
-
else
|
185
|
-
inner_statements << endless[i + 1]
|
186
|
-
i += 1
|
187
|
-
next
|
188
|
-
end
|
189
|
-
end
|
178
|
+
indent = []
|
179
|
+
pass = []
|
180
|
+
bol = true
|
181
|
+
last = [0, 0]
|
182
|
+
bol_indent = 0
|
190
183
|
|
191
|
-
|
192
|
-
in_here_document = [$1, $2]
|
193
|
-
end
|
184
|
+
while t = r.token
|
194
185
|
|
195
|
-
|
196
|
-
break
|
197
|
-
end
|
186
|
+
if bol && !(RubyToken::TkSPACE === t) && !(RubyToken::TkNL === t)
|
198
187
|
|
199
|
-
|
200
|
-
|
188
|
+
bol_indent = this_indent = t.char_no
|
189
|
+
while (indent.last && pass.last) && ((this_indent < indent.last) || (this_indent <= indent.last && !pass.last.include?(t.class)))
|
190
|
+
if RubyToken::TkEND === t && this_indent == indent.last
|
201
191
|
break
|
202
192
|
end
|
203
|
-
end
|
204
193
|
|
205
|
-
|
206
|
-
|
194
|
+
_indent = indent.pop
|
195
|
+
pass.pop
|
196
|
+
idt = []
|
197
|
+
loop do
|
198
|
+
pure.seek pure.pos - 1
|
199
|
+
if RUBY_VERSION < "1.9" # 1.8
|
200
|
+
c = pure.read(1)
|
201
|
+
else
|
202
|
+
c = pure.getc
|
203
|
+
end
|
204
|
+
break if pure.pos == 0 || !(["\n", ' '].include?(c))
|
205
|
+
pure.seek pure.pos - 1
|
206
|
+
idt.unshift c
|
207
|
+
end
|
208
|
+
if idt.first == "\n"
|
209
|
+
pure.write idt.shift
|
210
|
+
end
|
211
|
+
pure.write "#{' '*_indent}end\n"
|
212
|
+
pure.write idt.join
|
213
|
+
end
|
214
|
+
bol = false
|
207
215
|
end
|
208
216
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
217
|
+
case t
|
218
|
+
when RubyToken::TkNL
|
219
|
+
bol = true
|
220
|
+
when RubyToken::TkEND
|
221
|
+
indent.pop
|
222
|
+
pass.pop
|
223
|
+
if opts[:decompile]
|
224
|
+
last[0] += 3
|
225
|
+
last[1] += 3
|
226
|
+
next
|
213
227
|
end
|
214
|
-
|
228
|
+
when RubyToken::TkIF, RubyToken::TkUNLESS
|
229
|
+
pass << [RubyToken::TkELSE, RubyToken::TkELSIF]
|
230
|
+
indent << t.char_no
|
231
|
+
when RubyToken::TkFOR
|
232
|
+
pass << []
|
233
|
+
indent << t.char_no
|
234
|
+
when RubyToken::TkWHILE, RubyToken::TkUNTIL
|
235
|
+
pass << []
|
236
|
+
indent << t.char_no
|
237
|
+
when RubyToken::TkBEGIN
|
238
|
+
pass << [RubyToken::TkRESCUE, RubyToken::TkELSE, RubyToken::TkENSURE]
|
239
|
+
indent << t.char_no
|
240
|
+
when RubyToken::TkDEF
|
241
|
+
pass << [RubyToken::TkRESCUE, RubyToken::TkELSE, RubyToken::TkENSURE]
|
242
|
+
indent << t.char_no
|
243
|
+
when RubyToken::TkCLASS, RubyToken::TkMODULE
|
244
|
+
pass << []
|
245
|
+
indent << t.char_no
|
246
|
+
when RubyToken::TkCASE
|
247
|
+
pass << [RubyToken::TkWHEN, RubyToken::TkELSE]
|
248
|
+
indent << t.char_no
|
249
|
+
when RubyToken::TkDO
|
250
|
+
pass << []
|
251
|
+
indent << bol_indent
|
252
|
+
when RubyToken::TkSPACE
|
215
253
|
end
|
216
254
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
255
|
+
|
256
|
+
pos = io.pos
|
257
|
+
io.seek pos
|
258
|
+
|
259
|
+
pos = io.pos
|
260
|
+
|
261
|
+
if RUBY_VERSION < "1.9" # 1.8
|
262
|
+
io.seek t.seek
|
263
|
+
pure.write io.read(r.seek - t.seek)
|
264
|
+
else # 1.9
|
265
|
+
io.seek last[0]
|
266
|
+
(r.seek - last[1]).times do
|
267
|
+
pure.write io.getc
|
227
268
|
end
|
269
|
+
last = [io.pos, r.seek]
|
228
270
|
end
|
229
271
|
|
230
|
-
|
272
|
+
io.seek pos
|
231
273
|
end
|
232
|
-
pure.join "\n"
|
233
|
-
end
|
234
274
|
|
235
|
-
|
236
|
-
|
237
|
-
|
275
|
+
until opts[:decompile] || (indent.empty? && pass.empty?)
|
276
|
+
_indent = indent.pop
|
277
|
+
pass.pop
|
278
|
+
pure.seek pure.pos - 1
|
279
|
+
if RUBY_VERSION < "1.9" # 1.8
|
280
|
+
c = pure.read 1
|
281
|
+
else
|
282
|
+
c = pure.getc
|
283
|
+
end
|
284
|
+
if c == "\n"
|
285
|
+
pure.write "#{' '*_indent}end"
|
286
|
+
else
|
287
|
+
pure.write "\n#{' '*_indent}end"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
pure.seek 0
|
292
|
+
ret = pure.read.chomp
|
293
|
+
pure.seek 0
|
294
|
+
|
295
|
+
opts[:out][:ensure] && opts[:out][:ensure].call
|
296
|
+
opts[:in][:ensure] && opts[:in][:ensure].call
|
297
|
+
|
298
|
+
ret
|
299
|
+
end
|
238
300
|
end
|
239
301
|
|
240
302
|
if __FILE__ == $PROGRAM_NAME
|