razyk 0.0.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.
@@ -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