windstorm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in hoge.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 parrot-studio
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
12
+ included 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
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ BF風言語解析器 - Windstorm
2
+ ===============
3
+
4
+ Introduction
5
+ ---------------
6
+ BrainF**k(BF)風のソースコード解析と実行をおこなうRubyのGemです。
7
+ 定義を作成することで、自由にBF風の言語を構築することができます。
8
+ いわゆる「ネタ言語用解析器」です。
9
+
10
+ Ruby Version
11
+ ---------------
12
+ - Ruby1.9.x以上
13
+ - CRuby1.9.2/1.9.3でspecが通ることを確認しています
14
+ - Ruby1.9.xに対応した処理系であれば動くと思われます(JRuby等)
15
+ - Ruby1.8.xに対応する予定はありません(動くかもしれませんが、一切保証しません)
16
+
17
+ Source
18
+ ---------------
19
+ https://github.com/parrot-studio/windstorm
20
+
21
+ Installation
22
+ ---------------
23
+ gem install windstorm
24
+
25
+ Usage
26
+ ---------------
27
+ ### 概要
28
+ 基本的な実行原理はBFと同じです。
29
+ 外部から自由に定義を与えることで、自由な解析器を作ることができます。
30
+
31
+ WindstormはParser/Machine/Executorの3クラスで構成されています。
32
+
33
+ - Parser : 与えられた定義を元に、ソースコードを命令の配列に変換する
34
+ - Machine : 与えられた命令の配列を元に、命令を実行して結果を返す
35
+ - Executor : ParserとMachineを連携させて一つの処理系を構成する
36
+
37
+ ### 命令
38
+ WindstormではBFに存在する8命令を抽象化し、シンボルで定義しています。
39
+ (括弧内は対応するBFの命令語)
40
+
41
+ - :pinc(>) : ポインタをインクリメントする
42
+ - :pdec(<) : ポインタをデクリメントする
43
+ - :inc (+) : ポインタが指す値をインクリメントする
44
+ - :dec (-) : ポインタが指す値をデクリメントする
45
+ - :out (.) : ポインタが指す値を出力する
46
+ - :inc (,) : 入力から読み込んで、ポインタが指す値を更新する
47
+ - :jmp ([) : ポインタが指す値が0ならば、対応する :ret にジャンプする
48
+ - :ret (]) : ポインタが指す値が0でなければ、対応する :jmp にジャンプする
49
+
50
+ また、以下の2命令を追加しています。
51
+
52
+ - :clip : ポインタが指す値を専用のclipバッファに保存する
53
+ - 再度呼ばれた場合は、新しい値でclipバッファを上書きする
54
+ - :paste : ポインタが指す値をclipバッファの値で上書きする
55
+ - 一度も :clip を呼ばれていない場合、0で上書きする
56
+
57
+ ### 定義
58
+
59
+ 10の命令について、対応する文字列をHash形式で指定します
60
+
61
+ table = {
62
+ :pinc => '>',
63
+ :pdec => '<',
64
+ :inc => ['+', 'a'], # 複数指定も可能
65
+ :dec => ['-', 'あ'], # マルチバイトでもOK
66
+ :out => ['.', '出力'] # 一文字でなくてもOK
67
+ # 不要な命令は定義しなくても良い
68
+ }
69
+ exec = Executor.create_from_table(table)
70
+
71
+ Executorは外部ファイルからの読み込みに対応しています。
72
+ その場合は定義をYAML形式(UTF-8)で記述します。
73
+
74
+ # table.yml
75
+ :pinc:
76
+ - '>'
77
+ :pdec:
78
+ - '<'
79
+ :inc:
80
+ - '+'
81
+ - 'a'
82
+ :dec:
83
+ - '-'
84
+ - あ
85
+ :out:
86
+ - '.'
87
+ - 出力
88
+ ########
89
+
90
+ exec = Executor.create_from_file('table.yml')
91
+
92
+ ### ソースコード仕様
93
+
94
+ - 文字コードはUTF-8で記述してください
95
+
96
+ - 定義されていない文字列は全て無視します
97
+ - 定義に重複する文字列があった場合、どちらの命令に変換されるかは不定です
98
+ - 解析には正規表現を用いていますので、それに依存した動作になります
99
+
100
+ - '#'か'//'で始まる行はコメントとして扱われます
101
+ - 空白を含め、一文字でも'#'等の前にあるとコメントになりません
102
+ - よって、文の途中に'#'等があっても、コメントとして解釈しません
103
+ - なので、'#'等を定義に含めても、命令として解釈させることが可能です
104
+
105
+ ### Executorによる解析と実行
106
+
107
+ # 定義をYAMLファイルから読み込み
108
+ exec = Executor.create_from_file('table.yml')
109
+
110
+ # 命令に対応する文字列だけを抽出
111
+ exec.filter('+!+? 出力します >') #=> ['+', '+', '出力', '>']
112
+
113
+ # 命令に変換した配列を取得
114
+ exec.build('+!+? 出力します >') #=> [:inc, :inc, :out, :pinc]
115
+
116
+ # 命令を実行
117
+ source = (['+'] * 33 + ['.']).join
118
+ exec.execute(source) #=> '!'
119
+
120
+ # デバッグモード(後述)で実行
121
+ exec.debug_execute(source)
122
+
123
+ # ファイルから読み込んで実行
124
+ exec.filter_from_file('hello.bf')
125
+ exec.build_from_file('hello.bf')
126
+ exec.execute_from_file('hello.bf')
127
+ exec.debug_execute_from_file('hello.bf')
128
+
129
+ Executorが中で使っているParser/Machineの詳細は、
130
+ それぞれのspecを参照してください。
131
+
132
+ ### 実行オプション
133
+
134
+ Executor#executeにはオプションを与えることができます
135
+
136
+ exec.execute(source, :debug => true, :size => 10)
137
+
138
+ - :debug, :loose, :flash : trueを与えると、各モードが有効になります
139
+ - :size : 値を格納するバッファのサイズを指定します
140
+
141
+ 格納バッファサイズのデフォルトは100となっていますが、
142
+ 実行時オプションで :size を指定することにより、
143
+ 任意のサイズに変更することが可能です。(通常は変更不要)
144
+
145
+ 格納バッファサイズはWindstorm処理系の仮想的な概念で、
146
+ 本質的にはRuby処理系のArrayクラス仕様に依存します。
147
+ 後述のlooseモードの説明も参照してください。
148
+
149
+ #### デバッグモード(:debug)
150
+
151
+ 命令を一つ実行するたびに、デバッグ文を出力します。
152
+
153
+ # 出力例
154
+ "step:197 com:out index:43 point:0 buffer:[115, 0] clip:100 result:s"
155
+
156
+ - step : 実行ステップ数
157
+ - com : 実行したコマンド
158
+ - index : comに対応する命令配列のindex
159
+ - point : ポインタの値(bufferのindexに対応)
160
+ - buffer : 値の格納バッファ(配列)
161
+ - clip : clipバッファに保存されている値
162
+ - result : :out で出力した結果
163
+
164
+ 出力先はRuby処理系の$stdoutが指すIOオブジェクトです。
165
+ 通常は実行しているコンソールになります。
166
+
167
+ #### strictモードとlooseモード(:loose)
168
+
169
+ Windstormでソースを実行する目的は、
170
+ 最終的に「何らかの文字列の出力すること」です。
171
+
172
+ そのため、デフォルトでは以下の状況になるとエラーになります。
173
+
174
+ - ポインタが指す値が負になる
175
+ - ポインタが負になる
176
+ - ポインタがsizeの範囲を超える
177
+ - clipバッファに負の値が格納される
178
+
179
+ これを「strictモード」と呼びます。
180
+
181
+ ただ、Ruby処理系としてみた場合、
182
+ Arrayに格納される値は負でもかまわないし、
183
+ ポインタ(Machine#pointが指す整数)が範囲を超えても問題ありません。
184
+
185
+ そこで、Ruby処理系として都合が悪い状況になるまでエラーにしないという指定が可能です。
186
+ これを「looseモード」と呼びます
187
+
188
+ looseモードでは以下の場合でもエラーとみなしません。
189
+
190
+ - ポインタが範囲を超えたが、値を操作する前に正常範囲に戻った場合
191
+ - ポインタが指す値が負になったが、:out が呼ばれる前に0以上に戻った場合
192
+ - clipバッファに負の値が格納された場合
193
+
194
+ その場でエラーにしないというだけで、想定した処理がおこなわれるかは状況次第です。
195
+ 特に理由がない限り、strictモードで実行してください。
196
+
197
+ #### flashモード(:flash)
198
+
199
+ Executor#executeは出力結果の文字列を返しますが、
200
+ 本来のBFでは、:out を呼んだ時点で文字を出力します。
201
+ BFと同じく、即時出力をおこなうのが「flashモード」です。
202
+
203
+ 通常の実行時にはさほど差がありませんが、
204
+ debugモードを有効にしていると、出力が混じるのでお勧めできません。
205
+
206
+ ### WindstormのI/Oについて
207
+
208
+ このドキュメントで使っている「出力先」という言葉は、
209
+ 厳密に言うとRuby処理系の_$stdout_が指すIOオブジェクトです。
210
+ 通常はRubyを実行しているコンソールになります。
211
+
212
+ 同じく、「入力元」はRuby処理系の_$stdin_が指すIOオブジェクトです。
213
+ 通常はRubyを実行しているコンソールからの入力になります。
214
+
215
+ これらの入出力を変更する必要はありませんが、
216
+ 必要であれば、StringIOなどのオブジェクトに差し替えることが可能です。
217
+ Machineに対するspecの記述も参考にしてください。
218
+
219
+ Note
220
+ ---------------
221
+ - windstorm : 暴風 = 上州名物・空っ風
222
+
223
+ - Windstormを使ってネタプログラム言語を作成するツール、「Youma」もあります
224
+ - https://github.com/parrot-studio/youma
225
+ - ネタ言語のサンプルも含まれています
226
+ - WindstormをWebアプリ等に組み込むのでなければ、Youmaを使ってください
227
+
228
+ License
229
+ ---------------
230
+ The MIT License
231
+
232
+ see LICENSE file for detail
233
+
234
+ Author
235
+ ---------------
236
+ ぱろっと(@parrot_studio / parrot.studio.dev at gmail.com)
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ end
8
+
9
+ task :default => :spec
data/lib/windstorm.rb ADDED
@@ -0,0 +1,12 @@
1
+ # coding: utf-8
2
+ module Windstorm
3
+
4
+ BUFFER_DEFAULT_SIZE = 100
5
+ COMMANDS = [:pinc, :pdec, :inc, :dec, :out, :inp, :jmp, :ret, :clip, :paste]
6
+
7
+ end
8
+
9
+ path = File.join(File.dirname(__FILE__), 'windstorm')
10
+ require File.join(path, 'parser')
11
+ require File.join(path, 'machine')
12
+ require File.join(path, 'executor')
@@ -0,0 +1,88 @@
1
+ # coding: utf-8
2
+ require 'yaml'
3
+
4
+ module Windstorm
5
+ module Reader
6
+
7
+ private
8
+
9
+ def read(file)
10
+ raise 'file not found' unless (File.exist?(file) && File.file?(file))
11
+ File.read(file)
12
+ end
13
+
14
+ def yaml_read(file)
15
+ YAML.load(read(file))
16
+ end
17
+ end
18
+
19
+ class Executor
20
+ include Reader
21
+
22
+ attr_writer :parser
23
+
24
+ class << self
25
+ include Reader
26
+
27
+ def create_from_file(file)
28
+ create_from_table(yaml_read(file))
29
+ end
30
+
31
+ def create_from_table(t)
32
+ create(Parser.create(t))
33
+ end
34
+
35
+ def create(pa)
36
+ return unless pa
37
+ ex = self.new
38
+ ex.parser = pa
39
+ ex
40
+ end
41
+
42
+ end
43
+
44
+ def parser
45
+ raise 'parser not found' unless @parser
46
+ @parser
47
+ end
48
+
49
+ def machine
50
+ raise 'not executed yet' unless @machine
51
+ @machine
52
+ end
53
+
54
+ def execute(source, params = nil)
55
+ @machine = Machine.create(parser.build(source), params)
56
+ @machine.execute
57
+ end
58
+
59
+ def execute_from_file(file, params = nil)
60
+ execute(read(file), params)
61
+ end
62
+
63
+ def filter(source)
64
+ parser.filter(source)
65
+ end
66
+
67
+ def filter_from_file(file)
68
+ filter(read(file))
69
+ end
70
+
71
+ def build(source)
72
+ parser.build(source)
73
+ end
74
+
75
+ def build_from_file(file)
76
+ build(read(file))
77
+ end
78
+
79
+ def debug_execute(source, params = nil)
80
+ execute(source, {:debug => true}.merge(params || {}))
81
+ end
82
+
83
+ def debug_execute_from_file(file, params = nil)
84
+ debug_execute(read(file), params)
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,254 @@
1
+ # coding: utf-8
2
+ module Windstorm
3
+ class Machine
4
+
5
+ attr_writer :commands, :size, :debug, :flash, :loose
6
+
7
+ class << self
8
+
9
+ def create(coms, params = nil)
10
+ params ||= {}
11
+ s = params[:size].to_i
12
+
13
+ m = self.new
14
+ m.commands = coms
15
+ m.size = s if s > 0
16
+ m.debug = true if params[:debug]
17
+ m.flash = true if params[:flash]
18
+ m.loose = true if params[:loose]
19
+ m
20
+ end
21
+
22
+ def execute(coms, params = nil)
23
+ m = create(coms, params)
24
+ m.execute
25
+ end
26
+
27
+ end
28
+
29
+ def execute
30
+ return result if finish?
31
+ parse_jump
32
+ loop do
33
+ break if finish?
34
+ step_execute
35
+ end
36
+ result
37
+ end
38
+
39
+ def step_execute
40
+ case com
41
+ when :pinc
42
+ move_point_inc
43
+ when :pdec
44
+ move_point_dec
45
+ when :inc
46
+ increment
47
+ when :dec
48
+ decrement
49
+ when :out
50
+ output
51
+ when :inp
52
+ input
53
+ when :jmp
54
+ jump if value == 0
55
+ when :ret
56
+ jump unless value == 0
57
+ when :clip
58
+ clip
59
+ when :paste
60
+ paste
61
+ end
62
+ debug_out if debug?
63
+ forward
64
+ self
65
+ end
66
+
67
+ def debug?
68
+ @debug ? true : false
69
+ end
70
+
71
+ def loose?
72
+ @loose ? true : false
73
+ end
74
+
75
+ def strict?
76
+ ! loose?
77
+ end
78
+
79
+ def step
80
+ @step ||= 0
81
+ @step
82
+ end
83
+
84
+ def add_step
85
+ @step = step + 1
86
+ step
87
+ end
88
+
89
+ def commands
90
+ @commands ||= []
91
+ @commands
92
+ end
93
+
94
+ def index
95
+ @index ||= 0
96
+ @index
97
+ end
98
+
99
+ def add_index
100
+ @index = index + 1
101
+ @index
102
+ end
103
+
104
+ def com(ind = nil)
105
+ ind ||= index
106
+ commands[ind]
107
+ end
108
+
109
+ def forward
110
+ add_step
111
+ add_index
112
+ self
113
+ end
114
+
115
+ def finish?
116
+ index >= commands.size ? true : false
117
+ end
118
+
119
+ def jump_table
120
+ @jump_table ||= {}
121
+ @jump_table
122
+ end
123
+
124
+ def parse_jump
125
+ return if commands.empty?
126
+ inds = []
127
+ commands.each.with_index do |com, i|
128
+ case com
129
+ when :jmp
130
+ inds << i
131
+ when :ret
132
+ hind = inds.delete_at(-1)
133
+ raise 'jump couples invalid' unless hind
134
+ jump_table[hind] = i
135
+ jump_table[i] = hind
136
+ end
137
+ end
138
+ raise 'jump couples invalid' unless inds.empty?
139
+ if debug? && !jump_table.empty?
140
+ puts 'jump table:'
141
+ puts jump_table
142
+ end
143
+ self
144
+ end
145
+
146
+ def jump_index(ind)
147
+ error 'jump index is nil' unless ind
148
+ strict_error 'out of command index' if ind < 0 || ind >= commands.size
149
+ @index = ind
150
+ end
151
+
152
+ def jump
153
+ jump_index(jump_table[index])
154
+ end
155
+
156
+ def size
157
+ @size ||= BUFFER_DEFAULT_SIZE
158
+ @size = BUFFER_DEFAULT_SIZE if @size <= 0
159
+ @size
160
+ end
161
+
162
+ def point
163
+ @point ||= 0
164
+ strict_error 'point over' if (@point < 0 || @point >= size)
165
+ @point
166
+ end
167
+
168
+ def move_point_inc
169
+ @point = point + 1
170
+ point
171
+ end
172
+
173
+ def move_point_dec
174
+ @point = point - 1
175
+ point
176
+ end
177
+
178
+ def increment
179
+ replace_value(value+1)
180
+ end
181
+
182
+ def decrement
183
+ replace_value(value-1)
184
+ end
185
+
186
+ def buffer
187
+ @buffer ||= []
188
+ @buffer
189
+ end
190
+
191
+ def value(po = nil)
192
+ po ||= point
193
+ strict_error 'point over' if (po < 0 || po >= size)
194
+ buffer[po] || 0
195
+ end
196
+
197
+ def replace_value(val, po = nil)
198
+ po ||= point
199
+ strict_error 'value under 0' if val < 0
200
+ strict_error 'point over' if (po < 0 || po >= size)
201
+ buffer[po] = val
202
+ end
203
+
204
+ def cache
205
+ @cache ||= ""
206
+ @cache
207
+ end
208
+ alias :result :cache
209
+
210
+ def output
211
+ putc value if @flash
212
+ cache << value.chr
213
+ end
214
+
215
+ def input
216
+ c = $stdin.getc
217
+ replace_value(c.bytes.first)
218
+ end
219
+
220
+ def clip_value
221
+ @clip_value ||= 0
222
+ @clip_value
223
+ end
224
+
225
+ def replace_clip_value(val)
226
+ strict_error 'value under 0' if val < 0
227
+ @clip_value = val
228
+ end
229
+
230
+ def clip
231
+ replace_clip_value(value)
232
+ end
233
+
234
+ def paste
235
+ replace_value(clip_value)
236
+ end
237
+
238
+ def debug_out
239
+ puts "step:#{step} com:#{com} index:#{index} point:#{point} buffer:#{buffer.inspect} clip:#{clip_value} result:#{result}"
240
+ end
241
+
242
+ private
243
+
244
+ def error(msg)
245
+ raise msg
246
+ end
247
+
248
+ def strict_error(msg)
249
+ return unless strict?
250
+ error(msg)
251
+ end
252
+
253
+ end
254
+ end