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