razyk 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,227 @@
1
+ class RazyK::Parser
2
+
3
+ token I K S
4
+ token SMALL_I
5
+ token BACKSLASH
6
+ token ASTAR
7
+ token LPAR RPAR
8
+ token ZERO ONE
9
+ token LITERAL STRING
10
+
11
+ start program
12
+
13
+ rule
14
+
15
+ program : ccexpr
16
+ {
17
+ result = val[0] || Combinator.new(:I)
18
+ }
19
+ ;
20
+
21
+ ccexpr : /* epsilon */
22
+ {
23
+ result = nil
24
+ }
25
+ | ccexpr expr
26
+ {
27
+ if val[0].nil?
28
+ result = val[1]
29
+ else
30
+ result = Pair.new(val[0], val[1])
31
+ end
32
+ }
33
+ ;
34
+
35
+ expr : SMALL_I
36
+ {
37
+ result = Combinator.new(:I)
38
+ }
39
+ | expr2
40
+ ;
41
+
42
+ iotaexpr: SMALL_I
43
+ {
44
+ result = Combinator.new(:IOTA)
45
+ }
46
+ | expr2
47
+ ;
48
+
49
+ expr2 : I
50
+ {
51
+ result = Combinator.new(:I)
52
+ }
53
+ | K
54
+ {
55
+ result = Combinator.new(:K)
56
+ }
57
+ | S
58
+ {
59
+ result = Combinator.new(:S)
60
+ }
61
+ | no_empty_jot_expr
62
+ {
63
+ comb = Combinator.new(:I)
64
+ @jot.reverse_each do |i|
65
+ case i
66
+ when 0
67
+ comb = Pair.new(Pair.new(comb, :S), :K)
68
+ when 1
69
+ comb = Pair.new(:S, Pair.new(:K, comb))
70
+ end
71
+ end
72
+ @jot.clear
73
+ result = comb
74
+ }
75
+ | BACKSLASH expr expr
76
+ {
77
+ result = Pair.new(val[1], val[2])
78
+ }
79
+ | ASTAR iotaexpr iotaexpr
80
+ {
81
+ result = Pair.new(val[1], val[2])
82
+ }
83
+ | LPAR ccexpr RPAR
84
+ {
85
+ result = val[1]
86
+ }
87
+ | LITERAL
88
+ {
89
+ result = Combinator.new(val[0].to_sym)
90
+ }
91
+ | STRING
92
+ {
93
+ result = str2list(val[0])
94
+ }
95
+ ;
96
+
97
+ no_empty_jot_expr : ZERO jot_expr
98
+ { @jot.push(0) }
99
+ | ONE jot_expr
100
+ { @jot.push(1) }
101
+ ;
102
+
103
+ jot_expr: no_empty_jot_expr
104
+ | /* epsilon */
105
+ ;
106
+
107
+ end
108
+
109
+ ---- header
110
+
111
+ require "razyk/node"
112
+
113
+ ---- inner
114
+
115
+ def str2list(str)
116
+ # (K 256) means End-of-stream. RazyK String adopt it as null terminator
117
+ head = Pair.new(:K, 256)
118
+ str.unpack("C*").reverse_each do |ch|
119
+ head = Pair.new(Pair.new(:CONS, ch), head)
120
+ end
121
+ head
122
+ end
123
+
124
+ def scan
125
+ # state : EXPR/IN_COMMENT/IN_LIRETAL/IN_STRING/IN_STRING_ESC
126
+ state = :EXPR
127
+ literal = []
128
+ @lineno = 1
129
+ @buf.each_char do |ch|
130
+ @lineno += 1 if ch == "\n"
131
+ case state
132
+ when :IN_COMMENT
133
+ state = :EXPR if ch == "\n"
134
+ next
135
+ when :IN_LITERAL
136
+ if /[\w.-]/ =~ ch
137
+ literal.push(ch)
138
+ next
139
+ else
140
+ raise "empty literal at line.#{@lineno}" if literal.empty?
141
+ name = literal.join
142
+ literal.clear
143
+ yield [:LITERAL, name]
144
+ state = :EXPR
145
+ # through down
146
+ end
147
+ when :IN_STRING
148
+ if "\\" == ch
149
+ state = :IN_STRING_ESC
150
+ elsif '"' == ch
151
+ yield [:STRING, literal.join]
152
+ literal.clear
153
+ state = :EXPR
154
+ else
155
+ literal.push(ch)
156
+ end
157
+ next
158
+ when :IN_STRING_ESC
159
+ case ch
160
+ when "n"
161
+ literal.push("\n")
162
+ when "t"
163
+ literal.push("\t")
164
+ when "r"
165
+ literal.push("\r")
166
+ when "b"
167
+ literal.push("\b")
168
+ when "f"
169
+ literal.push("\f")
170
+ else
171
+ literal.push(ch)
172
+ end
173
+ state = :IN_STRING
174
+ next
175
+ end
176
+
177
+ tok = case ch
178
+ when "#"
179
+ state = :IN_COMMENT
180
+ nil
181
+ when "$"
182
+ state = :IN_LITERAL
183
+ nil
184
+ when "\""
185
+ state = :IN_STRING
186
+ nil
187
+ when "I"
188
+ [:I, ch]
189
+ when "i"
190
+ [:SMALL_I, ch]
191
+ when "K", "k"
192
+ [:K, ch]
193
+ when "S", "s"
194
+ [:S, ch]
195
+ when "`"
196
+ [:BACKSLASH, ch]
197
+ when "*"
198
+ [:ASTAR, ch]
199
+ when "("
200
+ [:LPAR, ch]
201
+ when ")"
202
+ [:RPAR, ch]
203
+ when "0"
204
+ [:ZERO, ch]
205
+ when "1"
206
+ [:ONE, ch]
207
+ end
208
+ yield tok if tok
209
+ end
210
+ if state == :IN_LITERAL and not literal.empty?
211
+ name = literal.join
212
+ literal.clear
213
+ yield [:LITERAL, name]
214
+ state = :EXPR
215
+ end
216
+ yield [false, nil]
217
+ end
218
+
219
+ def parse(str, opt={})
220
+ @buf = str
221
+ @jot = []
222
+ yyparse self, :scan
223
+ end
224
+
225
+ def self.parse(str, opt={})
226
+ self.new.parse(str)
227
+ end
data/lib/razyk/vm.rb ADDED
@@ -0,0 +1,212 @@
1
+
2
+ require "enumerator"
3
+
4
+ if not(defined?(Enumerator)) and defined?(Enumerable::Enumerator)
5
+ # for 1.8
6
+ Enumerator = Enumerable::Enumerator
7
+ end
8
+
9
+ require "razyk/node"
10
+
11
+ module RazyK
12
+ class VM
13
+ class StackUnderflow < StandardError; end
14
+
15
+ def initialize(tree, input=$stdin, output=$stdout, recursive=false)
16
+ @root = Node.new(:root, [], [tree])
17
+ @generator = nil
18
+ @input = input
19
+ @output = output
20
+ @recursive = recursive
21
+ end
22
+
23
+ def tree
24
+ @root.to[0]
25
+ end
26
+
27
+ # Pop num of Pairs nodes from stack.
28
+ # Each Pair node is destroyed if it isn't referenced from other parent node
29
+ # Return value is [<root Pair node for replace>, cdr1, cdr2, ...]
30
+ def pop_pairs(stack, num)
31
+ raise StackUnderflow if stack.size < num
32
+ pairs = stack.pop(num)
33
+ root = pairs.first
34
+ root.cut_car if num <= 1
35
+ cdrs = [ root.cut_cdr ]
36
+ shared = false
37
+ pairs.inject do |parent, child|
38
+ parent.cut_car unless shared
39
+ if child.from.size != 0
40
+ shared = true
41
+ cdrs.unshift(child.cdr)
42
+ else
43
+ cdrs.unshift(child.cut_cdr)
44
+ end
45
+ child
46
+ end
47
+ cdrs.unshift(root)
48
+ cdrs
49
+ end
50
+
51
+ # replace old_root Pair with new_root Pair and push new_root to stack
52
+ def replace_root(stack, old_root, new_root)
53
+ old_root.replace(new_root)
54
+ stack.push(new_root)
55
+ end
56
+
57
+ def evaluate(root, gen=nil)
58
+ stack = [root]
59
+ until step(stack, gen).nil?
60
+ if gen
61
+ gen.yield(self)
62
+ end
63
+ end
64
+ if @recursive and stack.last.is_a?(Pair)
65
+ evaluate(stack.last.cdr, gen)
66
+ end
67
+ nil
68
+ end
69
+
70
+ def step(stack, gen=nil)
71
+ return nil if stack.empty?
72
+ while stack.last.is_a?(Pair)
73
+ stack.push(stack.last.car)
74
+ end
75
+ comb = stack.pop
76
+ case comb.label
77
+ when :I
78
+ # (I x) -> x
79
+ root, x = pop_pairs(stack, 1)
80
+ replace_root(stack, root, x)
81
+ when :K
82
+ # (K x y) -> x
83
+ root, x, y = pop_pairs(stack, 2)
84
+ replace_root(stack, root, x)
85
+ when :S
86
+ # (S x y z) -> ((x z) (y z))
87
+ root, x, y, z = pop_pairs(stack, 3)
88
+ new_root = Pair.new(Pair.new(x, z), Pair.new(y, z))
89
+ replace_root(stack, root, new_root)
90
+ when :IOTA
91
+ # (IOTA x) -> ((x S) K)
92
+ root, x = pop_pairs(stack, 1)
93
+ new_root = Pair.new(Pair.new(x, :S), :K)
94
+ replace_root(stack, root, new_root)
95
+ when Integer
96
+ # (<N> f x) -> x (N == 0)
97
+ # -> (f (<N-1> f x)) (N > 0)
98
+ root, f, x = pop_pairs(stack, 2)
99
+ num = comb.label
100
+ if num == 0
101
+ replace_root(stack, root, x)
102
+ else
103
+ # shortcut
104
+ if f.label == :INC and x.label.is_a?(Integer)
105
+ replace_root(stack, root, Combinator.new(num + x.label))
106
+ else
107
+ dec_pair = Pair.new(Combinator.new(num-1), f)
108
+ new_root = Pair.new(f, Pair.new(dec_pair, x))
109
+ replace_root(stack, root, new_root)
110
+ end
111
+ end
112
+ when :CONS
113
+ # (CONS a d f) -> (f a d)
114
+ root, a, d, f = pop_pairs(stack, 3)
115
+ new_root = Pair.new(Pair.new(f, a), d)
116
+ replace_root(stack, root, new_root)
117
+ when :IN
118
+ # (IN f) -> (CONS <CH> IN f) where <CH> is a byte from stdin
119
+ ch = @input.read(1)
120
+ if ch.nil?
121
+ ch = 256
122
+ else
123
+ ch = ch.ord
124
+ end
125
+ new_root = Pair.new(Pair.new(:CONS, Combinator.new(ch)),
126
+ :DUMMY) # reuse :IN combinator
127
+ comb.replace(new_root)
128
+ new_root.cdr = comb
129
+ stack.push(new_root)
130
+ when :CAR
131
+ # (CAR x) -> (x K) (CAR = (Lx.x TRUE), TRUE = (Lxy.x) = K)
132
+ root, x = pop_pairs(stack, 1)
133
+ new_root = Pair.new(x, :K) # K means TRUE
134
+ replace_root(stack, root, new_root)
135
+ when :CDR
136
+ # (CDR x) -> (x (K I)) (CDR = (Lx.x FALSE), FALSE = (Lxy.y) = (K I)
137
+ root, x = pop_pairs(stack, 1)
138
+ new_root = Pair.new(x, Pair.new(:K, :I)) # (K I) means FALSE
139
+ replace_root(stack, root, new_root)
140
+ when :OUT
141
+ # (OUTPUT f) -> ((PUTC ((CAR f) INC <0>) (OUTPUT (CDR f)))
142
+ root, f = pop_pairs(stack, 1)
143
+ new_root = Pair.new(
144
+ Pair.new(:PUTC,
145
+ Pair.new(Pair.new(Pair.new(:CAR, f), :INC), 0)),
146
+ Pair.new(comb, # reuse :OUT combinator
147
+ Pair.new(:CDR, f)))
148
+ replace_root(stack, root, new_root)
149
+ when :INC
150
+ # (INC n) -> n+1 : increment church number
151
+ raise StackUnderflow if stack.empty?
152
+ evaluate(stack.last.cdr, gen)
153
+ root, n = pop_pairs(stack, 1)
154
+ unless n.label.is_a?(Integer)
155
+ begin
156
+ msg = "argument of INC combinator is not a church number but #{n.inspect}"
157
+ rescue
158
+ msg = "argument of INC combinator is not a church number (too large combinator tree)"
159
+ end
160
+ raise msg
161
+ end
162
+ replace_root(stack, root, Combinator.new(n.label + 1))
163
+ when :PUTC
164
+ # (PUTC x y) -> y : evaluate x and putchar it
165
+ raise StackUnderflow if stack.size < 2
166
+ x = stack.pop
167
+ evaluate(x.cdr, gen)
168
+ unless x.cdr.label.is_a?(Integer)
169
+ begin
170
+ msg = "output is not church number but #{x.cdr.inspect}"
171
+ rescue
172
+ msg = "output is not church number (too large combinator tree)"
173
+ end
174
+ raise msg
175
+ end
176
+ num = x.cdr.label
177
+ if num >= 256
178
+ return nil
179
+ end
180
+ @output.write([num].pack("C"))
181
+ root = stack.pop
182
+ y = root.cut_cdr
183
+ replace_root(stack, root, y)
184
+ else # unknown combinator... treat as combinator without enough arguments
185
+ raise StackUnderflow
186
+ end
187
+ true
188
+ rescue StackUnderflow
189
+ return nil
190
+ end
191
+
192
+ def reduce
193
+ @generator ||= Enumerator.new{|e| evaluate(self.tree, e) }
194
+ begin
195
+ @generator.next
196
+ self
197
+ rescue StopIteration
198
+ nil
199
+ end
200
+ end
201
+
202
+ def run(&blk)
203
+ if blk
204
+ while reduce
205
+ blk.call(self)
206
+ end
207
+ else
208
+ evaluate(self.tree, nil)
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,69 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html>
3
+ <head>
4
+ <title>RazyK</title>
5
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
6
+ <script type="text/javascript">
7
+ function update() {
8
+ if ($("#program_mode").is(":checked")) {
9
+ $("#output_buffer").load("/stdout");
10
+ }
11
+ $("#expression").load("/expression");
12
+ $("#graph").attr("src", "/graph");
13
+ }
14
+ function set_program() {
15
+ $.post("/set_program",
16
+ {
17
+ program: $("#program").val(),
18
+ mode: $("#program_mode").is(":checked"),
19
+ stdin: $("#input_buffer").val()
20
+ },
21
+ function() {
22
+ update();
23
+ }, "json");
24
+ }
25
+
26
+ function step_reduction() {
27
+ $.get("/step", {},
28
+ function() {
29
+ update();
30
+ });
31
+ }
32
+
33
+ function toggle_mode() {
34
+ if ($("#program_mode").is(":checked")) {
35
+ $("#inout_pane").show("fast");
36
+ } else {
37
+ $("#inout_pane").hide("fast");
38
+ }
39
+ }
40
+ </script>
41
+ </head>
42
+ <body>
43
+ <p><div>enter program</div>
44
+ <textarea id="program" cols="100">skki</textarea>
45
+ </p>
46
+ <p>
47
+ <input type="checkbox" id="program_mode" onchange="toggle_mode();" />
48
+ <span>LazyK application mode</span>
49
+ </p>
50
+ <p>
51
+ <input type="button" value="set program" onclick="set_program();" />
52
+ <input type="button" value="step" onclick="step_reduction();" />
53
+ </p>
54
+ <span id="inout_pane" style="display: none">
55
+ <div>stdin</div>
56
+ <textarea id="input_buffer" cols="100"></textarea>
57
+ <div>stdout</div>
58
+ <div id="output_buffer"></div>
59
+ </span>
60
+ <p>
61
+ <div>combinator expression</div>
62
+ <div id="expression"> </div>
63
+ </p>
64
+ <p>graph expression</p>
65
+ <iframe id="graph" height="1000" width="100%">
66
+ using iframe.
67
+ </iframe>
68
+ </body>
69
+ </html>
@@ -0,0 +1,161 @@
1
+
2
+ require "rack"
3
+ require "tempfile"
4
+ require "thread"
5
+ require "stringio"
6
+
7
+ require "razyk/graph"
8
+
9
+ module RazyK
10
+ class WebApp
11
+ def initialize
12
+ reset
13
+ end
14
+
15
+ def reset
16
+ @vm = nil
17
+ @graph = 0
18
+ @step = 0
19
+ @thread = nil
20
+ @port_in = StringIO.new
21
+ @port_out = StringIO.new
22
+ @queue = Queue.new
23
+ end
24
+
25
+ def template(name)
26
+ File.join(File.dirname(File.expand_path(__FILE__)), "webapp", "templates", name)
27
+ end
28
+
29
+ def main_page(req)
30
+ reset
31
+ res = Rack::Response.new
32
+ res.status = 200
33
+ res.write File.read(template("main.html"))
34
+ res
35
+ end
36
+
37
+ def not_found(req)
38
+ res = Rack::Response.new
39
+ res.status = 404
40
+ res.write(<<-EOF)
41
+ <html><head><title>#{req.path} is not found</title></head>
42
+ <body>#{req.path} is not found</body></html>
43
+ EOF
44
+ res
45
+ end
46
+
47
+ def reduce_thread(vm)
48
+ while q = @queue.pop
49
+ begin
50
+ q.push(vm.reduce)
51
+ rescue
52
+ q.push($!)
53
+ end
54
+ end
55
+ end
56
+
57
+ def set_program(req)
58
+ res = Rack::Response.new
59
+ begin
60
+ tree = RazyK::Parser.parse(req.params["program"])
61
+ if req.params["mode"] == "true"
62
+ root = Pair.new(:OUT, Pair.new(tree, :IN))
63
+ @port_in = StringIO.new(req.params["stdin"] || "")
64
+ recursive = false
65
+ else
66
+ root = tree
67
+ recursive = true
68
+ end
69
+ # discard previous vm and thread
70
+ if @thread
71
+ @thread.kill
72
+ @thread = nil
73
+ end
74
+ @vm = VM.new(root, @port_in, @port_out, recursive)
75
+ # start Thread for reduction
76
+ @thread = Thread.start do reduce_thread(@vm) end
77
+ rescue
78
+ res.status = 501
79
+ puts $!.message, $@
80
+ return res
81
+ end
82
+ res.header["Content-Type"] = "text/json"
83
+ res.write('{"status":"success"}')
84
+ res
85
+ end
86
+
87
+ def step(req)
88
+ res = Rack::Response.new
89
+ res.header["Content-Type"] = "text/plain"
90
+ if @thread
91
+ rep = Queue.new
92
+ @queue.push(rep)
93
+ ret = rep.pop
94
+ if ret.is_a?(Exception)
95
+ raise ret
96
+ end
97
+ if ret
98
+ @step += 1
99
+ end
100
+ res.write("OK")
101
+ else
102
+ res.write("enter program first")
103
+ end
104
+ res
105
+ end
106
+
107
+ def stdout(req)
108
+ res = Rack::Response.new
109
+ res.header["Content-Type"] = "text/plain"
110
+ res.write(@port_out.string.inspect)
111
+ res
112
+ end
113
+
114
+ def expression(req)
115
+ res = Rack::Response.new
116
+ res.header["Content-Type"] = "text/plain"
117
+ if @vm
118
+ res.write(@vm.tree.inspect)
119
+ else
120
+ res.write("enter program first")
121
+ end
122
+ res
123
+ end
124
+
125
+ def graph(req)
126
+ res = Rack::Response.new
127
+ if @vm
128
+ res.header["Content-Type"] = "image/svg+xml"
129
+ tmpfile = Tempfile.new("razyk_graph")
130
+ RazyK::Graph.graph(@vm.tree, :style => :dag).output(:svg => tmpfile.path)
131
+ res.write(tmpfile.read)
132
+ tmpfile.unlink
133
+ else
134
+ res.header["Content-Type"] = "text/plain"
135
+ res.write("enter program first")
136
+ end
137
+ res
138
+ end
139
+
140
+ def call(env)
141
+ req = Rack::Request.new(env)
142
+ case req.path
143
+ when "/"
144
+ res = main_page(req)
145
+ when "/set_program"
146
+ res = set_program(req)
147
+ when "/step"
148
+ res = step(req)
149
+ when "/stdout"
150
+ res = stdout(req)
151
+ when "/expression"
152
+ res = expression(req)
153
+ when "/graph"
154
+ res = graph(req)
155
+ else
156
+ res = not_found(req)
157
+ end
158
+ res.finish
159
+ end
160
+ end
161
+ end
data/lib/razyk.rb ADDED
@@ -0,0 +1,20 @@
1
+
2
+ require "razyk/node"
3
+ require "razyk/parser"
4
+ require "razyk/vm"
5
+
6
+ module RazyK
7
+ def self.run(program, opt={}, &blk)
8
+ opt[:input] ||= $stdin
9
+ opt[:output] ||= $stdout
10
+ tree = Parser.parse(program, opt)
11
+ root = Pair.new(:OUT, Pair.new(tree, :IN))
12
+ vm = VM.new(root, opt[:input], opt[:output])
13
+
14
+ if blk
15
+ vm.run(&blk)
16
+ else
17
+ vm.run
18
+ end
19
+ end
20
+ end