verneuil 0.1.0 → 0.2.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/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
|