BadBoy-ruby-hackvm 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/README.textile +61 -0
- data/Rakefile +12 -0
- data/bin/hackvm.rb +245 -0
- data/ruby-hackvm.gemspec +31 -0
- data/samples/helloworld.hvm +14 -0
- metadata +64 -0
data/README.textile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
h1. Ruby Hack VM
|
2
|
+
|
3
|
+
h2. Information
|
4
|
+
|
5
|
+
h3. The Hack VM is a tiny, trivial, virtual machine.
|
6
|
+
|
7
|
+
Its purpose is to be used as a simple execution engine that can run very simple programs.
|
8
|
+
|
9
|
+
more Information on <a href="http://www.hacker.org/hvm/">http://www.hacker.org/hvm/</a>
|
10
|
+
|
11
|
+
The virtual machine executes a single program and terminates, either by reaching the end of the code, an '!' instruction, or because an exception was thrown during execution.
|
12
|
+
A program is represented by a string of single-character instructions.
|
13
|
+
The virtual machine starts with the first instruction, executes it, and moves on to the next instruction, etc...
|
14
|
+
|
15
|
+
The index of the current instruction is called the program counter.
|
16
|
+
The execution model is simple: the virtual machine has an operand stack, a memory buffer, and a call stack.
|
17
|
+
|
18
|
+
Each item on the operand stack or in memory is a cell that can hold a signed integer.
|
19
|
+
For implementation reasons, those integers are currently limited to 32 bits, but do not count on it, they could be large in future implementations.
|
20
|
+
The call stack is used to push the value of the program counter when jumping to a routine from which we want to return.
|
21
|
+
|
22
|
+
h2. Instructions
|
23
|
+
|
24
|
+
<pre>
|
25
|
+
Instruction Description
|
26
|
+
' ' Do Nothing
|
27
|
+
'p' Print S0 interpreted as an integer
|
28
|
+
'P' Print S0 interpreted as an ASCII character (only the least significant 7 bits of the value are used)
|
29
|
+
'0' Push the value 0 on the stack
|
30
|
+
'1' Push the value 1 on the stack
|
31
|
+
'2' Push the value 2 on the stack
|
32
|
+
'3' Push the value 3 on the stack
|
33
|
+
'4' Push the value 4 on the stack
|
34
|
+
'5' Push the value 5 on the stack
|
35
|
+
'6' Push the value 6 on the stack
|
36
|
+
'7' Push the value 7 on the stack
|
37
|
+
'8' Push the value 8 on the stack
|
38
|
+
'9' Push the value 9 on the stack
|
39
|
+
'+' Push S1+S0
|
40
|
+
'-' Push S1-S0
|
41
|
+
'*' Push S1*S0
|
42
|
+
'/' Push S1/S0
|
43
|
+
':' Push -1 if S1<S0, 0 if S1=S0, or 1 S1>S0
|
44
|
+
'g' Add S0 to the program counter
|
45
|
+
'?' Add S0 to the program counter if S1 is 0
|
46
|
+
'c' Push the program counter on the call stack and set the program counter to S0
|
47
|
+
'$' Set the program counter to the value pop'ed from the call stack
|
48
|
+
'<' Push the value of memory cell S0
|
49
|
+
'>' Store S1 into memory cell S0
|
50
|
+
'^' Push a copy of S<S0+1> (ex: 0^ duplicates S0)
|
51
|
+
'v' Remove S<S0+1> from the stack and push it on top (ex: 1v swaps S0 and S1)
|
52
|
+
'd' Drop S0
|
53
|
+
'!' Terminate the program
|
54
|
+
</pre>
|
55
|
+
|
56
|
+
|
57
|
+
h2. Examples
|
58
|
+
|
59
|
+
Input: "78*p" Output: 56
|
60
|
+
|
61
|
+
Input: "123451^2v5:4?9p2g8pppppp" Output: 945321
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'echoe'
|
3
|
+
|
4
|
+
Echoe.new('ruby-hackvm', '0.1.0') do |p|
|
5
|
+
p.description = "a virtual machine for hackers."
|
6
|
+
p.url = "http://github.com/BadBoy/ruby-hackvm"
|
7
|
+
p.author = "BadBoy_"
|
8
|
+
p.email = ""
|
9
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
10
|
+
p.development_dependencies = []
|
11
|
+
end
|
12
|
+
|
data/bin/hackvm.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# http://www.hacker.org/hvm/
|
4
|
+
|
5
|
+
class HackVM
|
6
|
+
class StackUnderFlow < Exception; end
|
7
|
+
class IntegerOverflow < Exception; end
|
8
|
+
class InitMemoryToBig < Exception; end
|
9
|
+
class OutOfStackException < Exception; end
|
10
|
+
class OutOfCodeBoundsException < Exception; end
|
11
|
+
class TooManyCyclesException < Exception; end
|
12
|
+
class WrongOpCode < Exception; end
|
13
|
+
class MemoryReadAccessViolation < Exception; end
|
14
|
+
class MemoryWriteAccessViolation < Exception; end
|
15
|
+
|
16
|
+
MAX_CYCLES=10000
|
17
|
+
MIN_INT=-(1<<63)
|
18
|
+
MAX_INT=(1<<63)-1
|
19
|
+
|
20
|
+
OPS = {
|
21
|
+
' ' => lambda {},
|
22
|
+
"\n" => lambda {},
|
23
|
+
'p' => :printi,
|
24
|
+
'P' => :printc,
|
25
|
+
'0' => [:push, 0],
|
26
|
+
'1' => [:push, 1],
|
27
|
+
'2' => [:push, 2],
|
28
|
+
'3' => [:push, 3],
|
29
|
+
'4' => [:push, 4],
|
30
|
+
'5' => [:push, 5],
|
31
|
+
'6' => [:push, 6],
|
32
|
+
'7' => [:push, 7],
|
33
|
+
'8' => [:push, 8],
|
34
|
+
'9' => [:push, 9],
|
35
|
+
'+' => :add,
|
36
|
+
'-' => :sub,
|
37
|
+
'*' => :mul,
|
38
|
+
'/' => :div,
|
39
|
+
':' => :cmp,
|
40
|
+
'g' => :goto,
|
41
|
+
'?' => :goto_if_zero,
|
42
|
+
'c' => :call,
|
43
|
+
'$' => :doreturn,
|
44
|
+
'<' => :peek,
|
45
|
+
'>' => :poke,
|
46
|
+
'^' => :pick,
|
47
|
+
'v' => :roll,
|
48
|
+
'd' => :drop,
|
49
|
+
'!' => :exit
|
50
|
+
}
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@memory = [0]*16384
|
54
|
+
@callstack = []
|
55
|
+
@operandstack = []
|
56
|
+
@programcounter = 0
|
57
|
+
@cyclecounter = 0
|
58
|
+
end
|
59
|
+
|
60
|
+
def init(memory)
|
61
|
+
if memory.size > 16384
|
62
|
+
raise InitMemoryToBig
|
63
|
+
end
|
64
|
+
memory.each_with_index do |e, i|
|
65
|
+
@memory[i] = e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute(code, trace)
|
70
|
+
codel = code.size
|
71
|
+
@codel = codel
|
72
|
+
while @programcounter != codel
|
73
|
+
op_code = code[@programcounter, 1]
|
74
|
+
STDERR.print('@'+@programcounter.to_s+' '+op_code) if trace
|
75
|
+
@programcounter += 1
|
76
|
+
@cyclecounter += 1
|
77
|
+
if @cyclecounter > MAX_CYCLES
|
78
|
+
raise TooManyCyclesException, 'too many cycles'
|
79
|
+
end
|
80
|
+
if OPS[op_code]
|
81
|
+
if OPS[op_code].kind_of? Symbol
|
82
|
+
method(OPS[op_code]).call
|
83
|
+
elsif OPS[op_code].kind_of? Proc
|
84
|
+
OPS[op_code].call
|
85
|
+
elsif OPS[op_code].kind_of? Array
|
86
|
+
m = method(OPS[op_code][0])
|
87
|
+
m.call(OPS[op_code][1])
|
88
|
+
else
|
89
|
+
raise WrongOpCode, "wrong op-code '#{op_code}'"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
raise WrongOpCode, "wrong op-code '#{op_code}'"
|
93
|
+
end
|
94
|
+
if @programcounter < 0 || @programcounter > codel
|
95
|
+
raise OutOfCodeBoundsException, 'out of code bounds'
|
96
|
+
end
|
97
|
+
STDERR.puts(' ['+@operandstack.join(',')+']') if trace
|
98
|
+
end
|
99
|
+
puts
|
100
|
+
|
101
|
+
rescue Exception => e
|
102
|
+
STDERR.puts("!ERROR: exception while executing I=#{op_code} PC=#{@programcounter-1} STACK_SIZE=#{@operandstack.size}")
|
103
|
+
puts e
|
104
|
+
Kernel.exit(1)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def push(v)
|
109
|
+
if v < MIN_INT || v > MAX_INT
|
110
|
+
raise IntegerOverflow
|
111
|
+
end
|
112
|
+
@operandstack.push(v)
|
113
|
+
end
|
114
|
+
|
115
|
+
def pop
|
116
|
+
if @operandstack.size == 0
|
117
|
+
raise StackUnderFlow
|
118
|
+
end
|
119
|
+
@operandstack.pop
|
120
|
+
end
|
121
|
+
|
122
|
+
def printc
|
123
|
+
print( pop.chr )
|
124
|
+
end
|
125
|
+
|
126
|
+
def printi
|
127
|
+
print pop
|
128
|
+
end
|
129
|
+
|
130
|
+
def add
|
131
|
+
push(pop+pop)
|
132
|
+
end
|
133
|
+
|
134
|
+
def sub
|
135
|
+
a = pop
|
136
|
+
b = pop
|
137
|
+
push(b-a)
|
138
|
+
end
|
139
|
+
|
140
|
+
def mul
|
141
|
+
push(pop*pop)
|
142
|
+
end
|
143
|
+
|
144
|
+
def div
|
145
|
+
a = pop
|
146
|
+
b = pop
|
147
|
+
push(b/a)
|
148
|
+
end
|
149
|
+
|
150
|
+
def cmp
|
151
|
+
a = pop
|
152
|
+
b = pop
|
153
|
+
push( b<=>a )
|
154
|
+
end
|
155
|
+
|
156
|
+
def goto
|
157
|
+
@programcounter += pop
|
158
|
+
end
|
159
|
+
|
160
|
+
def goto_if_zero
|
161
|
+
offset = pop
|
162
|
+
@programcounter += offset if pop == 0
|
163
|
+
end
|
164
|
+
|
165
|
+
def call
|
166
|
+
@callstack.push(@programcounter)
|
167
|
+
@programcounter = pop
|
168
|
+
end
|
169
|
+
|
170
|
+
def doreturn
|
171
|
+
@programcounter = @callstack.pop
|
172
|
+
end
|
173
|
+
|
174
|
+
def peek
|
175
|
+
addr = pop
|
176
|
+
if addr < 0 || addr >= @memory.size
|
177
|
+
raise MemoryReadAccessViolation, 'memory read access violation @'+addr.to_s
|
178
|
+
end
|
179
|
+
push(@memory[addr])
|
180
|
+
end
|
181
|
+
|
182
|
+
def poke
|
183
|
+
addr = pop
|
184
|
+
if addr < 0 || addr >= @memory.size
|
185
|
+
raise MemoryWriteAccessViolation, 'memory write access violation @'+addr.to_s
|
186
|
+
end
|
187
|
+
@memory[addr] = pop
|
188
|
+
end
|
189
|
+
|
190
|
+
def pick
|
191
|
+
where = pop
|
192
|
+
if where < 0 || where >= @operandstack.size
|
193
|
+
raise OutOfStackException, 'out of stack @'+where.to_s
|
194
|
+
end
|
195
|
+
push(@operandstack[-1-where])
|
196
|
+
end
|
197
|
+
|
198
|
+
def roll
|
199
|
+
where = pop
|
200
|
+
if where < 0 || where >= @operandstack.size
|
201
|
+
raise OutOfStackException, 'out of stack @'+where.to_s
|
202
|
+
end
|
203
|
+
v = @perandstack[-1-where]
|
204
|
+
@perandStack.delete_at(-1-where)
|
205
|
+
push(v)
|
206
|
+
end
|
207
|
+
|
208
|
+
def drop
|
209
|
+
pop
|
210
|
+
end
|
211
|
+
|
212
|
+
def exit
|
213
|
+
@programcounter = @codel
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
require 'optparse'
|
218
|
+
|
219
|
+
trace = false
|
220
|
+
memory = []
|
221
|
+
ARGV.options do |o|
|
222
|
+
o.banner = "hackvm.rb [--init <init-mem-filename>] [--trace] <code-filename>\n"
|
223
|
+
o.banner += "The format for the initial memory file is: cell0,cell1,...\n"
|
224
|
+
o.banner += "More on http://www.hacker.org/hvm/"
|
225
|
+
o.separator('')
|
226
|
+
|
227
|
+
o.on('-t', '--trace', 'trace commands') do |t|
|
228
|
+
trace = t
|
229
|
+
end
|
230
|
+
|
231
|
+
o.on('-i', '--init FILE', 'init-mem-filename') do |i|
|
232
|
+
f = File.read(i)
|
233
|
+
memory = f.chomp.split(',').map{|e|Integer(e)}
|
234
|
+
end
|
235
|
+
end
|
236
|
+
begin
|
237
|
+
ARGV.parse!
|
238
|
+
rescue OptionParser::InvalidOption => e
|
239
|
+
STDERR.puts "!ERROR: #{e}"
|
240
|
+
exit 1
|
241
|
+
end
|
242
|
+
|
243
|
+
raise "not enough arguments" if ARGV.size < 1
|
244
|
+
hvm = HackVM.new
|
245
|
+
hvm.execute(File.read(ARGV[0]), trace)
|
data/ruby-hackvm.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{ruby-hackvm}
|
3
|
+
s.version = "0.1.0"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["BadBoy_"]
|
7
|
+
s.date = %q{2008-11-13}
|
8
|
+
s.default_executable = %q{hackvm.rb}
|
9
|
+
s.description = %q{a virtual machine for hackers.}
|
10
|
+
s.email = %q{}
|
11
|
+
s.executables = ["hackvm.rb"]
|
12
|
+
s.extra_rdoc_files = ["bin/hackvm.rb", "README.textile"]
|
13
|
+
s.files = ["samples/helloworld.hvm", "Rakefile", "ruby-hackvm.gemspec", "bin/hackvm.rb", "Manifest", "README.textile"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/BadBoy/ruby-hackvm}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ruby-hackvm", "--main", "README.textile"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{ruby-hackvm}
|
19
|
+
s.rubygems_version = %q{1.2.0}
|
20
|
+
s.summary = %q{a virtual machine for hackers.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if current_version >= 3 then
|
27
|
+
else
|
28
|
+
end
|
29
|
+
else
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: BadBoy-ruby-hackvm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- BadBoy_
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-13 00:00:00 -08:00
|
13
|
+
default_executable: hackvm.rb
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: a virtual machine for hackers.
|
17
|
+
email: ""
|
18
|
+
executables:
|
19
|
+
- hackvm.rb
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- bin/hackvm.rb
|
24
|
+
- README.textile
|
25
|
+
files:
|
26
|
+
- samples/helloworld.hvm
|
27
|
+
- Rakefile
|
28
|
+
- ruby-hackvm.gemspec
|
29
|
+
- bin/hackvm.rb
|
30
|
+
- Manifest
|
31
|
+
- README.textile
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/BadBoy/ruby-hackvm
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --line-numbers
|
37
|
+
- --inline-source
|
38
|
+
- --title
|
39
|
+
- Ruby-hackvm
|
40
|
+
- --main
|
41
|
+
- README.textile
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "1.2"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project: ruby-hackvm
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: a virtual machine for hackers.
|
63
|
+
test_files: []
|
64
|
+
|