1/*  Part of Refactoring Tools for SWI-Prolog
    2
    3    Author:        Edison Mera
    4    E-mail:        efmera@gmail.com
    5    WWW:           https://github.com/edisonm/refactor
    6    Copyright (C): 2013, Process Design Center, Breda, The Netherlands.
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(fix_termpos,
   36          [ fix_subtermpos/1,
   37            fix_subtermpos/2,
   38            fix_termpos/1,
   39            fix_termpos/2,
   40            term_innerpos/4
   41          ]).   42
   43:- use_module(library(apply)).   44:- use_module(library(lists)).   45:- use_module(library(option)).   46:- use_module(library(ref_context)).   47:- use_module(library(ref_message)).   48:- use_module(library(seek_text)).
 term_innerpos(OFrom, OTo, InnerFrom, InnerTo)
Contains the inner positions of a term, that exclude comments and some extra parenthesis.
   55:- thread_local term_innerpos/4.
 fix_termpos(@TermPos, +Options) is det
Applies fix_subtermpos recursivelly and extends the boundaries of the first term position from the first comment up to just before the ending dot.

The subterm positions are adjusted recursively according to the Options as follows:

   75fix_termpos(TermPos) :-
   76    fix_termpos(TermPos, []).
   77
   78fix_termpos(TermPos, Options) :-
   79    retractall(term_innerpos(_, _, _, _)),
   80    option(subterm_boundary(Boundary), Options, comment),
   81    fix_subtermpos_rec(TermPos, Boundary),
   82    fix_termouterpos(TermPos).
 fix_subtermpos(@TermPos, Boundaries) is det
Takes a subterm position, as returned by the subterm_positions option of read_term/2 and increases its precision, avoiding some minor mistmatches with the text, that for a refactoring tool is instrumental. This method also ensures that the minimal required parenthesis enclosing a term are contained in its scope, widening the positions 1 and 2 of the given term position specifier. The current implementation is aware of comments and extra parenthesis, asserting such information in term_innerpos/4 facts.
To be done
- This implementation has performance issues, needs optimization.
   97fix_subtermpos(Pos) :-
   98    fix_subtermpos(Pos, []).
   99
  100fix_subtermpos(Pos, _) :- var(Pos), !.
  101fix_subtermpos(Pos, Options) :-
  102    retractall(term_innerpos(_, _, _, _)),
  103    option(subterm_boundary(Boundary), Options, comment),
  104    fix_subtermpos_rec(Pos, Boundary),
  105    arg(1, Pos, From),
  106    arg(2, Pos, To),
  107    assertz(term_innerpos(From, To, From, To)).
 fix_termouterpos(@TermPos) is det
Extends the boundaries of the first term position from the first comment up to just before the ending dot.
  114fix_termouterpos(TermPos) :-
  115    arg(1, TermPos, From),
  116    ( refactor_context(comments, Comments)
  117    ->true
  118    ; Comments = []
  119    ),
  120    ( Comments = [Pos-_|_],
  121      stream_position_data(char_count, Pos, From1),
  122      From1 < From
  123    ->CommentFrom = From1
  124    ; CommentFrom = From
  125    ),
  126    refactor_context(text, Text),
  127    string_length(Text, L),
  128    % Now move to the left until the previous last one newline
  129    ( seek1_char_left(Text, ".", CommentFrom, DotFrom)
  130    ->( seek_sub_string(Text, "\n", 1, L, DotFrom, NLFrom),
  131        NLFrom < From
  132      ->succ(NLFrom, OuterFrom)
  133      ; succ(DotFrom, OuterFrom)
  134      )
  135    ; OuterFrom = CommentFrom
  136    ),
  137    arg(2, TermPos, To),
  138    ( append(_, [Pos-Comment], Comments),
  139      stream_position_data(char_count, Pos, To1),
  140      string_length(Comment, CL),
  141      To2 is To1 + CL,
  142      To2 > To
  143    ->To3 = To2
  144    ; To3 = To
  145    ),
  146    once(seek_sub_string(Text, ".", 1, L, To3, DotTo)),
  147    ( seek_sub_string(Text, "\n", 1, L, DotTo, NLTo)
  148      % TBD: this is assuming that from . to nl we only have comments or spaces
  149    ->succ(NLTo, OuterTo)
  150    ; succ(DotTo, OuterTo)
  151    ),
  152    nb_setarg(1, TermPos, OuterFrom),
  153    nb_setarg(2, TermPos, OuterTo),
  154    assertz(term_innerpos(OuterFrom, OuterTo, From, To)).
  155
  156fix_subtermpos_rec(Pos, _) :- var(Pos), !. % Nothing to fix
  157fix_subtermpos_rec(Pos, Boundary) :-
  158    Pos = term_position(From1, To1, FFrom, FTo, PosL),
  159    !,
  160    fix_subtermpos_from_to(Boundary, From1, To1, FFrom, FTo, From, To, PosL),
  161    nb_setarg(1, Pos, From),
  162    nb_setarg(2, Pos, To).
  163fix_subtermpos_rec(Pos, Boundary) :-
  164    Pos = key_value_position(From1, To1, SFrom, STo, _, KPos, VPos),
  165    !,
  166    fix_subtermpos_from_to(Boundary, From1, To1, SFrom, STo, From, To, [KPos, VPos]),
  167    nb_setarg(1, Pos, From),
  168    nb_setarg(2, Pos, To).
  169/*
  170fix_subtermpos_rec(Pos, Boundary) :-
  171    fail,
  172    Pos = dict_position(From1, To1, FFrom, FTo, PosL),
  173    !,
  174    fix_subtermpos_from_to(Boundary, From1, To1, FFrom, FTo, From, To, PosL),
  175    nb_setarg(1, Pos, From),
  176    nb_setarg(2, Pos, To).
  177*/
  178fix_subtermpos_rec(dict_position(_, _, _, TypeTo, KVPos), Boundary) :-
  179    refactor_context(text, Text),
  180    succ(TypeTo, TypeTo1),
  181    foldl(fix_termpos_from_left_comm(Boundary, Text), KVPos, TypeTo1, _).
  182fix_subtermpos_rec(_-_, _).
  183fix_subtermpos_rec(string_position(_, _), _).
  184fix_subtermpos_rec(brace_term_position(From, _, Arg), Boundary) :-
  185    refactor_context(text, Text),
  186    succ(From, From1),
  187    fix_termpos_from_left(Boundary, Text, Arg, From1, _).
  188fix_subtermpos_rec(parentheses_term_position(From, _, Arg), Boundary) :-
  189    refactor_context(text, Text),
  190    % BUG: we can not assume that the next character is '(', since a comment
  191    % could come next, which is included in the From-To interval, for instance
  192    % (/**/(Term)), but surprisinlgy this problem doesn't happen with braces {}
  193    % (see test seekn_parenthesis_right.plt)
  194    include_comments_right(Text, From, FixedFrom),
  195    succ(FixedFrom, From1),
  196    fix_termpos_from_left(Boundary, Text, Arg, From1, _).
  197% Note: don't assume that a list is between brackets [], because this clause is
  198% also used to process list of clauses:
  199fix_subtermpos_rec(list_position(From, To, Elms, Tail), Boundary) :-
  200    refactor_context(text, Text),
  201    foldl(fix_termpos_from_left_comm(Boundary, Text), Elms, From, To1),
  202    ( Tail = none
  203    ->true
  204    ; once(seek_sub_string(Text, "|", 1, To, To1, ToL)),
  205      succ(ToL, FromT),
  206      fix_termpos_from_left(Boundary, Text, Tail, FromT, _)
  207    ).
  208
  209rcomment_bound(From, To) :-
  210    refactor_context(comments, CommentL),
  211    reverse(CommentL, CommentR),
  212    comment_bound(CommentR, From, To).
  213
  214count_sub_string(Text, From1, To1, SubText, SubTextN, From, To, N) :-
  215    ( seek_sub_string(Text, SubText, SubTextN, To1, From1, From2)
  216    ->From = From2,
  217      To2 is From2 + SubTextN,
  218      ( To2 =< To1
  219      ->S = s(1, To2),
  220        forall(seek_sub_string(Text, SubText, SubTextN, To1, To2, To3),
  221               ( arg(1, S, N1),
  222                 succ(N1, N2),
  223                 nb_setarg(1, S, N2),
  224                 To4 is To3 + SubTextN,
  225                 nb_setarg(2, S, To4)
  226               )),
  227        arg(1, S, N),
  228        arg(2, S, To)
  229      ; N = 1,
  230        To = To2
  231      )
  232    ; From = To1,
  233      To = From1,
  234      N = 0
  235    ).
  236
  237
  238seek1_parenthesis_left(Text, F1, F) :-
  239    comment_bound(F2, F1),
  240    !,
  241    seek1_parenthesis_left(Text, F2, F).
  242seek1_parenthesis_left(Text, F1, F) :-
  243    succ(F2, F1),
  244    ( sub_string(Text, F2, _, _, "(")
  245    ->F = F2
  246    ; seek1_parenthesis_left(Text, F2, F)
  247    ).
  248
  249seekn_parenthesis_left(0,  _,    F,  F) :- !.
  250seekn_parenthesis_left(N1, Text, F1, F) :-
  251    N1>0,
  252    seek1_parenthesis_left(Text, F1, F2),
  253    succ(N, N1),
  254    seekn_parenthesis_left(N, Text, F2, F).
  255
  256include_comments_left(subterm,   _, From, From).
  257include_comments_left(rightcomm, _, From, From).
  258include_comments_left(leftcomm, Text, To, From) :- include_comments_left(Text, To, From).
  259include_comments_left(comment,  Text, To, From) :- include_comments_left(Text, To, From).
  260
  261include_comments_left(Text, To, From) :-
  262    S = s(To),
  263    ( rcomment_bound(FromC, ToC),
  264      arg(1, S, From1),
  265      ToC =< From1,
  266      ( L is From1 - ToC,
  267        sub_string(Text, ToC, L, _, Text1),
  268        \+ ( sub_string(Text1, _, 1, _, Char),
  269             \+ member(Char, [" ", "\t", "\n"])
  270           )
  271      ->nb_setarg(1, S, FromC),
  272        fail
  273      ; ToC = From1
  274      ->nb_setarg(1, S, FromC),
  275        !,
  276        fail
  277      ; !,
  278        fail
  279      )
  280    ->true
  281    ; true
  282    ),
  283    arg(1, S, From).
  284
  285include_comments_right(subterm,  _, To, To).
  286include_comments_right(leftcomm, _, To, To).
  287include_comments_right(rightcomm, Text, From, To) :- include_comments_right(Text, From, To).
  288include_comments_right(comment,   Text, From, To) :- include_comments_right(Text, From, To).
  289
  290include_comments_right(Text, From, To) :-
  291    S = s(From),
  292    ( comment_bound(FromC, ToC),
  293      arg(1, S, To1),
  294      To1 =< FromC,
  295      ( L is FromC - To1,
  296        sub_string(Text, To1, L, _, Text1),
  297        \+ ( sub_string(Text1, _, 1, _, Char),
  298             \+ member(Char, [" ", "\t", "\n"])
  299           )
  300      ->nb_setarg(1, S, ToC),
  301        fail
  302      ; To1 = FromC
  303      ->nb_setarg(1, S, ToC),
  304        !,
  305        fail
  306      ; !,
  307        fail
  308      )
  309    ->true
  310    ; true
  311    ),
  312    arg(1, S, To).
  313
  314seekn_parenthesis_right(N, Text, L, T1, T) :-
  315    seekn_char_right(N, Text, L, ")", T1, T).
  316
  317fix_boundaries_from_right(Boundary, Text, Pos, To1, From2, To3, From, To) :-
  318    arg(2, Pos, To2),
  319    ( To1 < To2
  320    ->RL is To2 - To1,
  321      sub_string(Text, To1, RL, _, TextL),
  322      with_termpos(refactor_message(warning, format("Misplaced text --> `~w'", [TextL])), Pos)
  323    ; true
  324    ),
  325    count_sub_string(Text, To2, To1, ")", 1, _, To3, N),
  326    include_comments_right(Boundary, Text, To3, To),
  327    arg(1, Pos, From1),
  328    seekn_parenthesis_left(N, Text, From1, From2),
  329    From = From2.
  330
  331fix_termpos_from_right(Boundary, Text, To1, Pos ) :-
  332    fix_subtermpos_rec(Pos, Boundary),
  333    fix_boundaries_from_right(Boundary, Text, Pos, To1, From2, To2, From, To),
  334    nb_setarg(1, Pos, From),
  335    nb_setarg(2, Pos, To),
  336    assertz(term_innerpos(From, To, From2, To2)).
  337
  338fix_termpos_from_left(Boundary, Text, Pos, From1, To) :-
  339    fix_subtermpos_rec(Pos, Boundary),
  340    fix_boundaries_from_left(Boundary, Text, Pos, From1, From2, From, To),
  341    nb_setarg(1, Pos, From),
  342    nb_setarg(2, Pos, To),
  343    assertz(term_innerpos(From, To, From2, To)).
  344
  345fix_termpos_from_left_comm(Boundary, Text, Pos, From1, To) :-
  346    fix_subtermpos_rec(Pos, Boundary),
  347    fix_boundaries_from_left(Boundary, Text, Pos, From1, From2, From, To2),
  348    include_comments_right(Boundary, Text, To2, To),
  349    nb_setarg(1, Pos, From),
  350    nb_setarg(2, Pos, To),
  351    assertz(term_innerpos(From, To, From2, To2)).
  352
  353fix_boundaries_from_left(Boundary, Text, Pos, From1, From3, From, To) :-
  354    arg(1, Pos, From2),
  355    ( From2 < From1
  356    ->RL is From1 - From2,
  357      sub_string(Text, From2, RL, _, TextL),
  358      with_termpos(
  359          refactor_message(
  360              warning,
  361              format("Misplaced text <-- `~w' (~w)",
  362                     [TextL,
  363                      fix_boundaries_from_left(Boundary, _, Pos, From1, From3, From, To)])),
  364          Pos)
  365    ; true
  366    ),
  367    count_sub_string(Text, From1, From2, "(", 1, From3, _, N),
  368    include_comments_left(Boundary, Text, From3, From),
  369    arg(2, Pos, To1),
  370    string_length(Text, L),
  371    seekn_parenthesis_right(N, Text, L, To1, To).
  372
  373fix_subtermpos_from_to(Boundary, From1, To1, FFrom, FTo, From, To, PosL) :-
  374    refactor_context(text, Text),
  375    sub_string(Text, FTo, 1, _, Char),
  376    ( PosL = [LPos, RPos ],
  377      arg(2, LPos, LTo),
  378      LTo =< FFrom
  379    ->fix_termpos_from_right(Boundary, Text, FFrom, LPos),
  380      fix_termpos_from_left(Boundary, Text, RPos, FTo, _),
  381      arg(1, LPos, From),
  382      arg(2, RPos, To)
  383    ; PosL = [Pos],
  384      arg(1, Pos, FromR),
  385      FTo =< FromR,
  386      Char \= "("
  387    ->fix_termpos_from_left(Boundary, Text, Pos, FTo, _),
  388      From = From1,
  389      arg(2, Pos, To)
  390    ; succ(FTo, FTo1),
  391      foldl(fix_termpos_from_left_comm(Boundary, Text), PosL, FTo1, _),
  392      From = From1,
  393      To = To1
  394    )