Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit c86c56b4b94b97c70cc527e17cb3cd0d6d393480 0 parents
@txus authored
1  .gitignore
@@ -0,0 +1 @@
+.rbx
34 Readme.md
@@ -0,0 +1,34 @@
+# microvm
+
+Sunday afternoon. One hour. Less than 150LOC (< 4kb).
+
+MicroVM is a stack-based micro virtual machine written in Ruby, running its own
+micro bytecode format called MC.
+
+![MC Bytecode](http://f.cl.ly/items/290t2S2H1j0F233R123r/mc.png)
+
+## Install
+
+ $ wget https://raw.github.com/txus/microvm/master/microvm
+ $ chmod +x microvm
+ $ ./microvm some_file.mc
+
+You can try some example MC files in this very repo.
+
+## Why?
+
+It's a learning example: it's a really simple implementation of a VM that
+works, so it's easy to get a general grasp about how things are structured.
+
+## Features
+
+* Compact bytecode format (although it could be more compact)
+* Method calls
+* Types: only String and Fixnum for now.
+* Sort of runtime type checking
+
+## Who's this
+
+This was made by [Josep M. Bach (Txus)](http://txustice.me) under the MIT
+license. I'm [@txustice](http://twitter.com/txustice) on twitter (where you
+should probably follow me!).
6 add.mc
@@ -0,0 +1,6 @@
+_main_
+:42,"hello",9999
+0.0
+0.2
+3
+8
13 functions.mc
@@ -0,0 +1,13 @@
+_main_
+:42,"add",9999
+0.0
+0.2
+0.1
+7.2
+8
+_add_
+:"add"
+1.0
+1.1
+3
+8
BIN  mc.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 microvm
@@ -0,0 +1,150 @@
+#!/usr/bin/env ruby -w
+
+module Micro; VERSION = '0.0.1'
+ class CodeLoader
+ OPCODES = [
+ :push_literal,
+ :push_local,
+ :pop,
+
+ :add,
+ :sub,
+ :mul,
+ :div,
+
+ :call,
+
+ :ret
+ ]
+
+ def self.load_file(filename)
+ functions = []
+
+ file = File.open(filename)
+ while !file.eof? && line = file.readline
+ if line =~/^_/
+ functions.push VM::Function.new(line.chomp[1..-2].to_sym)
+ functions.last.code = []
+ functions.last.literals = []
+ elsif line =~ /^:/
+ functions.last.literals = line[1..-1].split(',').map do |literal|
+ if literal =~ /\"/
+ literal.chomp[1..-2]
+ else
+ literal.to_i
+ end
+ end
+ else
+ opcode, operand = line.split('.').map(&:to_i)
+ functions.last.code.push VM::Instruction.new(OPCODES[opcode], operand)
+ end
+ end
+
+ return functions
+ end
+ end
+
+ class VM
+ Instruction = Struct.new(:opcode, :operand)
+ Function = Struct.new(:name, :code, :literals)
+
+ MAX_STACK_DEPTH = 128
+
+ def load(filename)
+ @functions = CodeLoader.load_file(filename)
+ self
+ end
+
+ def run
+ main = find_function(:main)
+ @frame = CallFrame.new(self, main)
+ p @frame.execute
+ end
+
+ def find_function(name)
+ @functions.detect { |m| m.name == name.to_sym }
+ end
+
+ class CallFrame
+ def initialize(state, function, locals = [], depth=0)
+ @state = state
+
+ @code = function.code
+ @literals = function.literals
+ @locals = locals
+ @depth = depth
+
+ @stack = []
+ end
+
+ def execute
+ @code.each do |instruction|
+ case instruction.opcode
+ when :push_literal
+ @stack.push @literals[instruction.operand]
+ when :push_local
+ @stack.push @locals[instruction.operand]
+ when :pop
+ @stack.pop
+ when :add
+ op1 = @stack.pop
+ op2 = @stack.pop
+ check_type(Fixnum, op1, op2)
+ @stack.push op1 + op2
+ when :sub
+ op1 = @stack.pop
+ op2 = @stack.pop
+ check_type(Fixnum, op1, op2)
+ @stack.push op1 - op2
+ when :mul
+ op1 = @stack.pop
+ op2 = @stack.pop
+ check_type(Fixnum, op1, op2)
+ @stack.push op1 * op2
+ when :div
+ op1 = @stack.pop
+ op2 = @stack.pop
+ check_type(Fixnum, op1, op2)
+ raise ZeroDivisionError if op2 == 0
+ @stack.push op1 / op2
+ when :call
+ argsnum = instruction.operand
+ function_name = @stack.pop
+ function = @state.find_function(function_name)
+
+ locals = []
+ argsnum.times { locals.push @stack.pop }
+
+ if !function
+ raise NameError, "Can't find function `#{function_name}'"
+ end
+
+ if @depth > MAX_STACK_DEPTH
+ raise "Call stack level too deep"
+ end
+
+ call_frame = CallFrame.new(@state, function, locals, @depth + 1)
+ @stack.push call_frame.execute
+ when :ret
+ return @stack.pop
+ end
+ end
+ raise "Broken bytecode: lack of RET opcode."
+ end
+
+ def check_type(type, *operands)
+ operands.each do |operand|
+ raise TypeError, "#{operand.inspect} is not a #{type}" unless operand.is_a?(type)
+ end
+ end
+ end
+ end
+end
+
+unless ARGV.first
+ puts "MicroVM #{Micro::VERSION}\n============="
+ puts "\tUsage: microvm my_file.mc"
+ exit(1)
+end
+
+Micro::VM.new.load(ARGV.first).run
14 stack_limit.mc
@@ -0,0 +1,14 @@
+_main_
+:42,"add",9999
+0.0
+0.2
+0.1
+7.2
+8
+_add_
+:"add"
+1.0
+1.1
+0.0
+7.2
+8
Please sign in to comment.
Something went wrong with that request. Please try again.