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 CHANGED
@@ -2,3 +2,4 @@
2
2
 
3
3
  * Alpha:
4
4
  * initial release
5
+ * 0.1.0 release
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/extensions.rb
7
+ lib/endlessruby/custom_require.rb
8
8
  lib/endlessruby/main.rb
9
9
  src/endlessruby.er
10
- src/endlessruby/extensions.er
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
- == FEATURES/PROBLEMS:
47
+ インデントを合わせる箇所に注意してください。インデントは行頭ではなくてキーワードに合わせてください。
35
48
 
36
- * def method; a; end みたいな書式に対応
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
- == SYNOPSIS:
61
+ 複数行のブロックを渡す場合は {} ではなくて do end を使ってください。 {} の場合は閉じカッコを省略できません。
39
62
 
40
63
  = REQUIREMENTs
41
64
 
data/bin/endlessruby CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require "endlessruby"
4
4
  require "endlessruby/main"
5
- EndlessRuby::Main.main ARGV
5
+ EndlessRuby::Main.main ARGV
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/extensions'
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.1"
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の構文をEndlessRubyの構文に変換します。
95
- def pure_ruby_to_endless_ruby src
96
- @decompile = true
97
- s = ER2RB(src)
98
- @decompile = nil
99
- s
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
- # EndlessRubyの構文をピュアなRubyの構文に変換します。
107
- def endless_ruby_to_pure_ruby src
108
- endless = src.split "\n"
103
+ private
104
+
105
+ def merge_options options
109
106
 
110
- pure = []
111
- i = 0
112
- while i < endless.length
113
- pure << (currently_line = endless[i])
107
+ opts = {
108
+ :in => {}, :out => {}
109
+ }
114
110
 
115
- if currently_line =~ /(.*)(?:^|(?:(?!\\).))\#(?!\{).*$/
116
- currently_line = $1
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
- if blank_line? currently_line
120
- i += 1
121
- next
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
- next i += 1 unless BLOCK_KEYWORDS.any? { |k| k[0] =~ unindent(currently_line) }
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
- if blank_line? inner_currently_line
161
- inner_statements << endless[i + 1]
162
- i += 1
163
- next
164
- end
167
+ def converting_helper options
168
+ opts = merge_options options
165
169
 
166
- just_after_indent_depth = indent_count inner_currently_line
170
+ io = opts[:in][:io]
167
171
 
168
- # 次の行がendならば意図のあるものなのでendを持ちあ揚げない
169
- if inner_currently_line =~ /^\s*end(?!\w).*$/
170
- comment_count = 0
171
- end
172
+ r = RubyLex.new
173
+ r.set_input io
174
+ r.skip_space = false
172
175
 
173
- if base_indent_depth < just_after_indent_depth
174
- comment_count = 0
175
- end
176
+ pure = opts[:out][:io]
176
177
 
177
- if in_here_document
178
- if (in_here_document[0] == '' && inner_currently_line =~ /^#{in_here_document[1]}\s*$/) || # <<DEFINE case
179
- (in_here_document[0] == '-' && inner_currently_line =~ /^\s*#{in_here_document[1]}\s*$/) # <<-DEFINE case
180
- in_here_document = nil
181
- inner_statements << endless[i + 1]
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
- if inner_currently_line =~ /^.*?\<\<(\-?)(\w+)(?!\w).*$/
192
- in_here_document = [$1, $2]
193
- end
184
+ while t = r.token
194
185
 
195
- if base_indent_depth > indent_count(inner_currently_line)
196
- break
197
- end
186
+ if bol && !(RubyToken::TkSPACE === t) && !(RubyToken::TkNL === t)
198
187
 
199
- if base_indent_depth == indent_count(inner_currently_line)
200
- unless keyword[1..-1].any? { |k| k =~ unindent(inner_currently_line) }
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
- inner_statements << endless[i + 1]
206
- i += 1
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
- # endをコメントより上の行へ持ち上げる
210
- if 0 < comment_count
211
- comment_count.times do
212
- inner_statements.pop
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
- i -= comment_count
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
- pure += ER2PR(inner_statements.join("\n")).split "\n"
218
- # 次の行がendならばendを補完しない(ワンライナーのため)
219
- unless @decompile
220
- unless endless[i + 1] && endless[i + 1] =~ /^\s*end(?!\w).*$/
221
- pure << "#{' '*currently_indent_depth}end"
222
- end
223
- else
224
- # メソッドチェインは削除しない
225
- if endless[i + 1] && endless[i + 1] =~ /^\s*end(?:\s|$)\s*$/
226
- i += 1
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
- i += 1
272
+ io.seek pos
231
273
  end
232
- pure.join "\n"
233
- end
234
274
 
235
- alias to_pure_ruby endless_ruby_to_pure_ruby
236
- alias ER2PR endless_ruby_to_pure_ruby
237
- alias ER2RB endless_ruby_to_pure_ruby
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