From 0643f081877ea19f8dd6610dd67b2606ecf4c880 Mon Sep 17 00:00:00 2001 From: ydah Date: Fri, 3 Jan 2025 19:51:58 +0900 Subject: [PATCH] Implement IF NODE locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following Location information has been added This is the information required for parse.y to be a universal parser: ``` ❯ ruby --parser=prism --dump=parsetree -y -e "if a; elsif b; else end" @ ProgramNode (location: (1,0)-(1,23)) +-- locals: [] +-- statements: @ StatementsNode (location: (1,0)-(1,23)) +-- body: (length: 1) +-- @ IfNode (location: (1,0)-(1,23)) +-- if_keyword_loc: (1,0)-(1,2) = "if" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +-- predicate: | @ CallNode (location: (1,3)-(1,4)) | +-- CallNodeFlags: variable_call, ignore_visibility | +-- receiver: nil | +-- call_operator_loc: nil | +-- name: :a | +-- message_loc: (1,3)-(1,4) = "a" | +-- opening_loc: nil | +-- arguments: nil | +-- closing_loc: nil | +-- block: nil +-- then_keyword_loc: nil ^^^^^^^^^^^^^^^^^^^^^^^^^ +-- statements: nil +-- subsequent: | @ IfNode (location: (1,6)-(1,23)) | +-- if_keyword_loc: (1,6)-(1,11) = "elsif" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | +-- predicate: | | @ CallNode (location: (1,12)-(1,13)) | | +-- CallNodeFlags: variable_call, ignore_visibility | | +-- receiver: nil | | +-- call_operator_loc: nil | | +-- name: :b | | +-- message_loc: (1,12)-(1,13) = "b" | | +-- opening_loc: nil | | +-- arguments: nil | | +-- closing_loc: nil | | +-- block: nil | +-- then_keyword_loc: nil ^^^^^^^^^^^^^^^^^^^^^^^^^ | +-- statements: nil | +-- subsequent: | | @ ElseNode (location: (1,15)-(1,23)) | | +-- else_keyword_loc: (1,15)-(1,19) = "else" | | +-- statements: nil | | +-- end_keyword_loc: (1,20)-(1,23) = "end" | +-- end_keyword_loc: (1,20)-(1,23) = "end" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +-- end_keyword_loc: (1,20)-(1,23) = "end" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` --- ast.c | 6 ++++++ node_dump.c | 5 ++++- parse.y | 28 +++++++++++++++++----------- rubyparser.h | 3 +++ test/ruby/test_ast.rb | 18 ++++++++++++++++++ 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/ast.c b/ast.c index a0c1801de1..efa393253c 100644 --- a/ast.c +++ b/ast.c @@ -807,6 +807,12 @@ node_locations(VALUE ast_value, const NODE *node) location_new(nd_code_loc(node)), location_new(&RNODE_CASE3(node)->case_keyword_loc), location_new(&RNODE_CASE3(node)->end_keyword_loc)); + case NODE_IF: + return rb_ary_new_from_args(4, + location_new(nd_code_loc(node)), + location_new(&RNODE_IF(node)->if_keyword_loc), + location_new(&RNODE_IF(node)->then_keyword_loc), + location_new(&RNODE_IF(node)->end_keyword_loc)); case NODE_NEXT: return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), diff --git a/node_dump.c b/node_dump.c index d346ba9a7e..94df6cf248 100644 --- a/node_dump.c +++ b/node_dump.c @@ -239,8 +239,11 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("example: if x == 1 then foo else bar end"); F_NODE(nd_cond, RNODE_IF, "condition expr"); F_NODE(nd_body, RNODE_IF, "then clause"); - LAST_NODE; F_NODE(nd_else, RNODE_IF, "else clause"); + F_LOC(if_keyword_loc, RNODE_IF); + F_LOC(then_keyword_loc, RNODE_IF); + LAST_NODE; + F_LOC(end_keyword_loc, RNODE_IF); return; case NODE_UNLESS: diff --git a/parse.y b/parse.y index c8629c779b..71b09eacaf 100644 --- a/parse.y +++ b/parse.y @@ -1058,7 +1058,7 @@ rb_discard_node(struct parser_params *p, NODE *n) static rb_node_scope_t *rb_node_scope_new(struct parser_params *p, rb_node_args_t *nd_args, NODE *nd_body, const YYLTYPE *loc); static rb_node_scope_t *rb_node_scope_new2(struct parser_params *p, rb_ast_id_table_t *nd_tbl, rb_node_args_t *nd_args, NODE *nd_body, const YYLTYPE *loc); static rb_node_block_t *rb_node_block_new(struct parser_params *p, NODE *nd_head, const YYLTYPE *loc); -static rb_node_if_t *rb_node_if_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, NODE *nd_else, const YYLTYPE *loc); +static rb_node_if_t *rb_node_if_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, NODE *nd_else, const YYLTYPE *loc, const YYLTYPE* if_keyword_loc, const YYLTYPE* then_keyword_loc, const YYLTYPE* end_keyword_loc); static rb_node_unless_t *rb_node_unless_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, NODE *nd_else, const YYLTYPE *loc, const YYLTYPE *keyword_loc, const YYLTYPE *then_keyword_loc, const YYLTYPE *end_keyword_loc); static rb_node_case_t *rb_node_case_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *case_keyword_loc, const YYLTYPE *end_keyword_loc); static rb_node_case2_t *rb_node_case2_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *case_keyword_loc, const YYLTYPE *end_keyword_loc); @@ -1166,7 +1166,7 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_SCOPE(a,b,loc) (NODE *)rb_node_scope_new(p,a,b,loc) #define NEW_SCOPE2(t,a,b,loc) (NODE *)rb_node_scope_new2(p,t,a,b,loc) #define NEW_BLOCK(a,loc) (NODE *)rb_node_block_new(p,a,loc) -#define NEW_IF(c,t,e,loc) (NODE *)rb_node_if_new(p,c,t,e,loc) +#define NEW_IF(c,t,e,loc,ik_loc,tk_loc,ek_loc) (NODE *)rb_node_if_new(p,c,t,e,loc,ik_loc,tk_loc,ek_loc) #define NEW_UNLESS(c,t,e,loc,k_loc,t_loc,e_loc) (NODE *)rb_node_unless_new(p,c,t,e,loc,k_loc,t_loc,e_loc) #define NEW_CASE(h,b,loc,ck_loc,ek_loc) (NODE *)rb_node_case_new(p,h,b,loc,ck_loc,ek_loc) #define NEW_CASE2(b,loc,ck_loc,ek_loc) (NODE *)rb_node_case2_new(p,b,loc,ck_loc,ek_loc) @@ -1387,7 +1387,7 @@ static NODE* cond(struct parser_params *p, NODE *node, const YYLTYPE *loc); static NODE* method_cond(struct parser_params *p, NODE *node, const YYLTYPE *loc); #define new_nil(loc) NEW_NIL(loc) static NODE *new_nil_at(struct parser_params *p, const rb_code_position_t *pos); -static NODE *new_if(struct parser_params*,NODE*,NODE*,NODE*,const YYLTYPE*); +static NODE *new_if(struct parser_params*,NODE*,NODE*,NODE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*); static NODE *new_unless(struct parser_params*,NODE*,NODE*,NODE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*); static NODE *logop(struct parser_params*,ID,NODE*,NODE*,const YYLTYPE*,const YYLTYPE*); @@ -3159,7 +3159,7 @@ stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem } | stmt modifier_if expr_value { - $$ = new_if(p, $3, remove_begin($1), 0, &@$); + $$ = new_if(p, $3, remove_begin($1), 0, &@$, &@2, &NULL_LOC, &NULL_LOC); fixpos($$, $3); /*% ripper: if_mod!($:3, $:1) %*/ } @@ -4044,7 +4044,7 @@ arg : lhs '=' lex_ctxt arg_rhs | arg '?' arg '\n'? ':' arg { value_expr($1); - $$ = new_if(p, $1, $3, $6, &@$); + $$ = new_if(p, $1, $3, $6, &@$, &NULL_LOC, &@5, &NULL_LOC); fixpos($$, $1); /*% ripper: ifop!($:1, $:3, $:6) %*/ } @@ -4488,7 +4488,10 @@ primary : inline_primary if_tail k_end { - $$ = new_if(p, $2, $4, $5, &@$); + if ($5 && nd_type_p($5, NODE_IF)) + RNODE_IF($5)->end_keyword_loc = @6; + + $$ = new_if(p, $2, $4, $5, &@$, &@1, &@3, &@6); fixpos($$, $2); /*% ripper: if!($:2, $:4, $:5) %*/ } @@ -4900,7 +4903,7 @@ if_tail : opt_else compstmt if_tail { - $$ = new_if(p, $2, $4, $5, &@$); + $$ = new_if(p, $2, $4, $5, &@$, &@1, &@3, &NULL_LOC); fixpos($$, $2); /*% ripper: elsif!($:2, $:4, $:5) %*/ } @@ -5465,7 +5468,7 @@ p_cases : opt_else p_top_expr : p_top_expr_body | p_top_expr_body modifier_if expr_value { - $$ = new_if(p, $3, $1, 0, &@$); + $$ = new_if(p, $3, $1, 0, &@$, &@2, &NULL_LOC, &NULL_LOC); fixpos($$, $3); /*% ripper: if_mod!($:3, $:1) %*/ } @@ -11512,12 +11515,15 @@ rb_node_yield_new(struct parser_params *p, NODE *nd_head, const YYLTYPE *loc) } static rb_node_if_t * -rb_node_if_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, NODE *nd_else, const YYLTYPE *loc) +rb_node_if_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, NODE *nd_else, const YYLTYPE *loc, const YYLTYPE* if_keyword_loc, const YYLTYPE* then_keyword_loc, const YYLTYPE* end_keyword_loc) { rb_node_if_t *n = NODE_NEWNODE(NODE_IF, rb_node_if_t, loc); n->nd_cond = nd_cond; n->nd_body = nd_body; n->nd_else = nd_else; + n->if_keyword_loc = *if_keyword_loc; + n->then_keyword_loc = *then_keyword_loc; + n->end_keyword_loc = *end_keyword_loc; return n; } @@ -14322,11 +14328,11 @@ new_nil_at(struct parser_params *p, const rb_code_position_t *pos) } static NODE* -new_if(struct parser_params *p, NODE *cc, NODE *left, NODE *right, const YYLTYPE *loc) +new_if(struct parser_params *p, NODE *cc, NODE *left, NODE *right, const YYLTYPE *loc, const YYLTYPE* if_keyword_loc, const YYLTYPE* then_keyword_loc, const YYLTYPE* end_keyword_loc) { if (!cc) return right; cc = cond0(p, cc, COND_IN_COND, loc, true); - return newline_node(NEW_IF(cc, left, right, loc)); + return newline_node(NEW_IF(cc, left, right, loc, if_keyword_loc, then_keyword_loc, end_keyword_loc)); } static NODE* diff --git a/rubyparser.h b/rubyparser.h index 3d594a06cf..2598f484ab 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -265,6 +265,9 @@ typedef struct RNode_IF { struct RNode *nd_cond; struct RNode *nd_body; struct RNode *nd_else; + rb_code_location_t if_keyword_loc; + rb_code_location_t then_keyword_loc; + rb_code_location_t end_keyword_loc; } rb_node_if_t; typedef struct RNode_UNLESS { diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index edfc25c43d..dcbd8d6b58 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1376,6 +1376,24 @@ dummy assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]]) end + def test_if_locations + node = ast_parse("if cond then 1 else 2 end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 25], [1, 0, 1, 2], [1, 8, 1, 12], [1, 22, 1, 25]]) + + node = ast_parse("1 if 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4], nil, nil]) + + node = ast_parse("if 1; elsif 2; else end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 23], [1, 0, 1, 2], [1, 4, 1, 5], [1, 20, 1, 23]]) + assert_locations(node.children[-1].children[-1].locations, [[1, 6, 1, 19], [1, 6, 1, 11], [1, 13, 1, 14], [1, 20, 1, 23]]) + + node = ast_parse("true ? 1 : 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 12], nil, [1, 9, 1, 10], nil]) + + node = ast_parse("case a; in b if c; end") + assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil]) + end + def test_next_locations node = ast_parse("loop { next 1 }") assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]])