From 5e0906375725755ab12bad4649aaa5c55f51b433 Mon Sep 17 00:00:00 2001 From: Raphael Amiard Date: Thu, 15 Feb 2018 11:43:55 +0100 Subject: [PATCH] Q322-037: Implement proper snapping Users can annotate node that they want to have snapping sloc behavior. Additionally, incomplete nodes snap at the end. Change-Id: Ib3b8d3337d1356059b01694b547265386afd814f --- langkit/compiled_types.py | 28 +++++ langkit/dsl.py | 11 +- .../pkg_implementation_body_ada.mako | 113 ++++++++++++------ .../tests/contrib/python_parse_1/test.out | 4 +- testsuite/tests/properties/logging/test.out | 8 +- .../tests/python-api/ghost_nodes/test.out | 12 +- 6 files changed, 129 insertions(+), 47 deletions(-) diff --git a/langkit/compiled_types.py b/langkit/compiled_types.py index 746a9e175..f6a3433af 100644 --- a/langkit/compiled_types.py +++ b/langkit/compiled_types.py @@ -2303,6 +2303,34 @@ class ASTNodeType(BaseStructType): )), ] + def snaps(self, anchor_end): + """ + Whether this node type snaps. To see what this means, see Annotations + documentation. + """ + from langkit.parsers import _Transform + + if not self.parser: + return False + + i = -1 if anchor_end else 0 + + return self.annotations.snaps or ( + isinstance(self.parser, _Transform) + and self.parser.parser.parsers + and self.parser.parser.parsers[i].get_type() + and self.parser.parser.parsers[i].get_type().is_ast_node + and self.parser.parser.parsers[i].get_type().snaps(anchor_end) + ) + + @property + def snaps_at_start(self): + return self.snaps(False) + + @property + def snaps_at_end(self): + return self.snaps(True) + # We tag the ASTNodeType class as abstract here, because of the circular # dependency between the @abstract decorator and the ASTNodeType class, which diff --git a/langkit/dsl.py b/langkit/dsl.py index c89bda804..758da8254 100644 --- a/langkit/dsl.py +++ b/langkit/dsl.py @@ -302,7 +302,7 @@ inherited_annotation = inherited_property(lambda s: s.get_parent_annotations()) class Annotations(object): def __init__(self, repr_name=None, generic_list_type=None, warn_on_node=None, rebindable=False, - custom_short_image=False): + custom_short_image=False, snaps=False): """ Constructor for a node's annotations. @@ -318,12 +318,17 @@ class Annotations(object): declaration and the definition of a function called `[NODE_NAME]_Short_Image` that takes the node in argument and that returns a `Text_Type` value. + :param bool snaps: Whether this node's SLOCs are supposed to snap or + not. Snapping designates the behavior where the start SLOC will be + anchored to the previous token's end SLOC rather than the node's + first token start SLOC, and conversely for the end SLOC. """ self.repr_name = repr_name self.generic_list_type = generic_list_type self._warn_on_node = warn_on_node self._rebindable = rebindable self.custom_short_image = custom_short_image + self._snaps = snaps @inherited_annotation def warn_on_node(self): @@ -364,6 +369,10 @@ class Annotations(object): bn = self.node.base return bn.annotations if bn else None + @inherited_annotation + def snaps(self): + return self._snaps + class _ASTNodeMetaclass(type): """ diff --git a/langkit/templates/pkg_implementation_body_ada.mako b/langkit/templates/pkg_implementation_body_ada.mako index 56a944c3a..b4a060092 100644 --- a/langkit/templates/pkg_implementation_body_ada.mako +++ b/langkit/templates/pkg_implementation_body_ada.mako @@ -101,6 +101,12 @@ package body ${ada_lib_name}.Analysis.Implementation is procedure Destroy (Env : in out Lexical_Env_Access); + function Snaps_At_Start + (Self : access ${root_node_value_type}'Class) return Boolean; + + function Snaps_At_End + (Self : access ${root_node_value_type}'Class) return Boolean; + ------------------- -- Is_Token_Node -- ------------------- @@ -599,51 +605,56 @@ package body ${ada_lib_name}.Analysis.Implementation is (Node : access ${root_node_value_type}'Class; Snap : Boolean := False) return Source_Location_Range is - TDH : Token_Data_Handler renames - Convert (Node.Unit).TDH; - Sloc_Start, Sloc_End : Source_Location; + pragma Unreferenced (Snap); + + type Token_Anchor is (T_Start, T_End); + type Token_Pos is record + Pos : Token_Index; + Anchor : Token_Anchor; + end record; + + TDH : Token_Data_Handler renames Convert (Node.Unit).TDH; + Token_Start, Token_End : Token_Pos; function Get - (Index : Token_Index) return Lexer.Token_Data_Type is + (Index : Token_Index) return Lexer.Token_Data_Type + is (Get_Token (TDH, Index)); + function Sloc + (T : Token_Pos) return Source_Location + is + (if T.Anchor = T_Start + then Start_Sloc (Get (T.Pos).Sloc_Range) + else End_Sloc (Get (T.Pos).Sloc_Range)); + begin if Node.Is_Synthetic then return Sloc_Range (Node.Parent); end if; - -- Snapping: We'll go one token before the start token, and one token - -- after the end token, and the sloc range will extend from the end of - -- the start token to the start of the end token, including any - -- whitespace and trivia that might be surrounding the node. - -- - -- TODO: Only nodes we're gonna try to snap are nodes with default - -- anchors. However, we can make the logic more specific, eg: - -- - -- * If the start anchor is beginning, then snap the start sloc. - -- - -- * If the end anchor is ending, then snap the end sloc. - -- - -- This way composite cases can work too. - - if Snap then - declare - Tok_Start : constant Token_Index := - Token_Index'Max (Node.Token_Start_Index - 1, 0); - Tok_End : constant Token_Index := - Token_Index'Min (Node.Token_End_Index + 1, Last_Token (TDH)); - begin - Sloc_Start := End_Sloc (Get (Tok_Start).Sloc_Range); - Sloc_End := Start_Sloc (Get (Tok_End).Sloc_Range); - end; + if Node.Is_Ghost then + Token_Start := (if Node.Token_Start_Index = 1 + then (1, T_Start) + else (Node.Token_Start_Index - 1, T_End)); + Token_End := Token_Start; else - Sloc_Start := Start_Sloc (Get (Node.Token_Start_Index).Sloc_Range); - Sloc_End := - (if Node.Token_End_Index /= No_Token_Index - then End_Sloc (Get (Node.Token_End_Index).Sloc_Range) - else Start_Sloc (Get (Node.Token_Start_Index).Sloc_Range)); + Token_Start := (Node.Token_Start_Index, T_Start); + Token_End := (Node.Token_End_Index, T_End); end if; - return Make_Range (Sloc_Start, Sloc_End); + + if Snaps_At_Start (Node) + and then not Node.Is_Ghost + and then Token_Start.Pos /= 1 + then + Token_Start := (Token_Start.Pos - 1, T_End); + end if; + + if Snaps_At_End (Node) and then Token_End.Pos /= Last_Token (TDH) then + Token_End := (Token_End.Pos + 1, T_Start); + end if; + + return Make_Range (Sloc (Token_Start), Sloc (Token_End)); end Sloc_Range; ------------ @@ -1065,6 +1076,40 @@ package body ${ada_lib_name}.Analysis.Implementation is end Short_Image; + -------------------- + -- Snaps_At_Start -- + -------------------- + + function Snaps_At_Start + (Self : access ${root_node_value_type}'Class) return Boolean is + begin + <%self:case_dispatch pred="${lambda n: n.snaps_at_start}"> + <%def name="action(node)"> + return True; + + <%def name="default()"> + return False; + + + end Snaps_At_Start; + + ------------------ + -- Snaps_At_End -- + ------------------ + + function Snaps_At_End + (Self : access ${root_node_value_type}'Class) return Boolean is + begin + <%self:case_dispatch pred="${lambda n: n.snaps_at_end}"> + <%def name="action(node)"> + return True; + + <%def name="default()"> + return Self.Is_Incomplete; + + + end Snaps_At_End; + ------------- -- Parents -- ------------- diff --git a/testsuite/tests/contrib/python_parse_1/test.out b/testsuite/tests/contrib/python_parse_1/test.out index e02582d1e..2dca4f916 100644 --- a/testsuite/tests/contrib/python_parse_1/test.out +++ b/testsuite/tests/contrib/python_parse_1/test.out @@ -18,9 +18,9 @@ FileNode[1:1-7:1] | | | | | |default_value: | | | | | SingleParam[2:13-2:14] | | | | | |is_varargs: -| | | | | | VarArgsFlagAbsent[2:13-2:13] +| | | | | | VarArgsFlagAbsent[2:12-2:12] | | | | | |is_kwargs: -| | | | | | KwArgsFlagAbsent[2:13-2:13] +| | | | | | KwArgsFlagAbsent[2:12-2:12] | | | | | |name: | | | | | | Id[2:13-2:14]: b | | | | | |default_value: diff --git a/testsuite/tests/properties/logging/test.out b/testsuite/tests/properties/logging/test.out index 50be4ac52..e309a0618 100644 --- a/testsuite/tests/properties/logging/test.out +++ b/testsuite/tests/properties/logging/test.out @@ -8,16 +8,16 @@ main.py: Running... [PROPERTIES] Decl.internal_env_mappings_0 (): [PROPERTIES] Result: (F_Key => "a", F_Val => ) [PROPERTIES] Decl.internal_md_1 (): - [PROPERTIES] HasPlus.p_as_bool (): - [PROPERTIES] HasPlusAbsent.p_as_bool (): + [PROPERTIES] HasPlus.p_as_bool (): + [PROPERTIES] HasPlusAbsent.p_as_bool (): [PROPERTIES] Result: False [PROPERTIES] Result: False [PROPERTIES] Result: (F_B => False) [PROPERTIES] Decl.internal_env_mappings_0 (): [PROPERTIES] Result: (F_Key => "b", F_Val => ) [PROPERTIES] Decl.internal_md_1 (): - [PROPERTIES] HasPlus.p_as_bool (): - [PROPERTIES] HasPlusPresent.p_as_bool (): + [PROPERTIES] HasPlus.p_as_bool (): + [PROPERTIES] HasPlusPresent.p_as_bool (): [PROPERTIES] Result: True [PROPERTIES] Result: True [PROPERTIES] Result: (F_B => True) diff --git a/testsuite/tests/python-api/ghost_nodes/test.out b/testsuite/tests/python-api/ghost_nodes/test.out index 57c5d1e0f..675bf0ae4 100644 --- a/testsuite/tests/python-api/ghost_nodes/test.out +++ b/testsuite/tests/python-api/ghost_nodes/test.out @@ -1,18 +1,18 @@ Regular node: Regular node: -Ghost node: -Ghost node: +Ghost node: +Ghost node: Regular node: Regular node: Regular node: -Ghost node: +Ghost node: Regular node: Regular node: -Ghost node: -Ghost node: +Ghost node: +Ghost node: Regular node: Regular node: Regular node: -Ghost node: +Ghost node: Done. Done