verneuil 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +4 -0
- data/README +12 -5
- data/Rakefile +1 -1
- data/bin/verneuil +24 -0
- data/lib/verneuil.rb +2 -0
- data/lib/verneuil/address.rb +1 -1
- data/lib/verneuil/block.rb +8 -4
- data/lib/verneuil/compiler.rb +58 -14
- data/lib/verneuil/kernel/fork.rb +14 -0
- data/lib/verneuil/kernel/process_join.rb +15 -0
- data/lib/verneuil/method.rb +8 -0
- data/lib/verneuil/process.rb +160 -30
- data/lib/verneuil/process/kernel_methods.rb +30 -0
- data/lib/verneuil/process_group.rb +57 -0
- data/lib/verneuil/program.rb +5 -15
- data/lib/verneuil/scope.rb +21 -6
- data/lib/verneuil/symbol_table.rb +35 -0
- metadata +8 -2
data/HISTORY.txt
CHANGED
data/README
CHANGED
@@ -6,7 +6,8 @@ rubies - they are too pure and don't have the characteristics of real ones.
|
|
6
6
|
|
7
7
|
This is a virtual machine that:
|
8
8
|
* executes something that is almost Ruby (it looks like it)
|
9
|
-
* and that can store its state to disk. And resume.
|
9
|
+
* and that can store its state to disk. And resume.
|
10
|
+
Think: Continuations. Serializable.
|
10
11
|
|
11
12
|
Is it useful? That depends. You could for example use this to
|
12
13
|
* script website interaction with your user
|
@@ -28,9 +29,6 @@ SYNOPSIS
|
|
28
29
|
|
29
30
|
STATUS
|
30
31
|
|
31
|
-
At version pre 0.1 and by no means ready for production. May or may not work,
|
32
|
-
depending on time of day.
|
33
|
-
|
34
32
|
Verneuil currently handles all the programs in spec/programs. The following
|
35
33
|
Ruby features should work:
|
36
34
|
|
@@ -41,5 +39,14 @@ Ruby features should work:
|
|
41
39
|
* while
|
42
40
|
* Masking class methods
|
43
41
|
* correct self
|
44
|
-
|
42
|
+
* fork, join
|
43
|
+
|
44
|
+
Currently this project lays sleeping for a few months - until I will need it
|
45
|
+
again. That day is sure to come.
|
46
|
+
|
47
|
+
CONTRIBUTORS
|
48
|
+
|
49
|
+
Florian Hanke (florianhanke.com)
|
50
|
+
Kaspar Schiess (absurd.li)
|
51
|
+
|
45
52
|
(c) 2011 Kaspar Schiess
|
data/Rakefile
CHANGED
data/bin/verneuil
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
4
|
+
require 'verneuil'
|
5
|
+
|
6
|
+
verbose = false
|
7
|
+
if ARGV.first == '-v'
|
8
|
+
verbose = true
|
9
|
+
ARGV.shift
|
10
|
+
end
|
11
|
+
|
12
|
+
program = Verneuil::Compiler.compile(ARGF.read).program
|
13
|
+
if verbose
|
14
|
+
p program
|
15
|
+
puts
|
16
|
+
end
|
17
|
+
|
18
|
+
process = Verneuil::Process.new(program, nil)
|
19
|
+
retval = process.run
|
20
|
+
|
21
|
+
if verbose
|
22
|
+
puts
|
23
|
+
puts "Program returned #{retval.inspect}."
|
24
|
+
end
|
data/lib/verneuil.rb
CHANGED
@@ -5,6 +5,7 @@ end
|
|
5
5
|
|
6
6
|
require 'verneuil/address'
|
7
7
|
require 'verneuil/method'
|
8
|
+
require 'verneuil/symbol_table'
|
8
9
|
require 'verneuil/block'
|
9
10
|
require 'verneuil/program'
|
10
11
|
|
@@ -13,3 +14,4 @@ require 'verneuil/compiler'
|
|
13
14
|
|
14
15
|
require 'verneuil/scope'
|
15
16
|
require 'verneuil/process'
|
17
|
+
require 'verneuil/process_group'
|
data/lib/verneuil/address.rb
CHANGED
data/lib/verneuil/block.rb
CHANGED
@@ -2,18 +2,22 @@
|
|
2
2
|
# Abstracts the notion of a block.
|
3
3
|
#
|
4
4
|
class Verneuil::Block
|
5
|
-
|
6
|
-
|
5
|
+
# At what address does the block code start?
|
6
|
+
attr_reader :address
|
7
|
+
attr_reader :scope
|
8
|
+
|
9
|
+
def initialize(address, process, scope)
|
10
|
+
@address = address
|
7
11
|
@process = process
|
8
12
|
@scope = scope
|
9
13
|
end
|
10
14
|
|
11
15
|
def call(*args)
|
12
|
-
@process.enter_block(args, @
|
16
|
+
@process.enter_block(args, @address, @scope)
|
13
17
|
throw :verneuil_code
|
14
18
|
end
|
15
19
|
|
16
20
|
def inspect
|
17
|
-
"block@#{@
|
21
|
+
"block@#{@address.ip}(#{@scope.inspect})"
|
18
22
|
end
|
19
23
|
end
|
data/lib/verneuil/compiler.rb
CHANGED
@@ -13,7 +13,8 @@ class Verneuil::Compiler
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.compile(*args)
|
16
|
-
new.
|
16
|
+
new.
|
17
|
+
tap { |compiler| compiler.compile(*args) }
|
17
18
|
end
|
18
19
|
|
19
20
|
# Compiles a piece of code within the current program. This means that any
|
@@ -55,11 +56,34 @@ class Verneuil::Compiler
|
|
55
56
|
raise NotImplementedError, "No acceptor for #{sexp}." \
|
56
57
|
unless respond_to? sym
|
57
58
|
|
58
|
-
|
59
|
+
begin
|
60
|
+
self.send(sym, *args)
|
61
|
+
rescue ArgumentError => ex
|
62
|
+
if md=ex.message.match(/wrong number of arguments \(([^)]+)\)/)
|
63
|
+
sexp_skeleton = s(sexp[0], *sexp[1..-1].map { |e| e && s(e.first, '...') })
|
64
|
+
raise ArgumentError, "wrong number of elements in #{sexp_skeleton} (#{md[1]})."
|
65
|
+
end
|
66
|
+
raise
|
67
|
+
end
|
59
68
|
end
|
60
69
|
|
61
70
|
#--------------------------------------------------------- visitor methods
|
62
71
|
|
72
|
+
# s(:colon2, s(:const, :Verneuil), :Process) - access something inside
|
73
|
+
# a namespace. Constants are resolved at compile time!
|
74
|
+
#
|
75
|
+
def accept_colon2(left, right)
|
76
|
+
left_const = visit(left)
|
77
|
+
const = left_const.const_get(right)
|
78
|
+
@generator.load const
|
79
|
+
end
|
80
|
+
|
81
|
+
# s(:const, :Verneuil) - Resolve a constant globally and return it.
|
82
|
+
#
|
83
|
+
def accept_const(const)
|
84
|
+
eval(const.to_s)
|
85
|
+
end
|
86
|
+
|
63
87
|
# s(:call, RECEIVER, NAME, ARGUMENTS) - Method calls with or without
|
64
88
|
# receiver.
|
65
89
|
#
|
@@ -105,6 +129,14 @@ class Verneuil::Compiler
|
|
105
129
|
@generator.load value
|
106
130
|
end
|
107
131
|
|
132
|
+
# s(:array, ELEMENTS) - array literal.
|
133
|
+
#
|
134
|
+
def accept_array(*elements)
|
135
|
+
elements.each { |el| visit(el) }
|
136
|
+
@generator.load Array
|
137
|
+
@generator.ruby_call :"[]", elements.size
|
138
|
+
end
|
139
|
+
|
108
140
|
# s(:if, COND, THEN, ELSE) - an if statement
|
109
141
|
#
|
110
142
|
def accept_if(cond, _then, _else)
|
@@ -157,14 +189,20 @@ class Verneuil::Compiler
|
|
157
189
|
|
158
190
|
# s(:defined, s(:lvar, :a)) - test if a local variable is defined.
|
159
191
|
#
|
160
|
-
def accept_defined(
|
161
|
-
type,
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
192
|
+
def accept_defined(exp)
|
193
|
+
type, * = exp
|
194
|
+
case type
|
195
|
+
when :call
|
196
|
+
# s(:call, nil, :a, s(:arglist))
|
197
|
+
@generator.test_defined exp[2]
|
198
|
+
when :lvar
|
199
|
+
# s(:lvar, :a)
|
200
|
+
@generator.test_defined exp.last
|
201
|
+
when :lit
|
202
|
+
@generator.load 'expression'
|
203
|
+
else
|
204
|
+
fail "Don't know how to implement defined? with #{variable.inspect}."
|
205
|
+
end
|
168
206
|
end
|
169
207
|
|
170
208
|
# s(:lasgn, VARIABLE, VALUE) - assignment of local variables.
|
@@ -195,10 +233,11 @@ class Verneuil::Compiler
|
|
195
233
|
adr_end = @generator.fwd_adr
|
196
234
|
@generator.jump adr_end
|
197
235
|
|
198
|
-
|
236
|
+
method = Verneuil::Method.new(
|
199
237
|
@class_context.last,
|
200
238
|
name,
|
201
239
|
@generator.current_adr)
|
240
|
+
@generator.program.symbols.add(method)
|
202
241
|
|
203
242
|
# Enters a new local scope and defines arguments
|
204
243
|
visit(args)
|
@@ -256,7 +295,7 @@ class Verneuil::Compiler
|
|
256
295
|
# s(:iter, s(:call, RECEIVER, METHOD, ARGUMENTS), ASSIGNS, BLOCK) - call
|
257
296
|
# a method with a block.
|
258
297
|
#
|
259
|
-
def accept_iter(call, assigns, block)
|
298
|
+
def accept_iter(call, assigns, block=nil)
|
260
299
|
# Jump over the block code
|
261
300
|
adr_end_of_block = @generator.fwd_adr
|
262
301
|
@generator.jump adr_end_of_block
|
@@ -269,7 +308,7 @@ class Verneuil::Compiler
|
|
269
308
|
unless type == :lasgn
|
270
309
|
accept_args(*names)
|
271
310
|
end
|
272
|
-
visit(block)
|
311
|
+
visit(block) if block
|
273
312
|
@generator.return
|
274
313
|
|
275
314
|
adr_end_of_block.resolve
|
@@ -298,6 +337,11 @@ class Verneuil::Compiler
|
|
298
337
|
def accept_self
|
299
338
|
@generator.load_self
|
300
339
|
end
|
301
|
-
|
340
|
+
|
341
|
+
# nil
|
342
|
+
#
|
343
|
+
def accept_nil
|
344
|
+
@generator.load nil
|
345
|
+
end
|
302
346
|
end
|
303
347
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
# Fork the current Verneuil process. Returns the new process instance to the
|
3
|
+
# caller; never exits the block in the child.
|
4
|
+
#
|
5
|
+
# Example: (V-code)
|
6
|
+
# child = fork do
|
7
|
+
# # do forked stuff here
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
Verneuil::Process.kernel_method nil, :fork do |process, _|
|
11
|
+
block = process.current_block
|
12
|
+
|
13
|
+
process.fork_child(block)
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
# Joins the V-process. This means that the joining process will be stopped
|
3
|
+
# until the joinee halts. Returns the process return value.
|
4
|
+
#
|
5
|
+
# Running this method only makes sense on childs of the current process.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
# child = fork { ...; 42 }
|
9
|
+
# child.join # waits and eventually returns 42
|
10
|
+
#
|
11
|
+
Verneuil::Process.kernel_method :'Verneuil::Process', :join do |parent, child|
|
12
|
+
parent.joining << child
|
13
|
+
|
14
|
+
nil
|
15
|
+
end
|
data/lib/verneuil/method.rb
CHANGED
@@ -2,4 +2,12 @@
|
|
2
2
|
# Represents a method that can be called.
|
3
3
|
#
|
4
4
|
class Verneuil::Method < Struct.new(:receiver, :name, :address)
|
5
|
+
def invoke(process, recv_obj)
|
6
|
+
if receiver
|
7
|
+
process.call address, recv_obj
|
8
|
+
else
|
9
|
+
process.call address
|
10
|
+
end
|
11
|
+
throw :verneuil_code
|
12
|
+
end
|
5
13
|
end
|
data/lib/verneuil/process.rb
CHANGED
@@ -21,7 +21,20 @@ class Verneuil::Process
|
|
21
21
|
@ip = 0
|
22
22
|
# Should we stop immediately? Cannot restart after setting this.
|
23
23
|
@halted = false
|
24
|
+
# This process' children
|
25
|
+
@children = []
|
26
|
+
# The list of processes that this process waits for currently.
|
27
|
+
@joining = []
|
24
28
|
end
|
29
|
+
|
30
|
+
# Instruction pointer
|
31
|
+
attr_accessor :ip
|
32
|
+
|
33
|
+
# A process is also a process group, containing its children.
|
34
|
+
attr_reader :children
|
35
|
+
|
36
|
+
# The list of processes that this process waits for currently.
|
37
|
+
attr_reader :joining
|
25
38
|
|
26
39
|
# Runs the program until it completes and returns the last expression
|
27
40
|
# in the program.
|
@@ -31,29 +44,64 @@ class Verneuil::Process
|
|
31
44
|
step
|
32
45
|
end
|
33
46
|
|
34
|
-
|
47
|
+
value
|
35
48
|
end
|
36
49
|
|
37
|
-
# Runs one instruction
|
38
|
-
# it
|
50
|
+
# Runs one instruction. If the current process waits on another process
|
51
|
+
# (joining not empty), it will run an instruction in the other process
|
52
|
+
# instead.
|
39
53
|
#
|
40
54
|
def step
|
41
|
-
|
42
|
-
|
55
|
+
verify_wait_conditions if waiting?
|
56
|
+
|
57
|
+
# If we're still waiting for someone, let's give them a little shove.
|
58
|
+
if waiting?
|
59
|
+
joining.first.step
|
60
|
+
return
|
61
|
+
end
|
62
|
+
|
43
63
|
instruction = fetch_and_advance
|
44
64
|
dispatch(instruction)
|
45
65
|
|
46
|
-
# p [
|
66
|
+
# p [self, instruction, @stack, @call_stack, current_scope]
|
47
67
|
|
48
|
-
|
49
|
-
|
50
|
-
|
68
|
+
# If we just ran into uncharted memory - and we're not still waiting
|
69
|
+
# for someone - we'll just stop the machine.
|
70
|
+
instr_halt if !waiting? && !instruction_pointer_valid?
|
51
71
|
end
|
52
72
|
|
53
73
|
# Returns true if the process has halted because it has reached its end.
|
54
74
|
#
|
55
75
|
def halted?
|
56
|
-
|
76
|
+
@halted
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the process waits for something to happen.
|
80
|
+
#
|
81
|
+
def waiting?
|
82
|
+
not @joining.empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Once the process has halted?, this returns the top of the stack. This is
|
86
|
+
# like the return value of the process.
|
87
|
+
#
|
88
|
+
def value
|
89
|
+
@stack.last
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal helper methods --------------------------------------------------
|
93
|
+
|
94
|
+
# Checks if the conditions that make this process wait still apply. This may
|
95
|
+
# clear up the wait state so that waiting? changes value after this method.
|
96
|
+
#
|
97
|
+
def verify_wait_conditions
|
98
|
+
@joining.delete_if { |process| process.halted? }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the process group having this process as root node.
|
102
|
+
#
|
103
|
+
def group
|
104
|
+
@group ||= Verneuil::ProcessGroup.new(self)
|
57
105
|
end
|
58
106
|
|
59
107
|
# Fetches the next instruction and advances @ip.
|
@@ -61,7 +109,7 @@ class Verneuil::Process
|
|
61
109
|
def fetch_and_advance
|
62
110
|
# Pretends that the memory beyond the current space is filled with :halt
|
63
111
|
# instructions.
|
64
|
-
return :halt
|
112
|
+
return :halt unless instruction_pointer_valid?
|
65
113
|
|
66
114
|
instruction = @program[@ip]
|
67
115
|
@ip += 1
|
@@ -70,7 +118,7 @@ class Verneuil::Process
|
|
70
118
|
end
|
71
119
|
|
72
120
|
# Decodes the instruction into opcode and arguments and calls a method
|
73
|
-
# on this instance called
|
121
|
+
# on this instance called instr_OPCODE giving the arguments as method
|
74
122
|
# arguments.
|
75
123
|
#
|
76
124
|
def dispatch(instruction)
|
@@ -103,7 +151,7 @@ class Verneuil::Process
|
|
103
151
|
def scope(context)
|
104
152
|
Verneuil::Scope.new(context)
|
105
153
|
end
|
106
|
-
|
154
|
+
|
107
155
|
# Returns the currently active scope.
|
108
156
|
#
|
109
157
|
def current_scope
|
@@ -129,13 +177,92 @@ class Verneuil::Process
|
|
129
177
|
end
|
130
178
|
|
131
179
|
# Calls the given address. (like a jump, but puts something on the return
|
132
|
-
# stack)
|
180
|
+
# stack) If context is left empty, it will call inside the current context.
|
133
181
|
#
|
134
182
|
def call(adr, context=nil)
|
135
183
|
@scopes.push scope(context || current_scope.context)
|
136
184
|
@call_stack.push @ip
|
137
185
|
jump adr
|
138
186
|
end
|
187
|
+
|
188
|
+
# Looks up a method in internal tables.
|
189
|
+
#
|
190
|
+
def lookup_method(receiver, name)
|
191
|
+
[
|
192
|
+
@program.symbols,
|
193
|
+
self.class.symbols
|
194
|
+
].each do |table|
|
195
|
+
method = table.lookup_method(receiver, name)
|
196
|
+
return method if method
|
197
|
+
end
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the currently active block or nil if no such block is available.
|
202
|
+
#
|
203
|
+
def current_block
|
204
|
+
@blocks.last
|
205
|
+
end
|
206
|
+
|
207
|
+
# Forks a new process that starts its execution at address and that halts
|
208
|
+
# when encountering a 'return' instruction. Returns that new process
|
209
|
+
# instance.
|
210
|
+
#
|
211
|
+
# The newly created child process is also stored in this process' children
|
212
|
+
# array. You can run the process and all of its children by calling
|
213
|
+
#
|
214
|
+
# process.group.run
|
215
|
+
#
|
216
|
+
def fork_child(block)
|
217
|
+
child = Verneuil::Process.new(@program, nil)
|
218
|
+
child.run_block(block)
|
219
|
+
|
220
|
+
@children << child
|
221
|
+
|
222
|
+
return child
|
223
|
+
end
|
224
|
+
|
225
|
+
# Confines execution to a single method. This means setting up the return
|
226
|
+
# stack to return into nirvana once the VM reaches a 'return' instruction.
|
227
|
+
#
|
228
|
+
def run_block(block)
|
229
|
+
@call_stack.push(-1)
|
230
|
+
jump block.address
|
231
|
+
|
232
|
+
@scopes = [block.scope]
|
233
|
+
end
|
234
|
+
|
235
|
+
# True if the current instruction pointer is valid.
|
236
|
+
#
|
237
|
+
def instruction_pointer_valid?
|
238
|
+
@ip >= 0 &&
|
239
|
+
@ip < @program.size
|
240
|
+
end
|
241
|
+
|
242
|
+
# Inspection of processes
|
243
|
+
#
|
244
|
+
def inspect
|
245
|
+
"process(#{object_id}, #{@ip}, #{@call_stack}, w:#{@joining.size}, c:#{children.size}, h:#{halted?})"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Try to dispatch a method call to a method defined inside the Verneuil
|
249
|
+
# VM. There are currently two kinds of methods in this category:
|
250
|
+
#
|
251
|
+
# * Kernel methods
|
252
|
+
# * Methods implemented in V
|
253
|
+
#
|
254
|
+
def dispatch_to_verneuil(receiver, name)
|
255
|
+
if v_method = lookup_method(receiver, name)
|
256
|
+
catch(:verneuil_code) {
|
257
|
+
retval = v_method.invoke(self, receiver)
|
258
|
+
@stack.push retval
|
259
|
+
}
|
260
|
+
return true
|
261
|
+
end
|
262
|
+
|
263
|
+
return false
|
264
|
+
end
|
265
|
+
|
139
266
|
|
140
267
|
# VM Implementation --------------------------------------------------------
|
141
268
|
|
@@ -149,11 +276,7 @@ class Verneuil::Process
|
|
149
276
|
end
|
150
277
|
|
151
278
|
# Verneuil method?
|
152
|
-
|
153
|
-
if v_method
|
154
|
-
call v_method.address
|
155
|
-
return
|
156
|
-
end
|
279
|
+
return if dispatch_to_verneuil(nil, name)
|
157
280
|
|
158
281
|
# Ruby method! (or else)
|
159
282
|
args = @stack.pop(argc)
|
@@ -164,15 +287,14 @@ class Verneuil::Process
|
|
164
287
|
#
|
165
288
|
def instr_ruby_call(name, argc)
|
166
289
|
receiver = @stack.pop
|
290
|
+
|
291
|
+
# TODO Fix argument count handling
|
292
|
+
# Currently the caller decides with how many arguments he calls the
|
293
|
+
# block and the callee pops off the stack what he wants. This is not a
|
294
|
+
# good situation.
|
167
295
|
|
168
296
|
# Verneuil method? (class method mask)
|
169
|
-
|
170
|
-
receiver.class.name.to_sym,
|
171
|
-
name)
|
172
|
-
if v_method
|
173
|
-
call v_method.address, receiver
|
174
|
-
return
|
175
|
-
end
|
297
|
+
return if dispatch_to_verneuil(receiver, name)
|
176
298
|
|
177
299
|
# Must be a Ruby method then. The catch allows internal classes like
|
178
300
|
# Verneuil::Block to skip the stack.push.
|
@@ -231,7 +353,7 @@ class Verneuil::Process
|
|
231
353
|
|
232
354
|
# Tests if the local variable exists. Puts true/false to the stack.
|
233
355
|
#
|
234
|
-
def
|
356
|
+
def instr_test_defined(name)
|
235
357
|
@stack.push current_scope.defined?(name)
|
236
358
|
end
|
237
359
|
|
@@ -246,7 +368,10 @@ class Verneuil::Process
|
|
246
368
|
# Pushes a block context to the block stack.
|
247
369
|
#
|
248
370
|
def instr_push_block(block_adr)
|
249
|
-
@blocks.push Verneuil::Block.new(
|
371
|
+
@blocks.push Verneuil::Block.new(
|
372
|
+
block_adr,
|
373
|
+
self,
|
374
|
+
current_scope.child)
|
250
375
|
end
|
251
376
|
|
252
377
|
# Loads the currently set implicit block to the stack. This is used when
|
@@ -255,7 +380,7 @@ class Verneuil::Process
|
|
255
380
|
#
|
256
381
|
def instr_load_block
|
257
382
|
fail "BUG: No implicit block!" if @blocks.empty?
|
258
|
-
@stack.push
|
383
|
+
@stack.push current_block
|
259
384
|
end
|
260
385
|
|
261
386
|
# Unloads a block
|
@@ -279,4 +404,9 @@ class Verneuil::Process
|
|
279
404
|
@stack.push current_scope.context
|
280
405
|
end
|
281
406
|
|
282
|
-
end
|
407
|
+
end
|
408
|
+
|
409
|
+
require 'verneuil/process/kernel_methods'
|
410
|
+
|
411
|
+
require 'verneuil/kernel/fork'
|
412
|
+
require 'verneuil/kernel/process_join'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Verneuil::Process
|
2
|
+
# A kernel method points to a Ruby method somewhere.
|
3
|
+
#
|
4
|
+
class KernelMethod < Struct.new(:receiver, :name, :method)
|
5
|
+
def invoke(process, receiver)
|
6
|
+
method.call(process, receiver)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Extends the Process' class with methods that allow managing kernel methods.
|
11
|
+
#
|
12
|
+
class <<self
|
13
|
+
# The VMs own kernel method table.
|
14
|
+
#
|
15
|
+
def symbols
|
16
|
+
@symbols ||= Verneuil::SymbolTable.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Registers a kernel method. These methods have precedence over the
|
20
|
+
# Ruby bridge, but can still be overridden by user methods.
|
21
|
+
#
|
22
|
+
def kernel_method(klass_name, method_name, &method_definition)
|
23
|
+
symbols.add(
|
24
|
+
KernelMethod.new(
|
25
|
+
klass_name,
|
26
|
+
method_name,
|
27
|
+
method_definition))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# A group of processes identified by their root process.
|
2
|
+
#
|
3
|
+
class Verneuil::ProcessGroup
|
4
|
+
def initialize(root)
|
5
|
+
@root = root
|
6
|
+
@counter = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
# Make our root conform to the interface group#step implements.
|
10
|
+
class Root < Struct.new(:process)
|
11
|
+
def step
|
12
|
+
process.step
|
13
|
+
return true
|
14
|
+
end
|
15
|
+
|
16
|
+
def halted?
|
17
|
+
process.halted?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Steps one of the processes in this group once. Returns true if all child
|
22
|
+
# process groups have made one step.
|
23
|
+
#
|
24
|
+
def step
|
25
|
+
list = processes
|
26
|
+
idx = @counter % list.size
|
27
|
+
|
28
|
+
if list[idx].step
|
29
|
+
# Child tree has finished 'one round' of stepping, carry over
|
30
|
+
@counter += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# Should we return a carry to our parent?
|
34
|
+
if @counter >= list.size
|
35
|
+
@counter = 0
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Runs this process group until all processes have halted.
|
43
|
+
#
|
44
|
+
def run
|
45
|
+
until halted?
|
46
|
+
step
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def halted?
|
51
|
+
processes.all? { |p| p.halted? }
|
52
|
+
end
|
53
|
+
|
54
|
+
def processes
|
55
|
+
[Root.new(@root)] + @root.children.map { |p| p.group }
|
56
|
+
end
|
57
|
+
end
|
data/lib/verneuil/program.rb
CHANGED
@@ -7,10 +7,12 @@ require 'verneuil/instruction'
|
|
7
7
|
class Verneuil::Program
|
8
8
|
# Gives access to the internal array of instructions (the program memory)
|
9
9
|
attr_reader :instructions
|
10
|
+
# Access to the programs symbol table.
|
11
|
+
attr_reader :symbols
|
10
12
|
|
11
13
|
def initialize
|
12
14
|
@instructions = []
|
13
|
-
@
|
15
|
+
@symbols = Verneuil::SymbolTable.new
|
14
16
|
end
|
15
17
|
|
16
18
|
# Make programs behave nicely with respect to comparison.
|
@@ -41,26 +43,14 @@ class Verneuil::Program
|
|
41
43
|
def add(instruction)
|
42
44
|
@instructions << instruction
|
43
45
|
end
|
44
|
-
|
45
|
-
# Defines a function.
|
46
|
-
#
|
47
|
-
def add_method(klass, name, adr)
|
48
|
-
@functions[[klass, name]] = Verneuil::Method.new(klass, name, adr)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Returns the function that matches the given receiver and method name.
|
52
|
-
#
|
53
|
-
def lookup_method(recv, name)
|
54
|
-
@functions[[recv, name]]
|
55
|
-
end
|
56
|
-
|
46
|
+
|
57
47
|
# Printing
|
58
48
|
#
|
59
49
|
def inspect
|
60
50
|
s = ''
|
61
51
|
@instructions.each_with_index do |instruction, idx|
|
62
52
|
method_label = ''
|
63
|
-
if entry
|
53
|
+
if entry=symbols.methods.find { |(r,n), m| m.address.ip == idx }
|
64
54
|
m = entry.last
|
65
55
|
method_label = [m.receiver, m.name].inspect
|
66
56
|
end
|
data/lib/verneuil/scope.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
# to user code.
|
5
5
|
#
|
6
6
|
class Verneuil::Scope
|
7
|
+
# In Ruby, we also call this the 'self'.
|
7
8
|
attr_reader :context
|
8
9
|
|
9
10
|
def initialize(context, local_vars={}, parent=nil)
|
@@ -12,19 +13,26 @@ class Verneuil::Scope
|
|
12
13
|
@parent = parent
|
13
14
|
end
|
14
15
|
|
15
|
-
def enter
|
16
|
-
Verneuil::Scope.new(context, {}, nil)
|
17
|
-
end
|
18
|
-
|
19
16
|
def lvar_exist?(name)
|
17
|
+
lvar_exist_locally?(name) || @parent && @parent.lvar_exist?(name)
|
18
|
+
end
|
19
|
+
def lvar_exist_locally?(name)
|
20
20
|
@local_vars.has_key?(name)
|
21
21
|
end
|
22
22
|
def lvar_get(name)
|
23
|
-
|
24
|
-
|
23
|
+
unless lvar_exist_locally? name
|
24
|
+
return @parent.lvar_get(name) if @parent
|
25
|
+
|
26
|
+
raise Verneuil::NameError, "No such local variable #{name.inspect}."
|
27
|
+
end
|
28
|
+
|
25
29
|
@local_vars[name]
|
26
30
|
end
|
27
31
|
def lvar_set(name, value)
|
32
|
+
unless lvar_exist_locally? name
|
33
|
+
return @parent.lvar_set(name, value) if @parent && @parent.lvar_exist?(name)
|
34
|
+
end
|
35
|
+
|
28
36
|
@local_vars[name] = value
|
29
37
|
end
|
30
38
|
def defined?(name)
|
@@ -33,6 +41,13 @@ class Verneuil::Scope
|
|
33
41
|
nil
|
34
42
|
end
|
35
43
|
|
44
|
+
# Returns a nested scope that has access to the current scope in a Ruby 1.9
|
45
|
+
# fashion.
|
46
|
+
#
|
47
|
+
def child(local_vars={})
|
48
|
+
self.class.new(context, local_vars, self)
|
49
|
+
end
|
50
|
+
|
36
51
|
def method_call(name, *args)
|
37
52
|
context.send(name, *args)
|
38
53
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
# A registry for methods.
|
3
|
+
#
|
4
|
+
class Verneuil::SymbolTable
|
5
|
+
attr_reader :methods
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@methods = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Defines a function. This function will override the default of calling
|
12
|
+
# back to Ruby. Methods added here must support at least #receiver,
|
13
|
+
# #name and #invoke.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# # Replaces Foo#bar with the V method at address 15.
|
17
|
+
# add(method_obj)
|
18
|
+
#
|
19
|
+
def add(method)
|
20
|
+
key = [method.receiver, method.name]
|
21
|
+
@methods[key] = method
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the function that matches the given receiver and method name.
|
25
|
+
#
|
26
|
+
def lookup_method(recv, name)
|
27
|
+
key = if recv
|
28
|
+
[recv.class.name.to_sym, name]
|
29
|
+
else
|
30
|
+
[nil, name]
|
31
|
+
end
|
32
|
+
|
33
|
+
@methods[key]
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Kaspar Schiess
|
@@ -71,15 +71,21 @@ files:
|
|
71
71
|
- LICENSE
|
72
72
|
- Rakefile
|
73
73
|
- README
|
74
|
+
- bin/verneuil
|
74
75
|
- lib/verneuil/address.rb
|
75
76
|
- lib/verneuil/block.rb
|
76
77
|
- lib/verneuil/compiler.rb
|
77
78
|
- lib/verneuil/generator.rb
|
78
79
|
- lib/verneuil/instruction.rb
|
80
|
+
- lib/verneuil/kernel/fork.rb
|
81
|
+
- lib/verneuil/kernel/process_join.rb
|
79
82
|
- lib/verneuil/method.rb
|
83
|
+
- lib/verneuil/process/kernel_methods.rb
|
80
84
|
- lib/verneuil/process.rb
|
85
|
+
- lib/verneuil/process_group.rb
|
81
86
|
- lib/verneuil/program.rb
|
82
87
|
- lib/verneuil/scope.rb
|
88
|
+
- lib/verneuil/symbol_table.rb
|
83
89
|
- lib/verneuil.rb
|
84
90
|
has_rdoc: true
|
85
91
|
homepage: http://kschiess.github.com/verneuil
|