-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathbytecode_generator.rb
More file actions
156 lines (132 loc) · 4 KB
/
bytecode_generator.rb
File metadata and controls
156 lines (132 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
## Our humble bytecode format
#
# Stack
# Opcode Operands before after
CALL, # index, argc [receiver, args...] [returned]
PUSH_NUMBER, # index [] [number]
PUSH_STRING, # index [] [string]
PUSH_SELF, # [] [self]
PUSH_NIL, # [] [nil]
PUSH_BOOL, # 1=t, 0=f [] [true or false]
GET_LOCAL, # index [] [value]
SET_LOCAL, # index [value] []
JUMP_UNLESS, # offset [condition] []
JUMP, # offset [] []
ADD, # [a, b] [result]
RETURN = * # [] []
(0..11)
class BytecodeGenerator
def initialize
@literals = []
@locals = []
@instructions = []
end
def compile_all(nodes)
nodes.each do |node|
node.compile(self)
end
end
def number_literal(value)
# literal_index(1) => 0
# literal_index("method_name") => 1
# literal_index(1) => 0
emit PUSH_NUMBER, literal_index(value)
end
def string_literal(value)
emit PUSH_STRING, literal_index(value)
end
def true_literal
emit PUSH_BOOL, 1
end
def false_literal
emit PUSH_BOOL, 0
end
def nil_literal
emit PUSH_NIL
end
def set_local(name, value_node)
# a = true
value_node.compile(self)
emit SET_LOCAL, local_index(name) # a => 0
end
def get_local(name)
emit GET_LOCAL, local_index(name)
end
def call(receiver_node, method, argument_nodes)
# object.method(...)
if receiver_node
receiver_node.compile(self)
# print(...) => self.print(...)
else
emit PUSH_SELF
end
argument_nodes.each do |node|
node.compile(self)
end
# Static typing example
# int c;
if method == "+"
# emit ADD_TWO_INTS
emit ADD
return
end
emit CALL, literal_index(method), argument_nodes.size
end
def if(condition_node, body_node, else_body_node)
condition_node.compile(self)
emit JUMP_UNLESS, 0 # <= offset_index of this byte
offset_index = @instructions.size - 1
body_node.compile(self)
emit JUMP, 0 if else_body_node
# Update w/ number of bytes generated by the body_node
@instructions[offset_index] = @instructions.size - 1 - offset_index
if else_body_node
offset_index = @instructions.size - 1
else_body_node.compile(self)
@instructions[offset_index] = @instructions.size - 1 - offset_index
end
end
# Returns the index of the local in the local table
def local_index(name)
@locals << name unless @locals.include?(name)
@locals.index(name)
end
# Returns the index of the literal in the literal table
def literal_index(literal)
@literals << literal unless @literals.include?(literal)
@literals.index(literal)
end
# Emit the instruction
# Eg.:
# emit CALL, 1, 10
# will generate the bytecode
# 0, 1, 10
def emit(opcode, *operands)
@instructions << opcode
@instructions.concat operands
end
# Updates the last generated byte to the size (in bytes) of the generated
# instructions of a passed blocked.
#
# Eg.:
# emit JUMP, 0 <---------------------------------v
# set_previous_byte_to_size_of do # Will update 0 to 2.
# emit PUSH_NUMBER, 1 # size = 2 bytes <-----------^
# end
def set_previous_byte_to_size_of
# Get the position of the previous byte in @instructions.
byte_position = @instructions.size - 1
# Execute the passed block (everything between do ... end).
yield
# Update the previous byte to the number of bytes generated by yield.
@instructions[byte_position] = @instructions.size - 1 - byte_position
end
def assemble
emit RETURN
{
:literals => @literals,
:locals => @locals,
:instructions => @instructions
}
end
end