|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | | -require "forwardable" |
4 | | - |
5 | 3 | module SyntaxTree |
6 | 4 | # This module provides an object representation of the YARV bytecode. |
7 | 5 | module YARV |
8 | | - class VM |
9 | | - class Jump |
10 | | - attr_reader :name |
11 | | - |
12 | | - def initialize(name) |
13 | | - @name = name |
14 | | - end |
15 | | - end |
16 | | - |
17 | | - class Leave |
18 | | - attr_reader :value |
19 | | - |
20 | | - def initialize(value) |
21 | | - @value = value |
22 | | - end |
23 | | - end |
24 | | - |
25 | | - class Frame |
26 | | - attr_reader :iseq, :parent, :stack_index, :_self, :nesting, :svars |
27 | | - |
28 | | - def initialize(iseq, parent, stack_index, _self, nesting) |
29 | | - @iseq = iseq |
30 | | - @parent = parent |
31 | | - @stack_index = stack_index |
32 | | - @_self = _self |
33 | | - @nesting = nesting |
34 | | - @svars = {} |
35 | | - end |
36 | | - end |
37 | | - |
38 | | - class TopFrame < Frame |
39 | | - def initialize(iseq) |
40 | | - super(iseq, nil, 0, TOPLEVEL_BINDING.eval("self"), [Object]) |
41 | | - end |
42 | | - end |
43 | | - |
44 | | - class BlockFrame < Frame |
45 | | - def initialize(iseq, parent, stack_index) |
46 | | - super(iseq, parent, stack_index, parent._self, parent.nesting) |
47 | | - end |
48 | | - end |
49 | | - |
50 | | - class MethodFrame < Frame |
51 | | - attr_reader :name, :block |
52 | | - |
53 | | - def initialize(iseq, parent, stack_index, _self, name, block) |
54 | | - super(iseq, parent, stack_index, _self, parent.nesting) |
55 | | - @name = name |
56 | | - @block = block |
57 | | - end |
58 | | - end |
59 | | - |
60 | | - class ClassFrame < Frame |
61 | | - def initialize(iseq, parent, stack_index, _self) |
62 | | - super(iseq, parent, stack_index, _self, parent.nesting + [_self]) |
63 | | - end |
64 | | - end |
65 | | - |
66 | | - class FrozenCore |
67 | | - define_method("core#hash_merge_kwd") { |left, right| left.merge(right) } |
68 | | - |
69 | | - define_method("core#hash_merge_ptr") do |hash, *values| |
70 | | - hash.merge(values.each_slice(2).to_h) |
71 | | - end |
72 | | - |
73 | | - define_method("core#set_method_alias") do |clazz, new_name, old_name| |
74 | | - clazz.alias_method(new_name, old_name) |
75 | | - end |
76 | | - |
77 | | - define_method("core#set_variable_alias") do |new_name, old_name| |
78 | | - # Using eval here since there isn't a reflection API to be able to |
79 | | - # alias global variables. |
80 | | - eval("alias #{new_name} #{old_name}", binding, __FILE__, __LINE__) |
81 | | - end |
82 | | - |
83 | | - define_method("core#set_postexe") { |&block| END { block.call } } |
84 | | - |
85 | | - define_method("core#undef_method") do |clazz, name| |
86 | | - clazz.undef_method(name) |
87 | | - end |
88 | | - end |
89 | | - |
90 | | - FROZEN_CORE = FrozenCore.new.freeze |
91 | | - |
92 | | - extend Forwardable |
93 | | - |
94 | | - attr_reader :stack |
95 | | - def_delegators :stack, :push, :pop |
96 | | - |
97 | | - attr_reader :frame |
98 | | - def_delegators :frame, :_self |
99 | | - |
100 | | - def initialize |
101 | | - @stack = [] |
102 | | - @frame = nil |
103 | | - end |
104 | | - |
105 | | - ########################################################################## |
106 | | - # Helper methods for frames |
107 | | - ########################################################################## |
108 | | - |
109 | | - def run_frame(frame) |
110 | | - # First, set the current frame to the given value. |
111 | | - @frame = frame |
112 | | - |
113 | | - # Next, set up the local table for the frame. This is actually incorrect |
114 | | - # as it could use the values already on the stack, but for now we're |
115 | | - # just doing this for simplicity. |
116 | | - frame.iseq.local_table.size.times { push(nil) } |
117 | | - |
118 | | - # Yield so that some frame-specific setup can be done. |
119 | | - yield if block_given? |
120 | | - |
121 | | - # This hash is going to hold a mapping of label names to their |
122 | | - # respective indices in our instruction list. |
123 | | - labels = {} |
124 | | - |
125 | | - # This array is going to hold our instructions. |
126 | | - insns = [] |
127 | | - |
128 | | - # Here we're going to preprocess the instruction list from the |
129 | | - # instruction sequence to set up the labels hash and the insns array. |
130 | | - frame.iseq.insns.each do |insn| |
131 | | - case insn |
132 | | - when Integer, Symbol |
133 | | - # skip |
134 | | - when InstructionSequence::Label |
135 | | - labels[insn.name] = insns.length |
136 | | - else |
137 | | - insns << insn |
138 | | - end |
139 | | - end |
140 | | - |
141 | | - # Finally we can execute the instructions one at a time. If they return |
142 | | - # jumps or leaves we will handle those appropriately. |
143 | | - pc = 0 |
144 | | - while pc < insns.length |
145 | | - insn = insns[pc] |
146 | | - pc += 1 |
147 | | - |
148 | | - case (result = insn.call(self)) |
149 | | - when Jump |
150 | | - pc = labels[result.name] |
151 | | - when Leave |
152 | | - return result.value |
153 | | - end |
154 | | - end |
155 | | - ensure |
156 | | - @stack = stack[0...frame.stack_index] |
157 | | - @frame = frame.parent |
158 | | - end |
159 | | - |
160 | | - def run_top_frame(iseq) |
161 | | - run_frame(TopFrame.new(iseq)) |
162 | | - end |
163 | | - |
164 | | - def run_block_frame(iseq, *args, &block) |
165 | | - run_frame(BlockFrame.new(iseq, frame, stack.length)) do |
166 | | - locals = [*args, block] |
167 | | - iseq.local_table.size.times do |index| |
168 | | - local_set(index, 0, locals.shift) |
169 | | - end |
170 | | - end |
171 | | - end |
172 | | - |
173 | | - def run_class_frame(iseq, clazz) |
174 | | - run_frame(ClassFrame.new(iseq, frame, stack.length, clazz)) |
175 | | - end |
176 | | - |
177 | | - def run_method_frame(name, iseq, _self, *args, **kwargs, &block) |
178 | | - run_frame( |
179 | | - MethodFrame.new(iseq, frame, stack.length, _self, name, block) |
180 | | - ) do |
181 | | - locals = [*args, block] |
182 | | - |
183 | | - if iseq.argument_options[:keyword] |
184 | | - # First, set up the keyword bits array. |
185 | | - keyword_bits = |
186 | | - iseq.argument_options[:keyword].map do |config| |
187 | | - kwargs.key?(config.is_a?(Array) ? config[0] : config) |
188 | | - end |
189 | | - |
190 | | - iseq.local_table.locals.each_with_index do |local, index| |
191 | | - # If this is the keyword bits local, then set it appropriately. |
192 | | - if local.name == 2 |
193 | | - locals.insert(index, keyword_bits) |
194 | | - next |
195 | | - end |
196 | | - |
197 | | - # First, find the configuration for this local in the keywords |
198 | | - # list if it exists. |
199 | | - name = local.name |
200 | | - config = |
201 | | - iseq.argument_options[:keyword].find do |keyword| |
202 | | - keyword.is_a?(Array) ? keyword[0] == name : keyword == name |
203 | | - end |
204 | | - |
205 | | - # If the configuration doesn't exist, then the local is not a |
206 | | - # keyword local. |
207 | | - next unless config |
208 | | - |
209 | | - if !config.is_a?(Array) |
210 | | - # required keyword |
211 | | - locals.insert(index, kwargs.fetch(name)) |
212 | | - elsif !config[1].nil? |
213 | | - # optional keyword with embedded default value |
214 | | - locals.insert(index, kwargs.fetch(name, config[1])) |
215 | | - else |
216 | | - # optional keyword with expression default value |
217 | | - locals.insert(index, nil) |
218 | | - end |
219 | | - end |
220 | | - end |
221 | | - |
222 | | - iseq.local_table.size.times do |index| |
223 | | - local_set(index, 0, locals.shift) |
224 | | - end |
225 | | - end |
226 | | - end |
227 | | - |
228 | | - ########################################################################## |
229 | | - # Helper methods for instructions |
230 | | - ########################################################################## |
231 | | - |
232 | | - def const_base |
233 | | - frame.nesting.last |
234 | | - end |
235 | | - |
236 | | - def frame_at(level) |
237 | | - current = frame |
238 | | - level.times { current = current.parent } |
239 | | - current |
240 | | - end |
241 | | - |
242 | | - def frame_svar |
243 | | - current = frame |
244 | | - current = current.parent while current.is_a?(BlockFrame) |
245 | | - current |
246 | | - end |
247 | | - |
248 | | - def frame_yield |
249 | | - current = frame |
250 | | - current = current.parent until current.is_a?(MethodFrame) |
251 | | - current |
252 | | - end |
253 | | - |
254 | | - def frozen_core |
255 | | - FROZEN_CORE |
256 | | - end |
257 | | - |
258 | | - def jump(label) |
259 | | - Jump.new(label.name) |
260 | | - end |
261 | | - |
262 | | - def leave |
263 | | - Leave.new(pop) |
264 | | - end |
265 | | - |
266 | | - def local_get(index, level) |
267 | | - stack[frame_at(level).stack_index + index] |
268 | | - end |
269 | | - |
270 | | - def local_set(index, level, value) |
271 | | - stack[frame_at(level).stack_index + index] = value |
272 | | - end |
273 | | - end |
274 | | - |
275 | 6 | # Compile the given source into a YARV instruction sequence. |
276 | 7 | def self.compile(source, options = Compiler::Options.new) |
277 | 8 | SyntaxTree.parse(source).accept(Compiler.new(options)) |
|
0 commit comments