Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b998a6e

Browse files
committed
Add a bit of execution
1 parent 14df44e commit b998a6e

File tree

6 files changed

+1487
-68
lines changed

6 files changed

+1487
-68
lines changed

.rubocop.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,18 @@ Lint/InterpolationCheck:
3131
Lint/MissingSuper:
3232
Enabled: false
3333

34+
Lint/NonLocalExitFromIterator:
35+
Enabled: false
36+
3437
Lint/RedundantRequireStatement:
3538
Enabled: false
3639

3740
Lint/SuppressedException:
3841
Enabled: false
3942

43+
Lint/UnderscorePrefixedVariableName:
44+
Enabled: false
45+
4046
Lint/UnusedMethodArgument:
4147
AllowUnusedKeywordArguments: true
4248

@@ -55,6 +61,9 @@ Naming/RescuedExceptionsVariableName:
5561
Naming/VariableNumber:
5662
Enabled: false
5763

64+
Security/Eval:
65+
Enabled: false
66+
5867
Style/AccessorGrouping:
5968
Enabled: false
6069

@@ -64,9 +73,18 @@ Style/CaseEquality:
6473
Style/CaseLikeIf:
6574
Enabled: false
6675

76+
Style/ClassVars:
77+
Enabled: false
78+
79+
Style/DocumentDynamicEvalDefinition:
80+
Enabled: false
81+
6782
Style/Documentation:
6883
Enabled: false
6984

85+
Style/EndBlock:
86+
Enabled: false
87+
7088
Style/ExplicitBlockArgument:
7189
Enabled: false
7290

lib/syntax_tree/yarv.rb

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,288 @@
11
# frozen_string_literal: true
22

3+
require "forwardable"
4+
35
module SyntaxTree
46
# This module provides an object representation of the YARV bytecode.
57
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+
6275
# Compile the given source into a YARV instruction sequence.
7276
def self.compile(source, options = Compiler::Options.new)
8277
SyntaxTree.parse(source).accept(Compiler.new(options))
9278
end
279+
280+
# Compile and interpret the given source.
281+
def self.interpret(source, options = Compiler::Options.new)
282+
iseq = RubyVM::InstructionSequence.compile(source, **options)
283+
iseq = InstructionSequence.from(iseq.to_a)
284+
iseq.specialize_instructions!
285+
VM.new.run_top_frame(iseq)
286+
end
10287
end
11288
end

lib/syntax_tree/yarv/compiler.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,8 +1987,8 @@ def visit_pattern(node, end_label)
19871987
match_failure_label = iseq.label
19881988
match_error_label = iseq.label
19891989

1990-
# If there's a constant, then check if we match against that constant or
1991-
# not first. Branch to failure if we don't.
1990+
# If there's a constant, then check if we match against that constant
1991+
# or not first. Branch to failure if we don't.
19921992
if node.constant
19931993
iseq.dup
19941994
visit(node.constant)
@@ -2007,8 +2007,8 @@ def visit_pattern(node, end_label)
20072007
iseq.topn(2)
20082008
iseq.branchunless(match_failure_label)
20092009

2010-
# Since we have a valid cached value, we can skip past the part where we
2011-
# call #deconstruct on the object.
2010+
# Since we have a valid cached value, we can skip past the part where
2011+
# we call #deconstruct on the object.
20122012
iseq.pop
20132013
iseq.topn(1)
20142014
iseq.jump(length_label)
@@ -2064,8 +2064,8 @@ def visit_pattern(node, end_label)
20642064
end
20652065
end
20662066

2067-
# Set up the routine here to raise an error to indicate that the type of
2068-
# the deconstructed array was incorrect.
2067+
# Set up the routine here to raise an error to indicate that the type
2068+
# of the deconstructed array was incorrect.
20692069
iseq.push(match_error_label)
20702070
iseq.putspecialobject(PutSpecialObject::OBJECT_VMCORE)
20712071
iseq.putobject(TypeError)

0 commit comments

Comments
 (0)