1:- module(onepointfour_basics_stringy_overwrite,
    2          [
    3            stringy_overwrite/5              % stringy_overwrite(BgText,FgText,FgPos,Result,ResultType)
    4           ,stringy_overwrite/7              % stringy_overwrite(+BgText,+FgText,+FgPos,+CutLeft,+CutRight,?Result,+ResultType)
    5           ,stringy_overwrite_using_chars/7  % stringy_overwrite_using_chars(+BgText,+FgText,+FgPos,+CutLeft,+CutRight,?Result,+ResultType)
    6           ,stringy_overwrite_using_runs/7   % stringy_overwrite_using_runs(+BgText,+FgText,+FgPos,+CutLeft,+CutRight,?Result,+ResultType)
    7          ]).    8
    9:- use_module(library('onepointfour_basics/checks.pl')).   10:- use_module(library('onepointfour_basics/space_stringy.pl')).   11:- use_module(library('onepointfour_basics/stringy_concat.pl')).   12:- use_module(library('onepointfour_basics/stringy_length.pl')).   13:- use_module(library('onepointfour_basics/stringy_morph.pl')).   14
   15/*  MIT License Follows (https://opensource.org/licenses/MIT)
   16
   17    Copyright 2021 David Tonhofer <ronerycoder@gluino.name>
   18
   19    Permission is hereby granted, free of charge, to any person obtaining
   20    a copy of this software and associated documentation files
   21    (the "Software"), to deal in the Software without restriction,
   22    including without limitation the rights to use, copy, modify, merge,
   23    publish, distribute, sublicense, and/or sell copies of the Software,
   24    and to permit persons to whom the Software is furnished to do so,
   25    subject to the following conditions:
   26
   27    The above copyright notice and this permission notice shall be
   28    included in all copies or substantial portions of the Software.
   29
   30    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   31    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   32    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   33    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   34    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   35    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   36    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   37*/
   38
   39/* pldoc ==================================================================== */

Overwrite a a background text BgText with foreground text FgText

Homepage for this code

https://github.com/dtonhofer/prolog_code/blob/main/unpacked/onepointfour_basics/README_stringy_overwrite.md

History

  1. 2020-07-XX: First version
  2. 2021-01-19: Review
  3. 2021-01-29: Changes in naming, review overwrite_intro_assertions/7
  4. 2021-05-27: Review
  5. 2021-06-06: Another review to use the (now completed) "checks"
  6. 2021-06-12: All test cases pass
  7. 2021-06-13: Code rearranged

*/

 stringy_overwrite(+BgText, +FgText, +FgPos, ?Result, +ResultType)
As overwrite/7, but cuts on both ends by default.
   62stringy_overwrite(BgText,FgText,FgPos,Result,ResultType) :-
   63   stringy_overwrite_using_runs(BgText,FgText,FgPos,true,true,Result,ResultType).
 overwrite(+BgText, +FgText, +FgPos, +CutLeft, +CutRight, ?Result, +ResultType)
Succeeds if Result is the outcome of overwriting BgText (a stringy) with FgText (a stringy), with FgText placed at position FgPos (relative to BgText) and resulting characters at position < 0 dropped if CutLeft is true and resulting characters at position >= length(BgText) dropped if CutRight is true. The Result is an atom if ResultType is atom and a string if ResultType is string. In case ResultType is uninstantiated and both BgText and FgText are of the same type, that type is used for Result.

CutLeft and CutRight must be one of true, false. ResultType must be one of atom, string.

   78stringy_overwrite(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType) :-
   79   stringy_overwrite_using_runs(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType).
   80
   81% Entry verification called by both overwrite_using_chars/7 and
   82% overwrite_using_runs/7.
   83
   84stringy_overwrite_entry(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType) :-
   85   check_that(BgText     , hard(stringy)),
   86   check_that(FgText     , hard(stringy)),
   87   check_that(FgPos      , hard(integer)),
   88   check_that(CutLeft    , hard(boolean)),
   89   check_that(CutRight   , hard(boolean)),
   90   check_that(Result     , break(var),hard(stringy)),         % throw if ResultType instantiated but bad type
   91   check_that(ResultType , break(var),hard(stringy_typeid)),  % throw if ResultType instantiated but not 'atom' or 'string'
   92   complete_result_type(BgText,FgText,Result,ResultType),     % succeeds with ResultType instantiated, or throws on missing info, or fails
   93   assertion(nonvar(ResultType)).
 overwrite_using_chars(+BgText, +FgText, +FgPos, +CutLeft, +CutRight, ?Result, +ResultType)
This implementation uses character-by-character processing and is slow but easy to verify for correctness.
  100stringy_overwrite_using_chars(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType) :-
  101   stringy_overwrite_entry(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType),
  102   stringy_charylist_morph(BgText,BgChars,_,chars),
  103   stringy_charylist_morph(FgText,FgChars,_,chars),
  104   stringy_length(BgText,BgLen),
  105   stringy_length(FgText,FgLen),
  106   PrelimStartPos is min(FgPos,0),
  107   PrelimEndPos   is max(BgLen,FgPos+FgLen),
  108   ((CutLeft == true)
  109    ->
  110    StartPos = 0
  111    ;
  112    StartPos = PrelimStartPos),
  113   ((CutRight == true)
  114    ->
  115    EndPos = BgLen
  116    ;
  117    EndPos = PrelimEndPos),
  118   FgEnd is FgPos+FgLen,
  119   collect(StartPos,EndPos,FgPos,FgEnd,FgChars,BgChars,BgLen,Tip,FinalFin),
  120   FinalFin=[], % close open list
  121   stringy_charylist_morph(Result,Tip,ResultType,_),
  122   !.
  123
  124collect(Pos,EndPos,_,_,_,_,_,Fin,Fin) :-
  125   Pos == EndPos,
  126   !.
  127
  128collect(Pos,EndPos,FgPos,FgEnd,FgChars,BgChars,BgLen,Fin,FinalFin) :-
  129   Pos < EndPos,
  130   !,
  131   (
  132      (FgPos=<Pos, Pos<FgEnd)
  133      ->
  134      (Index is Pos-FgPos,nth0(Index,FgChars,Char)) % use "foreground" character if possible
  135      ;
  136      (0=<Pos, Pos<BgLen)
  137      ->
  138      nth0(Pos,BgChars,Char)                        % otherwise use "background" character
  139      ;
  140      Char=' '                                      % otherwise use space as filler
  141   ),
  142   Fin=[Char|NewFin],
  143   PosPP is Pos+1,
  144   collect(PosPP,EndPos,FgPos,FgEnd,FgChars,BgChars,BgLen,NewFin,FinalFin).
 overwrite_using_runs(+BgText, +FgText, +FgPos, +CutLeft, +CutRight, ?Result, +ResultType)
This implementation uses run-of-character processing and is a bit hairy to verify, but fast.
  151stringy_overwrite_using_runs(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType) :-
  152   stringy_overwrite_entry(BgText,FgText,FgPos,CutLeft,CutRight,Result,ResultType),
  153   stringy_length(BgText,BgLen),
  154   stringy_length(FgText,FgLen),
  155   FgEnd is FgPos+FgLen,
  156   fg_completely_or_partially_on_positions_below_position0(FgText,FgPos,FgEnd,CutLeft,R1,ResultType),
  157   filler_between_end_of_fg_and_position0(FgEnd,CutLeft,R1,R2,ResultType),
  158   bg_visible_between_position0_and_start_of_fg(FgPos,BgLen,BgText,R2,R3,ResultType),
  159   fg_covering_bg(FgText,FgPos,FgEnd,BgLen,R3,R4,ResultType),
  160   bg_visible_between_end_of_fg_and_end_of_bg(BgText,FgEnd,BgLen,R4,R5,ResultType),
  161   filler_between_end_of_bg_and_start_of_fg(FgPos,BgLen,CutRight,R5,R6,ResultType),
  162   fg_completely_or_partially_on_the_right(FgText,FgPos,FgLen,FgEnd,BgLen,CutRight,R6,Result,ResultType).
  163
  164fg_completely_or_partially_on_positions_below_position0(FgText,FgPos,FgEnd,CutLeft,Rnew,ResultType) :-
  165   (CutLeft == true ; 0 =< FgPos)
  166   ->
  167   stringy_concat([],Rnew,ResultType)            % do nothing except returning the empty string/atom
  168   ;
  169   (Len is min(0,FgEnd)-FgPos,
  170    sub_atom(FgText,0,Len,_,Run),                % gives a string
  171    stringy_concat([Run],Rnew,ResultType)).      % gives sth corresponding to"ResultType"
  172
  173filler_between_end_of_fg_and_position0(FgEnd,CutLeft,Rprev,Rnew,ResultType) :-
  174   (CutLeft == true ; 0 =< FgEnd)
  175   ->
  176   (Rnew=Rprev) % do nothing
  177   ;
  178   (Len is -FgEnd,
  179    space_stringy(Len,Run,ResultType,hard),
  180    stringy_concat([Rprev,Run],Rnew,ResultType)). % gives sth corresponding to "ResultType"
  181
  182bg_visible_between_position0_and_start_of_fg(FgPos,BgLen,Bg,Rprev,Rnew,ResultType) :-
  183   (FgPos =< 0 ; BgLen == 0)
  184   ->
  185   (Rnew=Rprev) % do nothing
  186   ;
  187   (Len is min(BgLen,FgPos),
  188    sub_atom(Bg,0,Len,_,Run),                     % gives a string
  189    stringy_concat([Rprev,Run],Rnew,ResultType)). % gives sth corresponding to "ResultType"
  190
  191fg_covering_bg(FgText,FgPos,FgEnd,BgLen,Rprev,Rnew,ResultType) :-
  192   (FgEnd =< 0 ; BgLen =< FgPos)
  193   ->
  194   (Rnew=Rprev) % do nothing
  195   ;
  196   (StartPos     is max(0,FgPos),
  197    StartPosInFg is -min(0,FgPos),
  198    EndPos       is min(BgLen,FgEnd),
  199    Len          is EndPos-StartPos,
  200    sub_atom(FgText,StartPosInFg,Len,_,Run),       % gives an atom
  201    stringy_concat([Rprev,Run],Rnew,ResultType)).  % gives sth corresponding to "ResultType"
  202
  203bg_visible_between_end_of_fg_and_end_of_bg(BgText,FgEnd,BgLen,Rprev,Rnew,ResultType) :-
  204   (BgLen =< FgEnd)
  205   ->
  206   (Rnew=Rprev) % do nothing
  207   ;
  208   (Len is min(BgLen,BgLen-FgEnd),
  209    StartPos is max(0,FgEnd),
  210    sub_atom(BgText,StartPos,Len,_,Run),           % gives an atom
  211    stringy_concat([Rprev,Run],Rnew,ResultType)).  % gives sth corresponding to "ResultType"
  212
  213filler_between_end_of_bg_and_start_of_fg(FgPos,BgLen,CutRight,Rprev,Rnew,ResultType) :-
  214   (FgPos =< BgLen ; CutRight == true)
  215   ->
  216   (Rnew=Rprev) % do nothing
  217   ;
  218   (Len is FgPos-BgLen,
  219    space_stringy(Len,Run,ResultType,hard),
  220    stringy_concat([Rprev,Run],Rnew,ResultType)).  % gives sth corresponding to "ResultType"
  221
  222fg_completely_or_partially_on_the_right(FgText,FgPos,FgLen,FgEnd,BgLen,CutRight,Rprev,Rnew,ResultType) :-
  223   (FgEnd =< BgLen ; CutRight == true)
  224   ->
  225   (Rnew=Rprev) % do nothing
  226   ;
  227   (StartPos is max(BgLen,FgPos),
  228    Len      is FgEnd-StartPos,
  229    StartPosInFg is FgLen-Len,
  230    sub_atom(FgText,StartPosInFg,Len,_,Run),    % gives an atom
  231    stringy_concat([Rprev,Run],Rnew,ResultType)). % gives sth corresponding to "ResultType"
  232
  233% In case the ResultType (here called PassedResultType) has been left uninstantiated
  234% by the caller, clamp it to 'atom' or 'string' if both 'FgText' and 'BgText' are
  235% atoms, respectively strings. Possibly throw, possibly fail.
  236% Note that we demand to have a positive conclusion as to what type to deliver or
  237% we complain, unlike in stringy_concat for example, where lack of a positive
  238% conclusion leads to non-determinacy, with two solutions delivered.
  239
  240complete_result_type(BgText,FgText,Result,PassedResultType) :-
  241   has_type(BgText,BgTextType),
  242   has_type(FgText,FgTextType),
  243   has_type(Result,ResultType),
  244   has_type(PassedResultType,PassedResultTypeType),
  245   assertion(member(BgTextType,[atom,string])),         % has already been check_that-ed
  246   assertion(member(FgTextType,[atom,string])),         % has already been check_that-ed
  247   assertion(member(ResultType,[var,string,atom])),     % has already been check_that-ed
  248   assertion(member(PassedResultTypeType,[var,atom])),  % has already been check_that-ed (also, if atom, it is one of 'atom', 'string')
  249   complete_result_type_2(BgTextType,FgTextType,ResultType,PassedResultTypeType,PassedResultType), % this throws, or fails or succeeds, with PassedResultType instantiated
  250   !,                                                       % complete_result_type_2 generates choicepoint we don't want!
  251   check_that(PassedResultType , hard(stringy_typeid)).   % PassedResultType must be instantiated now!
  252
  253%                             FgTextType     PassedResultTypeType
  254%                  BgTextType     |   ResultType   |
  255%                      |          |       |        |  PassedResultType (maybe var on call, as indicated by the preceding arg)
  256%                      |          |       |        |        |
  257complete_result_type_2(atom   , string , var    , var   , _      ) :- error_msg(Msg),check_that(_,hard(nonvar),_{msg:Msg}). % unconditionally throw
  258complete_result_type_2(string , atom   , var    , var   , _      ) :- error_msg(Msg),check_that(_,hard(nonvar),_{msg:Msg}). % unconditionally throw
  259complete_result_type_2(atom   , atom   , var    , var   , atom   ).         % Guess: We want PassedResultType (unbound on call) to be the atom 'atom' because the input texts are both atom
  260complete_result_type_2(string , string , var    , var   , string ).         % Guess: We want PassedResultType (unbound on call) to be the atom 'string' because the input texts are both string
  261complete_result_type_2( _     , _      , string , var   , string ).         % Set: We definitely want PassedResultType (unbound on call) to be the atom 'string' because the provided Result is a string
  262complete_result_type_2( _     , _      , atom   , var   , atom   ).         % Set: We definitely want PassedResultType (unbound on call) to be the atom 'atom' because the provided Result is an atom
  263complete_result_type_2( _     , _      , var    , atom  , _      ).         % OK: the result type is specified (atom on 4th arg) but the Result is var, so anything is ok
  264complete_result_type_2( _     , _      , string , atom  , atom   ) :- fail. % FAIL: the result is specified and 'string' but the ResultType is provided and is 'atom'
  265complete_result_type_2( _     , _      , atom   , atom  , string ) :- fail. % FAIL: the result is specified and 'atom'   but the ResultType is provided and is 'string'
  266complete_result_type_2( _     , _      , string , atom  , string ).         % OK: the result is specified and 'string' and the ResultType is provided and is 'string'
  267complete_result_type_2( _     , _      , atom   , atom  , atom   ).         % OK: the result is specified and 'atom'   and the ResultType is provided and is 'atom'
  268
  269has_type(X,var)    :- var(X),!.
  270has_type(X,atom)   :- atom(X),!.
  271has_type(X,string) :- string(X),!.
  272
  273error_msg("If the input texts are of differing type (here, atom and string) and the Result is unbound, then ResultType must be bound")