RubyVM::InstructionSequence can show you how the MRI VM interprets your code:
code = "3 * 4 + 5"
is = RubyVM::InstructionSequence.new(code)
= <RubyVM::InstructionSequence:<compiled>@<compiled>>
is.eval
= 17
puts is.disasm
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace 1 ( 1)
0002 putobject 3
0004 putobject 4
0006 opt_mult <ic:2>
0008 putobject 5
0010 opt_plus <ic:3>
0012 leave
[12] pry(main)> RubyVM::InstructionSequence.instance_methods(false)
=> [:inspect, :disasm, :disassemble, :to_a, :eval]
[1] pry(main)> RubyVM.stat
=> {:global_method_state=>153, :global_constant_state=>3560, :class_serial=>22620}
[2] pry(main)> class A
[2] pry(main)* end
=> nil
[3] pry(main)> RubyVM.stat
=> {:global_method_state=>153, :global_constant_state=>3561, :class_serial=>22622}
[4] pry(main)> class B
[4] pry(main)* end
=> nil
[5] pry(main)> RubyVM.stat
=> {:global_method_state=>153, :global_constant_state=>3562, :class_serial=>22624}
[6] pry(main)> RubyVM.stat.class
=> Hash
[7] pry(main)> module C
[7] pry(main)* D = 12
[7] pry(main)* end
=> 12
[8] pry(main)> RubyVM.stat
=> {:global_method_state=>153, :global_constant_state=>3564, :class_serial=>22625}