verneuil 0.1.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/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'ruby_parser', '~> 2.0'
4
+
5
+ group :development do
6
+ gem 'rspec'
7
+ gem 'flexmock'
8
+
9
+ gem 'guard'
10
+ gem 'growl'
11
+ end
data/HISTORY.txt ADDED
@@ -0,0 +1,6 @@
1
+ == 0.1.0 / 8Feb11
2
+
3
+ * Initial version, supporting a rather large subset of ruby already.
4
+ Getting this out so I can base later releases on something.
5
+
6
+ * Not production ready, the paint is still fresh on this one.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Copyright (c) 2011 Kaspar Schiess
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,45 @@
1
+
2
+ A virtual machine for something almost like ruby.
3
+
4
+ The verneuil process produces artificial rubies that are somewhat unlike real
5
+ rubies - they are too pure and don't have the characteristics of real ones.
6
+
7
+ This is a virtual machine that:
8
+ * executes something that is almost Ruby (it looks like it)
9
+ * and that can store its state to disk. And resume.
10
+
11
+ Is it useful? That depends. You could for example use this to
12
+ * script website interaction with your user
13
+ * create scripts that run for days/months
14
+ * create crash safe programs (checkpoints? transactions?)
15
+ * transfer running programs over the wire (agents!)?
16
+ * and perhaps more
17
+
18
+ STEREOID-CODE
19
+
20
+ Verneuil is *eval* on stereoids.
21
+
22
+ SYNOPSIS
23
+
24
+ code = "puts 42"
25
+ program = Verneuil::Compiler.compile(code)
26
+ process = Verneuil::Process.new(code, self)
27
+ process.run # prints 42 to the console.
28
+
29
+ STATUS
30
+
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
+ Verneuil currently handles all the programs in spec/programs. The following
35
+ Ruby features should work:
36
+
37
+ * Method calls
38
+ * Local variables
39
+ * Method definitions
40
+ * if then else
41
+ * while
42
+ * Masking class methods
43
+ * correct self
44
+
45
+ (c) 2011 Kaspar Schiess
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+
2
+ require "rubygems"
3
+ require "rake/rdoctask"
4
+ require 'rspec/core/rake_task'
5
+ require "rake/gempackagetask"
6
+
7
+ desc "Run all examples"
8
+ RSpec::Core::RakeTask.new
9
+
10
+ task :default => :spec
11
+
12
+ # require 'sdoc'
13
+ #
14
+ # # Generate documentation
15
+ # Rake::RDocTask.new do |rdoc|
16
+ # rdoc.title = "parslet - construction of parsers made easy"
17
+ # rdoc.options << '--line-numbers'
18
+ # rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
19
+ # rdoc.template = 'direct' # lighter template used on railsapi.com
20
+ # rdoc.main = "README"
21
+ # rdoc.rdoc_files.include("README", "lib/**/*.rb")
22
+ # rdoc.rdoc_dir = "rdoc"
23
+ # end
24
+
25
+ desc 'Clear out RDoc'
26
+ task :clean => [:clobber_rdoc, :clobber_package]
27
+
28
+ # This task actually builds the gem.
29
+ spec = eval(File.read('verneuil.gemspec'))
30
+ desc "Generate the gem package."
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ task :gem => :spec
@@ -0,0 +1,21 @@
1
+
2
+ class Verneuil::Address
3
+ attr_accessor :ip
4
+
5
+ def initialize(ip, generator)
6
+ @ip = ip
7
+ @generator = generator
8
+ end
9
+
10
+ def inspect
11
+ "-> #{ip}"
12
+ end
13
+
14
+ def resolve
15
+ @generator.resolve(self)
16
+ end
17
+
18
+ def ==(other)
19
+ self.ip == other.ip
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+
2
+ # Abstracts the notion of a block.
3
+ #
4
+ class Verneuil::Block
5
+ def initialize(adr, process, scope)
6
+ @adr = adr
7
+ @process = process
8
+ @scope = scope
9
+ end
10
+
11
+ def call(*args)
12
+ @process.enter_block(args, @adr, @scope)
13
+ throw :verneuil_code
14
+ end
15
+
16
+ def inspect
17
+ "block@#{@adr.ip}(#{@scope.inspect})"
18
+ end
19
+ end
@@ -0,0 +1,303 @@
1
+
2
+ require 'ruby_parser'
3
+
4
+ # Compiles verneuil code into a program.
5
+ #
6
+ class Verneuil::Compiler
7
+ def initialize
8
+ @generator = Verneuil::Generator.new
9
+ end
10
+
11
+ def program
12
+ @generator.program
13
+ end
14
+
15
+ def self.compile(*args)
16
+ new.compile(*args)
17
+ end
18
+
19
+ # Compiles a piece of code within the current program. This means that any
20
+ # method definitions that have already been encountered will be used to
21
+ # resolve method calls. Returns the program which can also be accessed
22
+ # using #program on the compiler instance.
23
+ #
24
+ def compile(code)
25
+ parser = RubyParser.new
26
+ sexp = parser.parse(code)
27
+ # pp sexp
28
+
29
+ visitor = Visitor.new(@generator, self)
30
+ visitor.visit(sexp)
31
+
32
+ @generator.program
33
+ end
34
+
35
+ # Compiler visitor visits sexps and transforms them into executable code
36
+ # by calling back on its code generator.
37
+ #
38
+ class Visitor
39
+ NOPOP = [:return, :defn, :class]
40
+
41
+ def initialize(generator, compiler)
42
+ @generator = generator
43
+ @compiler = compiler
44
+
45
+ @class_context = []
46
+ end
47
+
48
+ def visit(sexp)
49
+ type, *args = sexp
50
+
51
+ sym = "accept_#{type}".to_sym
52
+
53
+ raise ArgumentError, "No sexp given?" unless sexp && type
54
+
55
+ raise NotImplementedError, "No acceptor for #{sexp}." \
56
+ unless respond_to? sym
57
+
58
+ self.send(sym, *args)
59
+ end
60
+
61
+ #--------------------------------------------------------- visitor methods
62
+
63
+ # s(:call, RECEIVER, NAME, ARGUMENTS) - Method calls with or without
64
+ # receiver.
65
+ #
66
+ def accept_call(receiver, method_name, args)
67
+ argc = visit(args)
68
+
69
+ if receiver
70
+ visit(receiver)
71
+ @generator.ruby_call method_name, argc
72
+ else
73
+ @generator.ruby_call_implicit method_name, argc
74
+ end
75
+ end
76
+
77
+ # s(:arglist, ARGUMENT, ARGUMENT) - Argument lists. Needs to return the
78
+ # actual number of arguments that we've compiled.
79
+ #
80
+ def accept_arglist(*args)
81
+ args.each do |arg|
82
+ visit(arg)
83
+ end
84
+ return args.size
85
+ end
86
+
87
+ # s(:block, STATEMENT, STATEMENT, ...) - Blocks of code.
88
+ #
89
+ def accept_block(*statements)
90
+ statements.each_with_index do |statement, idx|
91
+ type, *rest = statement
92
+
93
+ visit(statement)
94
+
95
+ unless idx+1 == statements.size ||
96
+ NOPOP.include?(type)
97
+ @generator.pop 1
98
+ end
99
+ end
100
+ end
101
+
102
+ # s(:lit, LITERAL) - A literal value.
103
+ #
104
+ def accept_lit(value)
105
+ @generator.load value
106
+ end
107
+
108
+ # s(:if, COND, THEN, ELSE) - an if statement
109
+ #
110
+ def accept_if(cond, _then, _else)
111
+ adr_else = @generator.fwd_adr
112
+ adr_end = @generator.fwd_adr
113
+
114
+ visit(cond)
115
+ @generator.jump_if_false adr_else
116
+
117
+ visit(_then)
118
+ @generator.jump adr_end
119
+
120
+ adr_else.resolve
121
+ if _else
122
+ visit(_else)
123
+ else
124
+ @generator.load nil
125
+ end
126
+ adr_end.resolve
127
+ end
128
+
129
+ # s(:while, test, body, true) - a while statement.
130
+ #
131
+ def accept_while(cond, body, t)
132
+ fail "What is t for?" unless t
133
+
134
+ adr_end = @generator.fwd_adr
135
+
136
+ adr_test = @generator.current_adr
137
+ visit(cond)
138
+ @generator.jump_if_false adr_end
139
+
140
+ visit(body)
141
+ @generator.jump adr_test
142
+
143
+ adr_end.resolve
144
+ end
145
+
146
+ # s(:op_asgn_or, s(:lvar, :a), s(:lasgn, :a, s(:lit, 1))) - var ||= value.
147
+ #
148
+ def accept_op_asgn_or(variable, assign)
149
+ (*), var, val = assign
150
+ visit(
151
+ s(:lasgn, var,
152
+ s(:if,
153
+ s(:defined, variable),
154
+ variable,
155
+ val)))
156
+ end
157
+
158
+ # s(:defined, s(:lvar, :a)) - test if a local variable is defined.
159
+ #
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
168
+ end
169
+
170
+ # s(:lasgn, VARIABLE, VALUE) - assignment of local variables.
171
+ # s(:lasgn, VARIABLE) - implicit assignment of local vars.
172
+ #
173
+ def accept_lasgn(*args)
174
+ if args.size == 2
175
+ val = args.last
176
+ visit(val)
177
+ end
178
+
179
+ name = args.first
180
+ @generator.dup 0
181
+ @generator.lvar_set name
182
+ end
183
+
184
+ # s(:lvar, VARIABLE) - local variable access.
185
+ #
186
+ def accept_lvar(name)
187
+ visit(
188
+ s(:call, nil, name, s(:arglist)))
189
+ end
190
+
191
+ # s(:defn, NAME, ARG_NAMES, BODY) - a method definition.
192
+ #
193
+ def accept_defn(name, args, body)
194
+ # Jumping over functions so that definitions don't get executed.
195
+ adr_end = @generator.fwd_adr
196
+ @generator.jump adr_end
197
+
198
+ @generator.program.add_method(
199
+ @class_context.last,
200
+ name,
201
+ @generator.current_adr)
202
+
203
+ # Enters a new local scope and defines arguments
204
+ visit(args)
205
+
206
+ visit(body)
207
+ @generator.return
208
+
209
+ adr_end.resolve
210
+ end
211
+
212
+ # s(:class, :Fixnum, SUPER, s(:scope, s(:defn, :foo, s(:args), DEF))) -
213
+ # a method definition for a class.
214
+ #
215
+ # NOTE that verneuils classes don't work like Rubies classes at all. This
216
+ # is more or less just a method for masking methods on Ruby classes,
217
+ # not a way to define classes.
218
+ #
219
+ def accept_class(name, _, scope)
220
+ @class_context.push name
221
+
222
+ visit(scope)
223
+ ensure
224
+ @class_context.pop
225
+ end
226
+
227
+ # s(:args, ARGUMENT_NAMES)
228
+ #
229
+ def accept_args(*arg_names)
230
+ arg_names.each do |name|
231
+ if name.to_s.start_with?('&')
232
+ stripped_name = name.to_s[1..-1].to_sym
233
+ @generator.load_block
234
+ @generator.lvar_set stripped_name
235
+ else
236
+ @generator.lvar_set name
237
+ end
238
+ end
239
+ end
240
+
241
+ # s(:scope, BODY) - a new scope.
242
+ #
243
+ def accept_scope(body)
244
+ # For now, we don't know what we'll eventually do with scopes. So here
245
+ # is this very basic idea...
246
+ visit(body)
247
+ end
248
+
249
+ # s(:return, RETVAL) - return from the current method.
250
+ #
251
+ def accept_return(val)
252
+ visit(val)
253
+ @generator.return
254
+ end
255
+
256
+ # s(:iter, s(:call, RECEIVER, METHOD, ARGUMENTS), ASSIGNS, BLOCK) - call
257
+ # a method with a block.
258
+ #
259
+ def accept_iter(call, assigns, block)
260
+ # Jump over the block code
261
+ adr_end_of_block = @generator.fwd_adr
262
+ @generator.jump adr_end_of_block
263
+
264
+ adr_start_of_block = @generator.current_adr
265
+
266
+ if assigns
267
+ type, *names = assigns
268
+ fail "BUG: Unsupported type of block arguments: #{type}" \
269
+ unless type == :lasgn
270
+ accept_args(*names)
271
+ end
272
+ visit(block)
273
+ @generator.return
274
+
275
+ adr_end_of_block.resolve
276
+
277
+ # Compile the call as we would normally, adding a push_block/pop_block
278
+ # around it.
279
+ @generator.push_block adr_start_of_block
280
+ visit(call)
281
+ @generator.pop_block
282
+ end
283
+
284
+ # true.
285
+ #
286
+ def accept_true
287
+ @generator.load true
288
+ end
289
+
290
+ # false.
291
+ #
292
+ def accept_false
293
+ @generator.load false
294
+ end
295
+
296
+ # self
297
+ #
298
+ def accept_self
299
+ @generator.load_self
300
+ end
301
+
302
+ end
303
+ end
@@ -0,0 +1,60 @@
1
+
2
+ require 'verneuil/process'
3
+
4
+ # A generator for VM code.
5
+ #
6
+ class Verneuil::Generator
7
+ # Returns the instruction stream as has been assembled to this moment.
8
+ #
9
+ attr_reader :program
10
+
11
+ def initialize
12
+ @program = Verneuil::Program.new
13
+ end
14
+
15
+ # Adds an instruction to the current stream.
16
+ #
17
+ def add_instruction(*parts)
18
+ @program.add Verneuil::Instruction.new(*parts)
19
+ end
20
+
21
+ # Returns an address that hasn't been fixed to an instruction pointer yet.
22
+ #
23
+ def fwd_adr
24
+ current_adr
25
+ end
26
+
27
+ # Returns an address that points at the location given by +ip+.
28
+ #
29
+ def abs_adr(ip)
30
+ Verneuil::Address.new(ip, self)
31
+ end
32
+
33
+ # Returns an address that points at the current location in the program.
34
+ #
35
+ def current_adr
36
+ Verneuil::Address.new(program.size, self)
37
+ end
38
+
39
+ # Resolves an address to the current location.
40
+ #
41
+ def resolve(adr)
42
+ adr.ip = program.size
43
+ end
44
+
45
+ # This implements many of the instruction methods that are just one-to-one
46
+ # correspondances between methods on the generator and the instructions in
47
+ # the stream.
48
+ #
49
+ # Example:
50
+ # generator.foo 1,2,3
51
+ # # will add [:foo, 1,2,3] to the instruction stream.
52
+ #
53
+ Verneuil::Process.instance_methods.
54
+ select { |method| method.to_s =~ /^instr_(.*)/ }.
55
+ map { |method| method.to_s[6..-1].to_sym }.each do |instruction|
56
+ define_method(instruction) do |*args|
57
+ add_instruction instruction, *args
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,10 @@
1
+
2
+ # A single instruction for the verneuil processor. This is like sexp a thin
3
+ # wrapper around array.
4
+ #
5
+ class Verneuil::Instruction < Array
6
+ def initialize(*parts)
7
+ super(parts.size)
8
+ replace(parts)
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+
2
+ # Represents a method that can be called.
3
+ #
4
+ class Verneuil::Method < Struct.new(:receiver, :name, :address)
5
+ end
@@ -0,0 +1,282 @@
1
+
2
+ # Processor state of one process.
3
+ #
4
+ class Verneuil::Process
5
+ # Create a process, giving it a program to run and a context to run in.
6
+ #
7
+ def initialize(program, context)
8
+ # p program
9
+
10
+ # The program that is being executed.
11
+ @program = program
12
+ # Keeps the current scope and the ones before it.
13
+ @scopes = [scope(context)]
14
+ # Keeps implicit blocks when executing iteration code.
15
+ @blocks = []
16
+ # Value stack
17
+ @stack = []
18
+ # Return address stack
19
+ @call_stack = []
20
+ # Instruction pointer
21
+ @ip = 0
22
+ # Should we stop immediately? Cannot restart after setting this.
23
+ @halted = false
24
+ end
25
+
26
+ # Runs the program until it completes and returns the last expression
27
+ # in the program.
28
+ #
29
+ def run
30
+ until halted?
31
+ step
32
+ end
33
+
34
+ @stack.last
35
+ end
36
+
37
+ # Runs one instruction and returns nil. If this was the last instruction,
38
+ # it returns the programs return value.
39
+ #
40
+ def step
41
+ # old_ip = @ip
42
+
43
+ instruction = fetch_and_advance
44
+ dispatch(instruction)
45
+
46
+ # p [old_ip, instruction, @stack, current_scope, @call_stack]
47
+
48
+ instr_halt if @ip >= @program.size
49
+
50
+ halted? ? @stack.last : nil
51
+ end
52
+
53
+ # Returns true if the process has halted because it has reached its end.
54
+ #
55
+ def halted?
56
+ !!@halted
57
+ end
58
+
59
+ # Fetches the next instruction and advances @ip.
60
+ #
61
+ def fetch_and_advance
62
+ # Pretends that the memory beyond the current space is filled with :halt
63
+ # instructions.
64
+ return :halt if @ip >= @program.size
65
+
66
+ instruction = @program[@ip]
67
+ @ip += 1
68
+
69
+ instruction
70
+ end
71
+
72
+ # Decodes the instruction into opcode and arguments and calls a method
73
+ # on this instance called opcode_OPCODE giving the arguments as method
74
+ # arguments.
75
+ #
76
+ def dispatch(instruction)
77
+ opcode, *rest = instruction
78
+ sym = "instr_#{opcode}"
79
+
80
+ begin
81
+ self.send(sym, *rest)
82
+ rescue NoMethodError => ex
83
+ # Catch our own method error, but not those that happen inside an
84
+ # instruction that exists..
85
+ if ex.message.match(/#{sym}/)
86
+ warn @program
87
+ exception "Unknown opcode #{opcode} (missing ##{sym})."
88
+ else
89
+ raise
90
+ end
91
+ end
92
+ end
93
+
94
+ # Raises an exception that contains useful information about where the
95
+ # process stopped.
96
+ #
97
+ def exception(message)
98
+ fail message
99
+ end
100
+
101
+ # Produces a new scope that links to the given context.
102
+ #
103
+ def scope(context)
104
+ Verneuil::Scope.new(context)
105
+ end
106
+
107
+ # Returns the currently active scope.
108
+ #
109
+ def current_scope
110
+ @scopes.last
111
+ end
112
+
113
+ # Enters a block. This pushes args to the stack, installs the scope given
114
+ # and jumps to adr.
115
+ #
116
+ def enter_block(args, adr, scope)
117
+ args.each { |arg| @stack.push arg }
118
+
119
+ @scopes.push scope
120
+
121
+ @call_stack.push @ip
122
+ jump adr
123
+ end
124
+
125
+ # Jumps to the given address.
126
+ #
127
+ def jump(adr)
128
+ @ip = adr.ip
129
+ end
130
+
131
+ # Calls the given address. (like a jump, but puts something on the return
132
+ # stack)
133
+ #
134
+ def call(adr, context=nil)
135
+ @scopes.push scope(context || current_scope.context)
136
+ @call_stack.push @ip
137
+ jump adr
138
+ end
139
+
140
+ # VM Implementation --------------------------------------------------------
141
+
142
+ # A call to an implicit target (self).
143
+ #
144
+ def instr_ruby_call_implicit(name, argc)
145
+ # Local variable
146
+ if argc==0 && current_scope.lvar_exist?(name)
147
+ @stack.push current_scope.lvar_get(name)
148
+ return
149
+ end
150
+
151
+ # Verneuil method?
152
+ v_method = @program.lookup_method(nil, name)
153
+ if v_method
154
+ call v_method.address
155
+ return
156
+ end
157
+
158
+ # Ruby method! (or else)
159
+ args = @stack.pop(argc)
160
+ @stack.push current_scope.method_call(name, *args)
161
+ end
162
+
163
+ # A call to an explicit receiver. The receiver should be on top of the stack.
164
+ #
165
+ def instr_ruby_call(name, argc)
166
+ receiver = @stack.pop
167
+
168
+ # 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
176
+
177
+ # Must be a Ruby method then. The catch allows internal classes like
178
+ # Verneuil::Block to skip the stack.push.
179
+ args = @stack.pop(argc)
180
+ catch(:verneuil_code) {
181
+ retval = receiver.send(name, *args)
182
+ @stack.push retval
183
+ }
184
+ end
185
+
186
+ # ------------------------------------------------------------ STACK CONTROL
187
+
188
+ # Pops n elements off the internal stack
189
+ #
190
+ def instr_pop(n)
191
+ @stack.pop(n)
192
+ end
193
+
194
+ # Loads a literal value to the stack.
195
+ #
196
+ def instr_load(val)
197
+ @stack.push val
198
+ end
199
+
200
+ # Duplicates the value given by stack_idx (from the top) and pushes it
201
+ # to the stack.
202
+ #
203
+ def instr_dup(stack_idx)
204
+ @stack.push @stack[-stack_idx-1]
205
+ end
206
+
207
+ # ----------------------------------------------------------------- JUMPS !!
208
+
209
+ # Jumps to the given address if the top of the stack contains a false value.
210
+ #
211
+ def instr_jump_if_false(adr)
212
+ val = @stack.pop
213
+ @ip = adr.ip unless val
214
+ end
215
+
216
+ # Unconditional jump
217
+ #
218
+ def instr_jump(adr)
219
+ jump adr
220
+ end
221
+
222
+ # Returning from a method (pops the call_stack.)
223
+ #
224
+ def instr_return
225
+ exception "Nothing to return to on the call stack." if @call_stack.empty?
226
+ @ip = @call_stack.pop
227
+ @scopes.pop
228
+ end
229
+
230
+ # ---------------------------------------------------------------- VARIABLES
231
+
232
+ # Tests if the local variable exists. Puts true/false to the stack.
233
+ #
234
+ def instr_test_lvar(name)
235
+ @stack.push current_scope.defined?(name)
236
+ end
237
+
238
+ # Sets the local variable given by name.
239
+ #
240
+ def instr_lvar_set(name)
241
+ current_scope.lvar_set(name, @stack.pop)
242
+ end
243
+
244
+ # ------------------------------------------------------------------- BLOCKS
245
+
246
+ # Pushes a block context to the block stack.
247
+ #
248
+ def instr_push_block(block_adr)
249
+ @blocks.push Verneuil::Block.new(block_adr, self, current_scope)
250
+ end
251
+
252
+ # Loads the currently set implicit block to the stack. This is used when
253
+ # turning an implicit block into an explicit block by storing it to a
254
+ # local variable.
255
+ #
256
+ def instr_load_block
257
+ fail "BUG: No implicit block!" if @blocks.empty?
258
+ @stack.push @blocks.last
259
+ end
260
+
261
+ # Unloads a block
262
+ #
263
+ def instr_pop_block
264
+ @blocks.pop
265
+ end
266
+
267
+ # Halts the processor and returns the last value on the stack.
268
+ #
269
+ def instr_halt
270
+ @halted = true
271
+ end
272
+
273
+ # ------------------------------------------------------ METHODS FOR CLASSES
274
+
275
+ # Loads the self on the stack. Toplevel self is the context you give, later
276
+ # on this may change to the class we're masking a method of.
277
+ #
278
+ def instr_load_self
279
+ @stack.push current_scope.context
280
+ end
281
+
282
+ end
@@ -0,0 +1,71 @@
1
+
2
+ require 'verneuil/instruction'
3
+
4
+ # A program is a sequence of instructions that can be run inside a process.
5
+ # You can use the compiler (Verneuil::Compiler) to create programs.
6
+ #
7
+ class Verneuil::Program
8
+ # Gives access to the internal array of instructions (the program memory)
9
+ attr_reader :instructions
10
+
11
+ def initialize
12
+ @instructions = []
13
+ @functions = {}
14
+ end
15
+
16
+ # Make programs behave nicely with respect to comparison.
17
+ def hash # :nodoc:
18
+ instructions.hash
19
+ end
20
+ def eql?(program) # :nodoc:
21
+ instructions.eql? program.instructions
22
+ end
23
+ def ==(program)
24
+ instructions == program.instructions
25
+ end
26
+
27
+ # Retrieves instruction at idx
28
+ #
29
+ def [](idx)
30
+ instructions[idx]
31
+ end
32
+
33
+ # Returns the size of the current program
34
+ #
35
+ def size
36
+ instructions.size
37
+ end
38
+
39
+ # Adds an instruction to the program. (at the end)
40
+ #
41
+ def add(instruction)
42
+ @instructions << instruction
43
+ 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
+
57
+ # Printing
58
+ #
59
+ def inspect
60
+ s = ''
61
+ @instructions.each_with_index do |instruction, idx|
62
+ method_label = ''
63
+ if entry=@functions.find { |(r,n), m| m.address.ip == idx }
64
+ m = entry.last
65
+ method_label = [m.receiver, m.name].inspect
66
+ end
67
+ s << sprintf("%20s %04d %s\n", method_label, idx, instruction)
68
+ end
69
+ s
70
+ end
71
+ end
@@ -0,0 +1,44 @@
1
+
2
+ # The execution scope for verneuil code. This maintains a link to the external
3
+ # context given when starting the process to be able to delegate method calls
4
+ # to user code.
5
+ #
6
+ class Verneuil::Scope
7
+ attr_reader :context
8
+
9
+ def initialize(context, local_vars={}, parent=nil)
10
+ @local_vars = local_vars
11
+ @context = context
12
+ @parent = parent
13
+ end
14
+
15
+ def enter
16
+ Verneuil::Scope.new(context, {}, nil)
17
+ end
18
+
19
+ def lvar_exist?(name)
20
+ @local_vars.has_key?(name)
21
+ end
22
+ def lvar_get(name)
23
+ raise Verneuil::NameError, "No such local variable #{name.inspect}." \
24
+ unless @local_vars.has_key?(name)
25
+ @local_vars[name]
26
+ end
27
+ def lvar_set(name, value)
28
+ @local_vars[name] = value
29
+ end
30
+ def defined?(name)
31
+ return 'local-variable' if lvar_exist?(name)
32
+ return 'method' if context.respond_to?(name)
33
+ nil
34
+ end
35
+
36
+ def method_call(name, *args)
37
+ context.send(name, *args)
38
+ end
39
+
40
+ def inspect
41
+ "scope(#{@local_vars.inspect[2..-2]})" +
42
+ (@parent ? "-> #{@parent.inspect}" : '')
43
+ end
44
+ end
data/lib/verneuil.rb ADDED
@@ -0,0 +1,15 @@
1
+
2
+ module Verneuil
3
+ class NameError < StandardError; end
4
+ end
5
+
6
+ require 'verneuil/address'
7
+ require 'verneuil/method'
8
+ require 'verneuil/block'
9
+ require 'verneuil/program'
10
+
11
+ require 'verneuil/generator'
12
+ require 'verneuil/compiler'
13
+
14
+ require 'verneuil/scope'
15
+ require 'verneuil/process'
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: verneuil
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Kaspar Schiess
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-02 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ruby_parser
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 0
31
+ version: "2.0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :development
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: flexmock
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id003
60
+ description:
61
+ email: kaspar.schiess@absurd.li
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - README
68
+ files:
69
+ - Gemfile
70
+ - HISTORY.txt
71
+ - LICENSE
72
+ - Rakefile
73
+ - README
74
+ - lib/verneuil/address.rb
75
+ - lib/verneuil/block.rb
76
+ - lib/verneuil/compiler.rb
77
+ - lib/verneuil/generator.rb
78
+ - lib/verneuil/instruction.rb
79
+ - lib/verneuil/method.rb
80
+ - lib/verneuil/process.rb
81
+ - lib/verneuil/program.rb
82
+ - lib/verneuil/scope.rb
83
+ - lib/verneuil.rb
84
+ has_rdoc: true
85
+ homepage: http://kschiess.github.com/verneuil
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --main
91
+ - README
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Artificial Rubies. Using a fusion process.
117
+ test_files: []
118
+