[Feature #20624] Enhance RubyVM::AbstractSyntaxTree::Node#locations
This commit introduce `RubyVM::AbstractSyntaxTree::Node#locations` method and `RubyVM::AbstractSyntaxTree::Location` class. Ruby AST node will hold multiple locations information. `RubyVM::AbstractSyntaxTree::Node#locations` provides a way to access these locations information. `RubyVM::AbstractSyntaxTree::Location` is a class which holds these location information: * `#first_lineno` * `#first_column` * `#last_lineno` * `#last_column`
This commit is contained in:
parent
5617fec1f8
commit
f23485a8d6
Notes:
git
2024-07-23 03:36:17 +00:00
7
NEWS.md
7
NEWS.md
@ -44,6 +44,12 @@ Note: We're only listing outstanding class updates.
|
|||||||
|
|
||||||
* Range#size now raises TypeError if the range is not iterable. [[Misc #18984]]
|
* Range#size now raises TypeError if the range is not iterable. [[Misc #18984]]
|
||||||
|
|
||||||
|
* RubyVM::AbstractSyntaxTree
|
||||||
|
|
||||||
|
* Add `RubyVM::AbstractSyntaxTree::Node#locations` method which returns location objects
|
||||||
|
associated with the AST node. [[Feature #20624]]
|
||||||
|
* Add `RubyVM::AbstractSyntaxTree::Location` class which holds location information. [[Feature #20624]]
|
||||||
|
|
||||||
## Stdlib updates
|
## Stdlib updates
|
||||||
|
|
||||||
* Tempfile
|
* Tempfile
|
||||||
@ -160,3 +166,4 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log
|
|||||||
[Feature #20429]: https://bugs.ruby-lang.org/issues/20429
|
[Feature #20429]: https://bugs.ruby-lang.org/issues/20429
|
||||||
[Feature #20443]: https://bugs.ruby-lang.org/issues/20443
|
[Feature #20443]: https://bugs.ruby-lang.org/issues/20443
|
||||||
[Feature #20497]: https://bugs.ruby-lang.org/issues/20497
|
[Feature #20497]: https://bugs.ruby-lang.org/issues/20497
|
||||||
|
[Feature #20624]: https://bugs.ruby-lang.org/issues/20624
|
||||||
|
123
ast.c
123
ast.c
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
static VALUE rb_mAST;
|
static VALUE rb_mAST;
|
||||||
static VALUE rb_cNode;
|
static VALUE rb_cNode;
|
||||||
|
static VALUE rb_cLocation;
|
||||||
|
|
||||||
struct ASTNodeData {
|
struct ASTNodeData {
|
||||||
VALUE ast_value;
|
VALUE ast_value;
|
||||||
@ -43,6 +44,32 @@ static const rb_data_type_t rb_node_type = {
|
|||||||
RUBY_TYPED_FREE_IMMEDIATELY,
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ASTLocationData {
|
||||||
|
int first_lineno;
|
||||||
|
int first_column;
|
||||||
|
int last_lineno;
|
||||||
|
int last_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
location_gc_mark(void *ptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
location_memsize(const void *ptr)
|
||||||
|
{
|
||||||
|
return sizeof(struct ASTLocationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const rb_data_type_t rb_location_type = {
|
||||||
|
"AST/location",
|
||||||
|
{location_gc_mark, RUBY_TYPED_DEFAULT_FREE, location_memsize,},
|
||||||
|
0, 0,
|
||||||
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
static VALUE rb_ast_node_alloc(VALUE klass);
|
static VALUE rb_ast_node_alloc(VALUE klass);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -720,6 +747,45 @@ ast_node_children(rb_execution_context_t *ec, VALUE self)
|
|||||||
return node_children(data->ast_value, data->node);
|
return node_children(data->ast_value, data->node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
location_new(rb_code_location_t *loc)
|
||||||
|
{
|
||||||
|
VALUE obj;
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
|
||||||
|
obj = TypedData_Make_Struct(rb_cLocation, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
data->first_lineno = loc->beg_pos.lineno;
|
||||||
|
data->first_column = loc->beg_pos.column;
|
||||||
|
data->last_lineno = loc->end_pos.lineno;
|
||||||
|
data->last_column = loc->end_pos.column;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
node_locations(VALUE ast_value, const NODE *node)
|
||||||
|
{
|
||||||
|
enum node_type type = nd_type(node);
|
||||||
|
switch (type) {
|
||||||
|
case NODE_ARGS_AUX:
|
||||||
|
case NODE_LAST:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return rb_ary_new_from_args(1, location_new(nd_code_loc(node)));
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_bug("node_locations: unknown node: %s", ruby_node_name(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_node_locations(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
struct ASTNodeData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data);
|
||||||
|
|
||||||
|
return node_locations(data->ast_value, data->node);
|
||||||
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
ast_node_first_lineno(rb_execution_context_t *ec, VALUE self)
|
ast_node_first_lineno(rb_execution_context_t *ec, VALUE self)
|
||||||
{
|
{
|
||||||
@ -823,6 +889,61 @@ ast_node_script_lines(rb_execution_context_t *ec, VALUE self)
|
|||||||
return rb_parser_build_script_lines_from(ret);
|
return rb_parser_build_script_lines_from(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_location_first_lineno(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
|
||||||
|
return INT2NUM(data->first_lineno);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_location_first_column(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
|
||||||
|
return INT2NUM(data->first_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_location_last_lineno(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
|
||||||
|
return INT2NUM(data->last_lineno);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_location_last_column(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
|
||||||
|
return INT2NUM(data->last_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ast_location_inspect(rb_execution_context_t *ec, VALUE self)
|
||||||
|
{
|
||||||
|
VALUE str;
|
||||||
|
VALUE cname;
|
||||||
|
struct ASTLocationData *data;
|
||||||
|
TypedData_Get_Struct(self, struct ASTLocationData, &rb_location_type, data);
|
||||||
|
|
||||||
|
cname = rb_class_path(rb_obj_class(self));
|
||||||
|
str = rb_str_new2("#<");
|
||||||
|
|
||||||
|
rb_str_append(str, cname);
|
||||||
|
rb_str_catf(str, ":@%d:%d-%d:%d>",
|
||||||
|
data->first_lineno, data->first_column,
|
||||||
|
data->last_lineno, data->last_column);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
#include "ast.rbinc"
|
#include "ast.rbinc"
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -830,5 +951,7 @@ Init_ast(void)
|
|||||||
{
|
{
|
||||||
rb_mAST = rb_define_module_under(rb_cRubyVM, "AbstractSyntaxTree");
|
rb_mAST = rb_define_module_under(rb_cRubyVM, "AbstractSyntaxTree");
|
||||||
rb_cNode = rb_define_class_under(rb_mAST, "Node", rb_cObject);
|
rb_cNode = rb_define_class_under(rb_mAST, "Node", rb_cObject);
|
||||||
|
rb_cLocation = rb_define_class_under(rb_mAST, "Location", rb_cObject);
|
||||||
rb_undef_alloc_func(rb_cNode);
|
rb_undef_alloc_func(rb_cNode);
|
||||||
|
rb_undef_alloc_func(rb_cLocation);
|
||||||
}
|
}
|
||||||
|
57
ast.rb
57
ast.rb
@ -272,5 +272,62 @@ module RubyVM::AbstractSyntaxTree
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# node.locations -> array
|
||||||
|
#
|
||||||
|
# Returns location objects associated with the AST node.
|
||||||
|
# The returned array contains RubyVM::AbstractSyntaxTree::Location.
|
||||||
|
def locations
|
||||||
|
Primitive.ast_node_locations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# RubyVM::AbstractSyntaxTree::Location instances are created by
|
||||||
|
# RubyVM::AbstractSyntaxTree#locations.
|
||||||
|
#
|
||||||
|
# This class is MRI specific.
|
||||||
|
#
|
||||||
|
class Location
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# location.first_lineno -> integer
|
||||||
|
#
|
||||||
|
# The line number in the source code where this AST's text began.
|
||||||
|
def first_lineno
|
||||||
|
Primitive.ast_location_first_lineno
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# location.first_column -> integer
|
||||||
|
#
|
||||||
|
# The column number in the source code where this AST's text began.
|
||||||
|
def first_column
|
||||||
|
Primitive.ast_location_first_column
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# location.last_lineno -> integer
|
||||||
|
#
|
||||||
|
# The line number in the source code where this AST's text ended.
|
||||||
|
def last_lineno
|
||||||
|
Primitive.ast_location_last_lineno
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# location.last_column -> integer
|
||||||
|
#
|
||||||
|
# The column number in the source code where this AST's text ended.
|
||||||
|
def last_column
|
||||||
|
Primitive.ast_location_last_column
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# location.inspect -> string
|
||||||
|
#
|
||||||
|
# Returns debugging information about this location as a string.
|
||||||
|
def inspect
|
||||||
|
Primitive.ast_location_inspect
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1297,6 +1297,13 @@ dummy
|
|||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_locations
|
||||||
|
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
|
||||||
|
locations = node.locations
|
||||||
|
|
||||||
|
assert_equal(RubyVM::AbstractSyntaxTree::Location, locations[0].class)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def assert_error_tolerant(src, expected, keep_tokens: false)
|
def assert_error_tolerant(src, expected, keep_tokens: false)
|
||||||
@ -1316,4 +1323,16 @@ dummy
|
|||||||
assert_equal(expected, str)
|
assert_equal(expected, str)
|
||||||
node
|
node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestLocation < Test::Unit::TestCase
|
||||||
|
def test_lineno_and_column
|
||||||
|
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
|
||||||
|
location = node.locations[0]
|
||||||
|
|
||||||
|
assert_equal(1, location.first_lineno)
|
||||||
|
assert_equal(0, location.first_column)
|
||||||
|
assert_equal(1, location.last_lineno)
|
||||||
|
assert_equal(5, location.last_column)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user