verneuil 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -0
- data/HISTORY.txt +6 -0
- data/LICENSE +23 -0
- data/README +45 -0
- data/Rakefile +35 -0
- data/lib/verneuil/address.rb +21 -0
- data/lib/verneuil/block.rb +19 -0
- data/lib/verneuil/compiler.rb +303 -0
- data/lib/verneuil/generator.rb +60 -0
- data/lib/verneuil/instruction.rb +10 -0
- data/lib/verneuil/method.rb +5 -0
- data/lib/verneuil/process.rb +282 -0
- data/lib/verneuil/program.rb +71 -0
- data/lib/verneuil/scope.rb +44 -0
- data/lib/verneuil.rb +15 -0
- metadata +118 -0
data/Gemfile
ADDED
data/HISTORY.txt
ADDED
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,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
|
+
|