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 +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
|
+
|