verneuil 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+