verneuil 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ == 0.2.0 / ???
2
+
3
+ * Call 'verneuil' with your code as argument to execute a script in V.
4
+
1
5
  == 0.1.0 / 8Feb11
2
6
 
3
7
  * Initial version, supporting a rather large subset of ruby already.
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
@@ -23,7 +23,7 @@ task :default => :spec
23
23
  # end
24
24
 
25
25
  desc 'Clear out RDoc'
26
- task :clean => [:clobber_rdoc, :clobber_package]
26
+ task :clean => [:clobber_package]
27
27
 
28
28
  # This task actually builds the gem.
29
29
  spec = eval(File.read('verneuil.gemspec'))
@@ -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
@@ -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'
@@ -2,7 +2,7 @@
2
2
  class Verneuil::Address
3
3
  attr_accessor :ip
4
4
 
5
- def initialize(ip, generator)
5
+ def initialize(ip, generator=nil)
6
6
  @ip = ip
7
7
  @generator = generator
8
8
  end
@@ -2,18 +2,22 @@
2
2
  # Abstracts the notion of a block.
3
3
  #
4
4
  class Verneuil::Block
5
- def initialize(adr, process, scope)
6
- @adr = adr
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, @adr, @scope)
16
+ @process.enter_block(args, @address, @scope)
13
17
  throw :verneuil_code
14
18
  end
15
19
 
16
20
  def inspect
17
- "block@#{@adr.ip}(#{@scope.inspect})"
21
+ "block@#{@address.ip}(#{@scope.inspect})"
18
22
  end
19
23
  end
@@ -13,7 +13,8 @@ class Verneuil::Compiler
13
13
  end
14
14
 
15
15
  def self.compile(*args)
16
- new.compile(*args)
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
- self.send(sym, *args)
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(variable)
161
- type, var = variable
162
-
163
- raise NotImplementedError, "defined? only defined for local variables." \
164
- unless type == :lvar
165
-
166
- # assert: type == :lvar
167
- @generator.test_lvar var
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
- @generator.program.add_method(
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
@@ -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
@@ -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
- @stack.last
47
+ value
35
48
  end
36
49
 
37
- # Runs one instruction and returns nil. If this was the last instruction,
38
- # it returns the programs return value.
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
- # old_ip = @ip
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 [old_ip, instruction, @stack, current_scope, @call_stack]
66
+ # p [self, instruction, @stack, @call_stack, current_scope]
47
67
 
48
- instr_halt if @ip >= @program.size
49
-
50
- halted? ? @stack.last : nil
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
- !!@halted
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 if @ip >= @program.size
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 opcode_OPCODE giving the arguments as method
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
- v_method = @program.lookup_method(nil, name)
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
- v_method = @program.lookup_method(
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 instr_test_lvar(name)
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(block_adr, self, current_scope)
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 @blocks.last
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
@@ -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
- @functions = {}
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=@functions.find { |(r,n), m| m.address.ip == idx }
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
@@ -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
- raise Verneuil::NameError, "No such local variable #{name.inspect}." \
24
- unless @local_vars.has_key?(name)
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
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.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