microruby 0.0.1
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/bin/microruby +17 -0
- data/lib/microruby.rb +26 -0
- data/lib/microruby/bytecode.rb +156 -0
- data/lib/microruby/instruction_set.rb +22 -0
- metadata +57 -0
data/bin/microruby
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'microruby'
|
3
|
+
|
4
|
+
cmd = ARGV.shift
|
5
|
+
abort "Missing command" if cmd.nil?
|
6
|
+
|
7
|
+
case cmd.downcase.to_sym
|
8
|
+
when :compile
|
9
|
+
path = ARGV.shift
|
10
|
+
abort "Missing file path" if path.nil?
|
11
|
+
MicroRuby.compile_file(path)
|
12
|
+
else
|
13
|
+
abort "Unknown command: #{cmd}"
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
|
data/lib/microruby.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "microruby/bytecode"
|
2
|
+
|
3
|
+
class MicroRuby
|
4
|
+
|
5
|
+
def self.compile_file(path)
|
6
|
+
raise "File not found: #{path}" unless File.file?(path)
|
7
|
+
|
8
|
+
dir = File.dirname(path)
|
9
|
+
filename = File.basename(path, ".rb")
|
10
|
+
|
11
|
+
bc = compile File.read(path)
|
12
|
+
|
13
|
+
target_path = "#{dir}/#{filename}.mrbc"
|
14
|
+
File.open(target_path, "w") do |f|
|
15
|
+
f.write(bc)
|
16
|
+
puts "Compiled to #{target_path}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.compile(code)
|
22
|
+
seq = RubyVM::InstructionSequence.compile(code).to_a
|
23
|
+
Bytecode.encode(seq).to_bin
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
require_relative "instruction_set"
|
3
|
+
|
4
|
+
class Bytecode
|
5
|
+
|
6
|
+
TYPES = {
|
7
|
+
:null => 0,
|
8
|
+
:int8 => 1,
|
9
|
+
:int16 => 2,
|
10
|
+
:int32 => 3,
|
11
|
+
:uint8 => 4,
|
12
|
+
:uint16 => 5,
|
13
|
+
:uint32 => 6,
|
14
|
+
:float => 7,
|
15
|
+
:double => 8,
|
16
|
+
:bool => 9,
|
17
|
+
:string => 10,
|
18
|
+
:array => 11
|
19
|
+
}
|
20
|
+
|
21
|
+
attr_accessor :arg_size
|
22
|
+
attr_accessor :local_size
|
23
|
+
attr_accessor :stack_max
|
24
|
+
attr_accessor :inst_seq
|
25
|
+
attr_accessor :bytes
|
26
|
+
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@bytes = []
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def self.encode(sequence)
|
34
|
+
bc = Bytecode.new
|
35
|
+
bc.arg_size = sequence[4][:arg_size]
|
36
|
+
bc.local_size = sequence[4][:local_size]
|
37
|
+
bc.stack_max = sequence[4][:stack_max]
|
38
|
+
# TODO: preserve line numbers (optionally)
|
39
|
+
instructions = sequence[13].find_all {|i| i.is_a?(Array)}
|
40
|
+
instructions.each do |i|
|
41
|
+
bc.bytes += encode_instruction(i)
|
42
|
+
end
|
43
|
+
bc
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def to_bin
|
48
|
+
self.bytes.pack("C*")
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
|
55
|
+
def self.encode_instruction(instruction)
|
56
|
+
|
57
|
+
inst_bytes = []
|
58
|
+
opcode = instruction[0]
|
59
|
+
|
60
|
+
inst_def = INSTRUCTIONS[opcode]
|
61
|
+
|
62
|
+
if inst_def
|
63
|
+
|
64
|
+
inst_bytes << inst_def[0]
|
65
|
+
|
66
|
+
if [:putobject, :putstring].include?(opcode)
|
67
|
+
# literal object - requires special encoding
|
68
|
+
val = instruction[1]
|
69
|
+
inst_bytes += encode_object(val)
|
70
|
+
elsif [:trace, :setlocal, :getlocal, :opt_plus, :opt_minus, :opt_mult, :opt_div].include?(opcode)
|
71
|
+
# simple operand opcodes
|
72
|
+
inst_bytes += instruction[1..-1]
|
73
|
+
elsif [:nop, :dup, :leave].include?(opcode)
|
74
|
+
# zero operand opcodes
|
75
|
+
end
|
76
|
+
|
77
|
+
else
|
78
|
+
raise "Unexpected opcode (#{opcode})"
|
79
|
+
end
|
80
|
+
|
81
|
+
inst_bytes
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def self.encode_object(val)
|
87
|
+
val_bytes = []
|
88
|
+
|
89
|
+
case val
|
90
|
+
when NilClass
|
91
|
+
val_bytes << TYPES[:null]
|
92
|
+
# empty value
|
93
|
+
|
94
|
+
when FalseClass
|
95
|
+
val_bytes << TYPES[:bool]
|
96
|
+
val_bytes << 0
|
97
|
+
when TrueClass
|
98
|
+
val_bytes << TYPES[:bool]
|
99
|
+
val_bytes << 1
|
100
|
+
|
101
|
+
when Fixnum
|
102
|
+
if val >= 0
|
103
|
+
# unsigned
|
104
|
+
if val < 2**8
|
105
|
+
val_bytes << TYPES[:uint8]
|
106
|
+
val_bytes += [val].pack("C").unpack("C*")
|
107
|
+
elsif val < 2**16
|
108
|
+
val_bytes << TYPES[:uint16]
|
109
|
+
val_bytes += [val].pack("S>").unpack("C*")
|
110
|
+
elsif val < 2**32
|
111
|
+
val_bytes << TYPES[:uint32]
|
112
|
+
val_bytes += [val].pack("L>").unpack("C*")
|
113
|
+
else
|
114
|
+
raise "64 bit integers not supported... yet."
|
115
|
+
end
|
116
|
+
else
|
117
|
+
# signed
|
118
|
+
if val.abs < 2**7
|
119
|
+
val_bytes << TYPES[:int8]
|
120
|
+
val_bytes += [val].pack("c").unpack("C*")
|
121
|
+
elsif val.abs < 2**15
|
122
|
+
val_bytes << TYPES[:int16]
|
123
|
+
val_bytes += [val].pack("s>").unpack("C*")
|
124
|
+
elsif val.abs < 2**31
|
125
|
+
val_bytes << TYPES[:int32]
|
126
|
+
val_bytes += [val].pack("l>").unpack("C*")
|
127
|
+
else
|
128
|
+
raise "64 bit integers not supported... yet."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
when Float
|
133
|
+
# TODO: add single-precision support
|
134
|
+
val_bytes << TYPES[:double]
|
135
|
+
val_bytes += [val].pack("G").unpack("C*")
|
136
|
+
|
137
|
+
when String
|
138
|
+
# TODO: support for strings larger than 255 bytes
|
139
|
+
val_bytes << TYPES[:string]
|
140
|
+
val_bytes << val.length
|
141
|
+
val_bytes += val.bytes.to_a
|
142
|
+
|
143
|
+
when Array
|
144
|
+
# TODO: support for arrays larger than 255 bytes
|
145
|
+
val_bytes << TYPES[:array]
|
146
|
+
val_bytes << val.length
|
147
|
+
val.each do |v|
|
148
|
+
val_bytes += encode_object(v)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
val_bytes
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
class Bytecode
|
3
|
+
|
4
|
+
INSTRUCTIONS = {
|
5
|
+
# opcode key => [opcode value, operand count]
|
6
|
+
:nop => [0, 0],
|
7
|
+
:getlocal => [1, 1],
|
8
|
+
:setlocal => [2, 1],
|
9
|
+
:putobject => [17, 1],
|
10
|
+
:putstring => [18, 1],
|
11
|
+
:dup => [31, 0],
|
12
|
+
:trace => [43, 1],
|
13
|
+
:leave => [48, 0],
|
14
|
+
:opt_plus => [59, 1],
|
15
|
+
:opt_minus => [60, 1],
|
16
|
+
:opt_mult => [61, 1],
|
17
|
+
:opt_div => [62, 1],
|
18
|
+
:newarray => [22, 1],
|
19
|
+
:expandarray => [24, 1]
|
20
|
+
}
|
21
|
+
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: microruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Lyons
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-12-03 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: MicroRuby compiles Ruby to compact bytecode for execution on embedded microcontrollers.
|
17
|
+
email: mark@microruby.com
|
18
|
+
executables:
|
19
|
+
- microruby
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/microruby/bytecode.rb
|
26
|
+
- lib/microruby/instruction_set.rb
|
27
|
+
- lib/microruby.rb
|
28
|
+
- bin/microruby
|
29
|
+
homepage: http://microruby.com
|
30
|
+
licenses: []
|
31
|
+
|
32
|
+
post_install_message: "Warning: This is an alpha release! Opcode support is limited."
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.9.2
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.24
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: MicroRuby compiler
|
56
|
+
test_files: []
|
57
|
+
|