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
This commit is contained in:
Raphael Amiard
2018-02-15 11:43:55 +01:00
parent 54c79a6c39
commit 5e09063757
6 changed files with 129 additions and 47 deletions

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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
</%self:case_dispatch>
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>
<%def name="default()">
return False;
</%def>
</%self:case_dispatch>
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>
<%def name="default()">
return Self.Is_Incomplete;
</%def>
</%self:case_dispatch>
end Snaps_At_End;
-------------
-- Parents --
-------------

View File

@@ -18,9 +18,9 @@ FileNode[1:1-7:1]
| | | | | |default_value: <null>
| | | | | 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: <null>

View File

@@ -8,16 +8,16 @@ main.py: Running...
[PROPERTIES] Decl.internal_env_mappings_0 (<Decl main.txt:1:1-1:5>):
[PROPERTIES] Result: (F_Key => "a", F_Val => <Decl main.txt:1:1-1:5>)
[PROPERTIES] Decl.internal_md_1 (<Decl main.txt:1:6-1:12>):
[PROPERTIES] HasPlus.p_as_bool (<HasPlusAbsent main.txt:1:6-1:6>):
[PROPERTIES] HasPlusAbsent.p_as_bool (<HasPlusAbsent main.txt:1:6-1:6>):
[PROPERTIES] HasPlus.p_as_bool (<HasPlusAbsent main.txt:1:5-1:5>):
[PROPERTIES] HasPlusAbsent.p_as_bool (<HasPlusAbsent main.txt:1:5-1:5>):
[PROPERTIES] Result: False
[PROPERTIES] Result: False
[PROPERTIES] Result: (F_B => False)
[PROPERTIES] Decl.internal_env_mappings_0 (<Decl main.txt:1:6-1:12>):
[PROPERTIES] Result: (F_Key => "b", F_Val => <Decl main.txt:1:6-1:12>)
[PROPERTIES] Decl.internal_md_1 (<Decl main.txt:1:13-1:18>):
[PROPERTIES] HasPlus.p_as_bool (<HasPlusPresent main.txt:1:13-1:13>):
[PROPERTIES] HasPlusPresent.p_as_bool (<HasPlusPresent main.txt:1:13-1:13>):
[PROPERTIES] HasPlus.p_as_bool (<HasPlusPresent main.txt:1:12-1:12>):
[PROPERTIES] HasPlusPresent.p_as_bool (<HasPlusPresent main.txt:1:12-1:12>):
[PROPERTIES] Result: True
[PROPERTIES] Result: True
[PROPERTIES] Result: (F_B => True)

View File

@@ -1,18 +1,18 @@
Regular node: <Param 1:1-1:2>
Regular node: <Name 1:1-1:2>
Ghost node: <EnumDefault 2:1-2:1>
Ghost node: <PlusQualifierAbsent 2:1-2:1>
Ghost node: <EnumDefault 1:2-1:2>
Ghost node: <PlusQualifierAbsent 1:2-1:2>
Regular node: <Param 2:1-2:7>
Regular node: <Name 2:1-2:2>
Regular node: <EnumNull 2:3-2:7>
Ghost node: <PlusQualifierAbsent 3:1-3:1>
Ghost node: <PlusQualifierAbsent 2:7-2:7>
Regular node: <Param 3:1-3:2>
Regular node: <Name 3:1-3:2>
Ghost node: <EnumDefault 4:1-4:1>
Ghost node: <PlusQualifierAbsent 4:1-4:1>
Ghost node: <EnumDefault 3:2-3:2>
Ghost node: <PlusQualifierAbsent 3:2-3:2>
Regular node: <Param 4:1-4:7>
Regular node: <Name 4:1-4:2>
Regular node: <EnumNull 4:3-4:7>
Ghost node: <PlusQualifierAbsent 4:8-4:8>
Ghost node: <PlusQualifierAbsent 4:7-4:7>
Done.
Done