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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/LICENSE +56 -0
- data/README.rdoc +17 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/bin/razyk +72 -0
- data/examples/lazier.scm +328 -0
- data/examples/prelude.scm +86 -0
- data/examples/prime.lazy +20 -0
- data/examples/reverse.lazy +4 -0
- data/examples/reverse.scm +12 -0
- data/lib/razyk/graph.rb +66 -0
- data/lib/razyk/node.rb +117 -0
- data/lib/razyk/parser.rb +423 -0
- data/lib/razyk/parser.y +227 -0
- data/lib/razyk/vm.rb +212 -0
- data/lib/razyk/webapp/templates/main.html +69 -0
- data/lib/razyk/webapp.rb +161 -0
- data/lib/razyk.rb +20 -0
- data/spec/node_spec.rb +58 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/vm_spec.rb +128 -0
- metadata +129 -0
data/lib/razyk/parser.y
ADDED
@@ -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>
|
data/lib/razyk/webapp.rb
ADDED
@@ -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
|