windstorm 0.1.0

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/.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