syntax_tree 5.1.0 → 5.3.0
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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +4 -0
- data/.github/workflows/auto-merge.yml +1 -1
- data/.github/workflows/gh-pages.yml +1 -1
- data/.github/workflows/main.yml +5 -2
- data/.gitmodules +9 -0
- data/.rubocop.yml +11 -1
- data/CHANGELOG.md +29 -1
- data/Gemfile.lock +10 -10
- data/README.md +1 -0
- data/Rakefile +7 -0
- data/exe/yarv +63 -0
- data/lib/syntax_tree/cli.rb +3 -2
- data/lib/syntax_tree/formatter.rb +23 -2
- data/lib/syntax_tree/index.rb +374 -0
- data/lib/syntax_tree/node.rb +146 -107
- data/lib/syntax_tree/parser.rb +20 -3
- data/lib/syntax_tree/plugin/disable_ternary.rb +7 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/yarv/assembler.rb +17 -13
- data/lib/syntax_tree/yarv/bf.rb +13 -16
- data/lib/syntax_tree/yarv/compiler.rb +25 -14
- data/lib/syntax_tree/yarv/decompiler.rb +10 -1
- data/lib/syntax_tree/yarv/disassembler.rb +3 -2
- data/lib/syntax_tree/yarv/instruction_sequence.rb +191 -87
- data/lib/syntax_tree/yarv/instructions.rb +1011 -42
- data/lib/syntax_tree/yarv/legacy.rb +59 -3
- data/lib/syntax_tree/yarv/vm.rb +628 -0
- data/lib/syntax_tree/yarv.rb +0 -269
- data/lib/syntax_tree.rb +16 -0
- metadata +9 -3
@@ -0,0 +1,628 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module SyntaxTree
|
6
|
+
# This module provides an object representation of the YARV bytecode.
|
7
|
+
module YARV
|
8
|
+
class VM
|
9
|
+
class Jump
|
10
|
+
attr_reader :label
|
11
|
+
|
12
|
+
def initialize(label)
|
13
|
+
@label = label
|
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
|
+
attr_accessor :line, :pc
|
28
|
+
|
29
|
+
def initialize(iseq, parent, stack_index, _self, nesting)
|
30
|
+
@iseq = iseq
|
31
|
+
@parent = parent
|
32
|
+
@stack_index = stack_index
|
33
|
+
@_self = _self
|
34
|
+
@nesting = nesting
|
35
|
+
|
36
|
+
@svars = {}
|
37
|
+
@line = iseq.line
|
38
|
+
@pc = 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class TopFrame < Frame
|
43
|
+
def initialize(iseq)
|
44
|
+
super(iseq, nil, 0, TOPLEVEL_BINDING.eval("self"), [Object])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class BlockFrame < Frame
|
49
|
+
def initialize(iseq, parent, stack_index)
|
50
|
+
super(iseq, parent, stack_index, parent._self, parent.nesting)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class MethodFrame < Frame
|
55
|
+
attr_reader :name, :block
|
56
|
+
|
57
|
+
def initialize(iseq, nesting, parent, stack_index, _self, name, block)
|
58
|
+
super(iseq, parent, stack_index, _self, nesting)
|
59
|
+
@name = name
|
60
|
+
@block = block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ClassFrame < Frame
|
65
|
+
def initialize(iseq, parent, stack_index, _self)
|
66
|
+
super(iseq, parent, stack_index, _self, parent.nesting + [_self])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class RescueFrame < Frame
|
71
|
+
def initialize(iseq, parent, stack_index)
|
72
|
+
super(iseq, parent, stack_index, parent._self, parent.nesting)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class ThrownError < StandardError
|
77
|
+
attr_reader :value
|
78
|
+
|
79
|
+
def initialize(value, backtrace)
|
80
|
+
super("This error was thrown by the Ruby VM.")
|
81
|
+
@value = value
|
82
|
+
set_backtrace(backtrace)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class ReturnError < ThrownError
|
87
|
+
end
|
88
|
+
|
89
|
+
class BreakError < ThrownError
|
90
|
+
end
|
91
|
+
|
92
|
+
class NextError < ThrownError
|
93
|
+
end
|
94
|
+
|
95
|
+
class FrozenCore
|
96
|
+
define_method("core#hash_merge_kwd") { |left, right| left.merge(right) }
|
97
|
+
|
98
|
+
define_method("core#hash_merge_ptr") do |hash, *values|
|
99
|
+
hash.merge(values.each_slice(2).to_h)
|
100
|
+
end
|
101
|
+
|
102
|
+
define_method("core#set_method_alias") do |clazz, new_name, old_name|
|
103
|
+
clazz.alias_method(new_name, old_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
define_method("core#set_variable_alias") do |new_name, old_name|
|
107
|
+
# Using eval here since there isn't a reflection API to be able to
|
108
|
+
# alias global variables.
|
109
|
+
eval("alias #{new_name} #{old_name}", binding, __FILE__, __LINE__)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method("core#set_postexe") { |&block| END { block.call } }
|
113
|
+
|
114
|
+
define_method("core#undef_method") do |clazz, name|
|
115
|
+
clazz.undef_method(name)
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# This is the main entrypoint for events firing in the VM, which allows
|
121
|
+
# us to implement tracing.
|
122
|
+
class NullEvents
|
123
|
+
def publish_frame_change(frame)
|
124
|
+
end
|
125
|
+
|
126
|
+
def publish_instruction(iseq, insn)
|
127
|
+
end
|
128
|
+
|
129
|
+
def publish_stack_change(stack)
|
130
|
+
end
|
131
|
+
|
132
|
+
def publish_tracepoint(event)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# This is a simple implementation of tracing that prints to STDOUT.
|
137
|
+
class STDOUTEvents
|
138
|
+
attr_reader :disassembler
|
139
|
+
|
140
|
+
def initialize
|
141
|
+
@disassembler = Disassembler.new
|
142
|
+
end
|
143
|
+
|
144
|
+
def publish_frame_change(frame)
|
145
|
+
puts "%-16s %s" % ["frame-change", "#{frame.iseq.file}@#{frame.line}"]
|
146
|
+
end
|
147
|
+
|
148
|
+
def publish_instruction(iseq, insn)
|
149
|
+
disassembler.current_iseq = iseq
|
150
|
+
puts "%-16s %s" % ["instruction", insn.disasm(disassembler)]
|
151
|
+
end
|
152
|
+
|
153
|
+
def publish_stack_change(stack)
|
154
|
+
puts "%-16s %s" % ["stack-change", stack.values.inspect]
|
155
|
+
end
|
156
|
+
|
157
|
+
def publish_tracepoint(event)
|
158
|
+
puts "%-16s %s" % ["tracepoint", event.inspect]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# This represents the global VM stack. It effectively is an array, but
|
163
|
+
# wraps mutating functions with instrumentation.
|
164
|
+
class Stack
|
165
|
+
attr_reader :events, :values
|
166
|
+
|
167
|
+
def initialize(events)
|
168
|
+
@events = events
|
169
|
+
@values = []
|
170
|
+
end
|
171
|
+
|
172
|
+
def concat(...)
|
173
|
+
values.concat(...).tap { events.publish_stack_change(self) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def last
|
177
|
+
values.last
|
178
|
+
end
|
179
|
+
|
180
|
+
def length
|
181
|
+
values.length
|
182
|
+
end
|
183
|
+
|
184
|
+
def push(...)
|
185
|
+
values.push(...).tap { events.publish_stack_change(self) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def pop(...)
|
189
|
+
values.pop(...).tap { events.publish_stack_change(self) }
|
190
|
+
end
|
191
|
+
|
192
|
+
def slice!(...)
|
193
|
+
values.slice!(...).tap { events.publish_stack_change(self) }
|
194
|
+
end
|
195
|
+
|
196
|
+
def [](...)
|
197
|
+
values.[](...)
|
198
|
+
end
|
199
|
+
|
200
|
+
def []=(...)
|
201
|
+
values.[]=(...).tap { events.publish_stack_change(self) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
FROZEN_CORE = FrozenCore.new.freeze
|
206
|
+
|
207
|
+
extend Forwardable
|
208
|
+
|
209
|
+
attr_reader :events
|
210
|
+
|
211
|
+
attr_reader :stack
|
212
|
+
def_delegators :stack, :push, :pop
|
213
|
+
|
214
|
+
attr_reader :frame
|
215
|
+
|
216
|
+
def initialize(events = NullEvents.new)
|
217
|
+
@events = events
|
218
|
+
@stack = Stack.new(events)
|
219
|
+
@frame = nil
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.run(iseq)
|
223
|
+
new.run_top_frame(iseq)
|
224
|
+
end
|
225
|
+
|
226
|
+
##########################################################################
|
227
|
+
# Helper methods for frames
|
228
|
+
##########################################################################
|
229
|
+
|
230
|
+
def run_frame(frame)
|
231
|
+
# First, set the current frame to the given value.
|
232
|
+
previous = @frame
|
233
|
+
@frame = frame
|
234
|
+
events.publish_frame_change(@frame)
|
235
|
+
|
236
|
+
# Next, set up the local table for the frame. This is actually incorrect
|
237
|
+
# as it could use the values already on the stack, but for now we're
|
238
|
+
# just doing this for simplicity.
|
239
|
+
stack.concat(Array.new(frame.iseq.local_table.size))
|
240
|
+
|
241
|
+
# Yield so that some frame-specific setup can be done.
|
242
|
+
start_label = yield if block_given?
|
243
|
+
frame.pc = frame.iseq.insns.index(start_label) if start_label
|
244
|
+
|
245
|
+
# Finally we can execute the instructions one at a time. If they return
|
246
|
+
# jumps or leaves we will handle those appropriately.
|
247
|
+
loop do
|
248
|
+
case (insn = frame.iseq.insns[frame.pc])
|
249
|
+
when Integer
|
250
|
+
frame.line = insn
|
251
|
+
frame.pc += 1
|
252
|
+
when Symbol
|
253
|
+
events.publish_tracepoint(insn)
|
254
|
+
frame.pc += 1
|
255
|
+
when InstructionSequence::Label
|
256
|
+
# skip labels
|
257
|
+
frame.pc += 1
|
258
|
+
else
|
259
|
+
begin
|
260
|
+
events.publish_instruction(frame.iseq, insn)
|
261
|
+
result = insn.call(self)
|
262
|
+
rescue ReturnError => error
|
263
|
+
raise if frame.iseq.type != :method
|
264
|
+
|
265
|
+
stack.slice!(frame.stack_index..)
|
266
|
+
@frame = frame.parent
|
267
|
+
events.publish_frame_change(@frame)
|
268
|
+
|
269
|
+
return error.value
|
270
|
+
rescue BreakError => error
|
271
|
+
raise if frame.iseq.type != :block
|
272
|
+
|
273
|
+
catch_entry =
|
274
|
+
find_catch_entry(frame, InstructionSequence::CatchBreak)
|
275
|
+
raise unless catch_entry
|
276
|
+
|
277
|
+
stack.slice!(
|
278
|
+
(
|
279
|
+
frame.stack_index + frame.iseq.local_table.size +
|
280
|
+
catch_entry.restore_sp
|
281
|
+
)..
|
282
|
+
)
|
283
|
+
@frame = frame
|
284
|
+
events.publish_frame_change(@frame)
|
285
|
+
|
286
|
+
frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
|
287
|
+
push(result = error.value)
|
288
|
+
rescue NextError => error
|
289
|
+
raise if frame.iseq.type != :block
|
290
|
+
|
291
|
+
catch_entry =
|
292
|
+
find_catch_entry(frame, InstructionSequence::CatchNext)
|
293
|
+
raise unless catch_entry
|
294
|
+
|
295
|
+
stack.slice!(
|
296
|
+
(
|
297
|
+
frame.stack_index + frame.iseq.local_table.size +
|
298
|
+
catch_entry.restore_sp
|
299
|
+
)..
|
300
|
+
)
|
301
|
+
@frame = frame
|
302
|
+
events.publish_frame_change(@frame)
|
303
|
+
|
304
|
+
frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
|
305
|
+
push(result = error.value)
|
306
|
+
rescue Exception => error
|
307
|
+
catch_entry =
|
308
|
+
find_catch_entry(frame, InstructionSequence::CatchRescue)
|
309
|
+
raise unless catch_entry
|
310
|
+
|
311
|
+
stack.slice!(
|
312
|
+
(
|
313
|
+
frame.stack_index + frame.iseq.local_table.size +
|
314
|
+
catch_entry.restore_sp
|
315
|
+
)..
|
316
|
+
)
|
317
|
+
@frame = frame
|
318
|
+
events.publish_frame_change(@frame)
|
319
|
+
|
320
|
+
frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
|
321
|
+
push(result = run_rescue_frame(catch_entry.iseq, frame, error))
|
322
|
+
end
|
323
|
+
|
324
|
+
case result
|
325
|
+
when Jump
|
326
|
+
frame.pc = frame.iseq.insns.index(result.label) + 1
|
327
|
+
when Leave
|
328
|
+
# this shouldn't be necessary, but is because we're not handling
|
329
|
+
# the stack correctly at the moment
|
330
|
+
stack.slice!(frame.stack_index..)
|
331
|
+
|
332
|
+
# restore the previous frame
|
333
|
+
@frame = previous || frame.parent
|
334
|
+
events.publish_frame_change(@frame) if @frame
|
335
|
+
|
336
|
+
return result.value
|
337
|
+
else
|
338
|
+
frame.pc += 1
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def find_catch_entry(frame, type)
|
345
|
+
iseq = frame.iseq
|
346
|
+
iseq.catch_table.find do |catch_entry|
|
347
|
+
next unless catch_entry.is_a?(type)
|
348
|
+
|
349
|
+
begin_pc = iseq.insns.index(catch_entry.begin_label)
|
350
|
+
end_pc = iseq.insns.index(catch_entry.end_label)
|
351
|
+
|
352
|
+
(begin_pc...end_pc).cover?(frame.pc)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def run_top_frame(iseq)
|
357
|
+
run_frame(TopFrame.new(iseq))
|
358
|
+
end
|
359
|
+
|
360
|
+
def run_block_frame(iseq, frame, *args, **kwargs, &block)
|
361
|
+
run_frame(BlockFrame.new(iseq, frame, stack.length)) do
|
362
|
+
setup_arguments(iseq, args, kwargs, block)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def run_class_frame(iseq, clazz)
|
367
|
+
run_frame(ClassFrame.new(iseq, frame, stack.length, clazz))
|
368
|
+
end
|
369
|
+
|
370
|
+
def run_method_frame(name, nesting, iseq, _self, *args, **kwargs, &block)
|
371
|
+
run_frame(
|
372
|
+
MethodFrame.new(
|
373
|
+
iseq,
|
374
|
+
nesting,
|
375
|
+
frame,
|
376
|
+
stack.length,
|
377
|
+
_self,
|
378
|
+
name,
|
379
|
+
block
|
380
|
+
)
|
381
|
+
) { setup_arguments(iseq, args, kwargs, block) }
|
382
|
+
end
|
383
|
+
|
384
|
+
def run_rescue_frame(iseq, frame, error)
|
385
|
+
run_frame(RescueFrame.new(iseq, frame, stack.length)) do
|
386
|
+
local_set(0, 0, error)
|
387
|
+
nil
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def setup_arguments(iseq, args, kwargs, block)
|
392
|
+
locals = [*args]
|
393
|
+
local_index = 0
|
394
|
+
start_label = nil
|
395
|
+
|
396
|
+
# First, set up all of the leading arguments. These are positional and
|
397
|
+
# required arguments at the start of the argument list.
|
398
|
+
if (lead_num = iseq.argument_options[:lead_num])
|
399
|
+
lead_num.times do
|
400
|
+
local_set(local_index, 0, locals.shift)
|
401
|
+
local_index += 1
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Next, set up all of the optional arguments. The opt array contains
|
406
|
+
# the labels that the frame should start at if the optional is
|
407
|
+
# present. The last element of the array is the label that the frame
|
408
|
+
# should start at if all of the optional arguments are present.
|
409
|
+
if (opt = iseq.argument_options[:opt])
|
410
|
+
opt[0...-1].each do |label|
|
411
|
+
if locals.empty?
|
412
|
+
start_label = label
|
413
|
+
break
|
414
|
+
else
|
415
|
+
local_set(local_index, 0, locals.shift)
|
416
|
+
local_index += 1
|
417
|
+
end
|
418
|
+
|
419
|
+
start_label = opt.last if start_label.nil?
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# If there is a splat argument, then we'll set that up here. It will
|
424
|
+
# grab up all of the remaining positional arguments.
|
425
|
+
if (rest_start = iseq.argument_options[:rest_start])
|
426
|
+
if (post_start = iseq.argument_options[:post_start])
|
427
|
+
length = post_start - rest_start
|
428
|
+
local_set(local_index, 0, locals[0...length])
|
429
|
+
locals = locals[length..]
|
430
|
+
else
|
431
|
+
local_set(local_index, 0, locals.dup)
|
432
|
+
locals.clear
|
433
|
+
end
|
434
|
+
local_index += 1
|
435
|
+
end
|
436
|
+
|
437
|
+
# Next, set up any post arguments. These are positional arguments that
|
438
|
+
# come after the splat argument.
|
439
|
+
if (post_num = iseq.argument_options[:post_num])
|
440
|
+
post_num.times do
|
441
|
+
local_set(local_index, 0, locals.shift)
|
442
|
+
local_index += 1
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
if (keyword_option = iseq.argument_options[:keyword])
|
447
|
+
# First, set up the keyword bits array.
|
448
|
+
keyword_bits =
|
449
|
+
keyword_option.map do |config|
|
450
|
+
kwargs.key?(config.is_a?(Array) ? config[0] : config)
|
451
|
+
end
|
452
|
+
|
453
|
+
iseq.local_table.locals.each_with_index do |local, index|
|
454
|
+
# If this is the keyword bits local, then set it appropriately.
|
455
|
+
if local.name.is_a?(Integer)
|
456
|
+
local_set(index, 0, keyword_bits)
|
457
|
+
next
|
458
|
+
end
|
459
|
+
|
460
|
+
# First, find the configuration for this local in the keywords
|
461
|
+
# list if it exists.
|
462
|
+
name = local.name
|
463
|
+
config =
|
464
|
+
keyword_option.find do |keyword|
|
465
|
+
keyword.is_a?(Array) ? keyword[0] == name : keyword == name
|
466
|
+
end
|
467
|
+
|
468
|
+
# If the configuration doesn't exist, then the local is not a
|
469
|
+
# keyword local.
|
470
|
+
next unless config
|
471
|
+
|
472
|
+
if !config.is_a?(Array)
|
473
|
+
# required keyword
|
474
|
+
local_set(index, 0, kwargs.fetch(name))
|
475
|
+
elsif !config[1].nil?
|
476
|
+
# optional keyword with embedded default value
|
477
|
+
local_set(index, 0, kwargs.fetch(name, config[1]))
|
478
|
+
else
|
479
|
+
# optional keyword with expression default value
|
480
|
+
local_set(index, 0, kwargs[name])
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
local_set(local_index, 0, block) if iseq.argument_options[:block_start]
|
486
|
+
|
487
|
+
start_label
|
488
|
+
end
|
489
|
+
|
490
|
+
##########################################################################
|
491
|
+
# Helper methods for instructions
|
492
|
+
##########################################################################
|
493
|
+
|
494
|
+
def const_base
|
495
|
+
frame.nesting.last
|
496
|
+
end
|
497
|
+
|
498
|
+
def frame_at(level)
|
499
|
+
current = frame
|
500
|
+
level.times { current = current.parent }
|
501
|
+
current
|
502
|
+
end
|
503
|
+
|
504
|
+
def frame_svar
|
505
|
+
current = frame
|
506
|
+
current = current.parent while current.is_a?(BlockFrame)
|
507
|
+
current
|
508
|
+
end
|
509
|
+
|
510
|
+
def frame_yield
|
511
|
+
current = frame
|
512
|
+
current = current.parent until current.is_a?(MethodFrame)
|
513
|
+
current
|
514
|
+
end
|
515
|
+
|
516
|
+
def frozen_core
|
517
|
+
FROZEN_CORE
|
518
|
+
end
|
519
|
+
|
520
|
+
def jump(label)
|
521
|
+
Jump.new(label)
|
522
|
+
end
|
523
|
+
|
524
|
+
def leave
|
525
|
+
Leave.new(pop)
|
526
|
+
end
|
527
|
+
|
528
|
+
def local_get(index, level)
|
529
|
+
stack[frame_at(level).stack_index + index]
|
530
|
+
end
|
531
|
+
|
532
|
+
def local_set(index, level, value)
|
533
|
+
stack[frame_at(level).stack_index + index] = value
|
534
|
+
end
|
535
|
+
|
536
|
+
##########################################################################
|
537
|
+
# Methods for overriding runtime behavior
|
538
|
+
##########################################################################
|
539
|
+
|
540
|
+
DLEXT = ".#{RbConfig::CONFIG["DLEXT"]}"
|
541
|
+
SOEXT = ".#{RbConfig::CONFIG["SOEXT"]}"
|
542
|
+
|
543
|
+
def require_resolved(filepath)
|
544
|
+
$LOADED_FEATURES << filepath
|
545
|
+
iseq = RubyVM::InstructionSequence.compile_file(filepath)
|
546
|
+
run_top_frame(InstructionSequence.from(iseq.to_a))
|
547
|
+
end
|
548
|
+
|
549
|
+
def require_internal(filepath, loading: false)
|
550
|
+
case (extname = File.extname(filepath))
|
551
|
+
when ""
|
552
|
+
# search for all the extensions
|
553
|
+
searching = filepath
|
554
|
+
extensions = ["", ".rb", DLEXT, SOEXT]
|
555
|
+
when ".rb", DLEXT, SOEXT
|
556
|
+
# search only for the given extension name
|
557
|
+
searching = File.basename(filepath, extname)
|
558
|
+
extensions = [extname]
|
559
|
+
else
|
560
|
+
# we don't handle these extensions, raise a load error
|
561
|
+
raise LoadError, "cannot load such file -- #{filepath}"
|
562
|
+
end
|
563
|
+
|
564
|
+
if filepath.start_with?("/")
|
565
|
+
# absolute path, search only in the given directory
|
566
|
+
directories = [File.dirname(searching)]
|
567
|
+
searching = File.basename(searching)
|
568
|
+
else
|
569
|
+
# relative path, search in the load path
|
570
|
+
directories = $LOAD_PATH
|
571
|
+
end
|
572
|
+
|
573
|
+
directories.each do |directory|
|
574
|
+
extensions.each do |extension|
|
575
|
+
absolute_path = File.join(directory, "#{searching}#{extension}")
|
576
|
+
next unless File.exist?(absolute_path)
|
577
|
+
|
578
|
+
if !loading && $LOADED_FEATURES.include?(absolute_path)
|
579
|
+
return false
|
580
|
+
elsif extension == ".rb"
|
581
|
+
require_resolved(absolute_path)
|
582
|
+
return true
|
583
|
+
elsif loading
|
584
|
+
return Kernel.send(:yarv_load, filepath)
|
585
|
+
else
|
586
|
+
return Kernel.send(:yarv_require, filepath)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
if loading
|
592
|
+
Kernel.send(:yarv_load, filepath)
|
593
|
+
else
|
594
|
+
Kernel.send(:yarv_require, filepath)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
def require(filepath)
|
599
|
+
require_internal(filepath, loading: false)
|
600
|
+
end
|
601
|
+
|
602
|
+
def require_relative(filepath)
|
603
|
+
Kernel.yarv_require_relative(filepath)
|
604
|
+
end
|
605
|
+
|
606
|
+
def load(filepath)
|
607
|
+
require_internal(filepath, loading: true)
|
608
|
+
end
|
609
|
+
|
610
|
+
def eval(
|
611
|
+
source,
|
612
|
+
binding = TOPLEVEL_BINDING,
|
613
|
+
filename = "(eval)",
|
614
|
+
lineno = 1
|
615
|
+
)
|
616
|
+
Kernel.yarv_eval(source, binding, filename, lineno)
|
617
|
+
end
|
618
|
+
|
619
|
+
def throw(tag, value = nil)
|
620
|
+
Kernel.throw(tag, value)
|
621
|
+
end
|
622
|
+
|
623
|
+
def catch(tag, &block)
|
624
|
+
Kernel.catch(tag, &block)
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|