Put following snippets to ~/.irbrc.
unless defined?(SCRIPT_LINES__)
SCRIPT_LINES__ = {}
end
ast_happier = TracePoint.new(:call) do |tp|
SCRIPT_LINES__['(irb)'] = tp.binding.local_variable_get(:statements).lines
end
ast_happier.enable(target: IRB::WorkSpace.instance_method(:evaluate))
It works, yay!
% irb
irb(main):001:0> pp RubyVM::AbstractSyntaxTree.of(-> { puts :hi })
(SCOPE@1:35-1:48
tbl: []
args:
(ARGS@1:35-1:35
pre_num: 0
pre_init: nil
opt: nil
first_post: nil
post_num: 0
post_init: nil
rest: nil
kw: nil
kwrest: nil
block: nil)
body: (FCALL@1:38-1:46 :puts (ARRAY@1:43-1:46 (LIT@1:43-1:46 :hi) nil)))
=> #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:35-1:48>
But how does it work?
SCRIPT_LINES__ is used for standard library debug.
https://docs.ruby-lang.org/en/2.6.0/DEBUGGER__.html
Of course there is no documentation.
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ # :nodoc:
https://github.com/ruby/ruby/blob/v2_6_3/lib/debug.rb#L22
In the ast.c, SCRIPT_LINES__ is also used for finding the source code of the proc.
static VALUE
script_lines(VALUE path)
{
VALUE hash, lines;
ID script_lines;
CONST_ID(script_lines, "SCRIPT_LINES__");
if (!rb_const_defined_at(rb_cObject, script_lines)) return Qnil;
hash = rb_const_get_at(rb_cObject, script_lines);
if (!RB_TYPE_P(hash, T_HASH)) return Qnil;
lines = rb_hash_lookup(hash, path);
if (!RB_TYPE_P(lines, T_ARRAY)) return Qnil;
return lines;
}
https://github.com/ruby/ruby/blob/v2_6_3/ast.c#L189-L201
static VALUE
rb_ast_s_of(VALUE module, VALUE body)
{
VALUE path, node, lines;
int node_id;
const rb_iseq_t *iseq = NULL;
if (rb_obj_is_proc(body)) {
iseq = vm_proc_iseq(body);
if (!rb_obj_is_iseq((VALUE)iseq)) {
iseq = NULL;
}
}
else {
iseq = rb_method_iseq(body);
}
if (!iseq) return Qnil;
path = rb_iseq_path(iseq);
node_id = iseq->body->location.node_id;
if (!NIL_P(lines = script_lines(path))) {
node = rb_ast_parse_array(lines);
}
else if (RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0) {
node = rb_ast_parse_str(rb_e_script);
}
else {
node = rb_ast_parse_file(path);
}
return node_find(node, node_id);
}
https://github.com/ruby/ruby/blob/v2_6_3/ast.c#L220-L253
During the RubyVM::AbstractSyntaxTree.of, ruby does
- read the file location from the
RubyVM::InstractionSequenceof given proc. - read whole source code from some sources, such as
SCRIPT_LINES__/the real file/command line input given by-e - parse whole source code again to create an AST.
- read the node id from the
RubyVM::InstructionSequenceof given proc. - find a node from the AST, the node which has same node id of given proc.
If parsing the same file, ruby parser gives identical node id for same object in that position. That's the point.
I use TracePoint to set the source code to SCRIPT_LINES__ before evaluate irb input.
The TracePoint work at here.
# Evaluate the given +statements+ within the context of this workspace.
def evaluate(context, statements, file = __FILE__, line = __LINE__)
eval(statements, @binding, file, line)
end
https://github.com/ruby/ruby/blob/v2_6_3/lib/irb/workspace.rb#L83-L86
Hava a fun!
Top comments (0)