View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker and Anjo Anjewierden
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org/
    6    Copyright (c)  2011-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(prolog_breakpoints,
   38          [ set_breakpoint/4,           % +File, +Line, +CharPos, -Id
   39            set_breakpoint/5,           % +Owner, +File, +Line, +CharPos, -Id
   40            delete_breakpoint/1,        % +Id
   41            breakpoint_property/2       % ?Id, ?Property
   42          ]).   43:- autoload(library(debug), [debug/3]).   44:- autoload(library(error), [existence_error/2]).   45:- autoload(library(lists), [nth1/3, member/2]).   46:- autoload(library(prolog_clause), [clause_info/4, clause_name/2]).

Manage Prolog break-points

This module provides an interface for development tools to set and delete break-points, giving a location in the source. Development tools that want to track changes to breakpoints must use user:message_hook/3 to intercept these message terms:

Note that the hook must fail after creating its side-effects to give other hooks the opportunity to react. */

 set_breakpoint(+File, +Line, +Char, -Id) is det
 set_breakpoint(+Owner, +File, +Line, +Char, -Id) is det
Put a breakpoint at the indicated source-location. File is a current sourcefile (as reported by source_file/1). Line is the 1-based line in which Char is. Char is the position of the break.

First, '$clause_from_source'/4 uses the SWI-Prolog clause-source information to find the last clause starting before Line. '$break_pc' generated (on backtracking), a list of possible break-points.

Note that in addition to setting the break-point, the system must be in debug mode for the breakpoint to take effect. With threading enabled, there are various different ways this may be done. See debug/0, tdebug/0 and tdebug/1. Therefore, this predicate does not enable debug mode.

Arguments:
Owner- denotes the file that owns the clause. set_breakpoint/5 is used to set breakpoints in an included file in the context of the Owner main file. See source_file_property/2.
   85set_breakpoint(File, Line, Char, Id) :-
   86    set_breakpoint(File, File, Line, Char, Id).
   87set_breakpoint(Owner, File, Line, Char, Id) :-
   88    debug(break, 'break_at(~q, ~d, ~d).', [File, Line, Char]),
   89    '$clause_from_source'(Owner, File, Line, ClauseRefs),
   90    member(ClauseRef, ClauseRefs),
   91    clause_info(ClauseRef, InfoFile, TermPos, _NameOffset),
   92    (   InfoFile == File
   93    ->  '$break_pc'(ClauseRef, PC, NextPC),
   94        debug(break, 'Clause ~p, PC=~p NextPC=~p', [ClauseRef, PC, NextPC]),
   95        '$clause_term_position'(ClauseRef, NextPC, List),
   96        debug(break, 'Location = ~w', [List]),
   97        range(List, TermPos, SubPos),
   98        arg(1, SubPos, A),
   99        arg(2, SubPos, Z),
  100        debug(break, 'Term from ~w-~w', [A, Z]),
  101        Z >= Char, !,
  102        Len is Z - A,
  103        b_setval('$breakpoint', file_location(File, Line, A, Len))
  104    ;   print_message(warning, breakpoint(no_source(ClauseRef, File, Line))),
  105        '$break_pc'(ClauseRef, PC, _), !,
  106        nb_delete('$breakpoint')
  107    ),
  108    debug(break, 'Break at clause ~w, PC=~w', [ClauseRef, PC]),
  109    '$break_at'(ClauseRef, PC, true),
  110    nb_delete('$breakpoint'),
  111    known_breakpoint(ClauseRef, PC, _Location, Id).
  112
  113range(_,  Pos, _), var(Pos) =>
  114    fail.
  115range(List, parentheses_term_position(_,_,Pos), SubPos) =>
  116    range(List, Pos, SubPos).
  117range([], Pos, SubPos) =>
  118    SubPos = Pos.
  119range([H|T], term_position(_, _, _, _, PosL), SubPos) =>
  120    nth1(H, PosL, Pos),
  121    range(T, Pos, SubPos).
  122range(exit, Pos, SubPos) =>
  123    arg(2, Pos, End),
  124    Dot is End,
  125    EndDot is Dot+1,
  126    SubPos = Dot-EndDot.
  127
  128:- dynamic
  129    known_breakpoint/4,             % ClauseRef, PC, Location, Id
  130    break_id/1.  131
  132next_break_id(Id) :-
  133    retract(break_id(Id0)),
  134    !,
  135    Id is Id0+1,
  136    asserta(break_id(Id)).
  137next_break_id(1) :-
  138    asserta(break_id(1)).
 delete_breakpoint(+Id) is det
Delete breakpoint with given Id. If successful, print_message(breakpoint(delete, Id)) is called. Message hooks working on this message may still call breakpoint_property/2.
Errors
- existence_error(breakpoint, Id).
  148delete_breakpoint(Id) :-
  149    integer(Id),
  150    known_breakpoint(ClauseRef, PC, _Location, Id),
  151    !,
  152    '$break_at'(ClauseRef, PC, false).
  153delete_breakpoint(Id) :-
  154    existence_error(breakpoint, Id).
 breakpoint_property(?Id, ?Property) is nondet
True when Property is a property of the breakpoint Id. Defined properties are:
file(File)
Provided if the breakpoint is in a clause associated to a file. May not be known.
line_count(Line)
Line of the breakpoint. May not be known.
character_range(Start, Len)
One-based character offset of the break-point. May not be known.
clause(Reference)
Reference of the clause in which the breakpoint resides.
  172breakpoint_property(Id, file(File)) :-
  173    known_breakpoint(ClauseRef,_,_,Id),
  174    clause_property(ClauseRef, file(File)).
  175breakpoint_property(Id, line_count(Line)) :-
  176    known_breakpoint(_,_,Location,Id),
  177    location_line(Location, Line).
  178breakpoint_property(Id, character_range(Start, Len)) :-
  179    known_breakpoint(ClauseRef,PC,Location,Id),
  180    (   Location = file_location(_File, _Line, Start, Len)
  181    ->  true
  182    ;   break_location(ClauseRef, PC, _File, Start-End),
  183        Len is End+1-Start
  184    ).
  185breakpoint_property(Id, clause(Reference)) :-
  186    known_breakpoint(Reference,_,_,Id).
  187
  188location_line(file_location(_File, Line, _Start, _Len), Line).
  189location_line(file_character_range(File, Start, _Len), Line) :-
  190    file_line(File, Start, Line).
  191location_line(file_line(_File, Line), Line).
 file_line(+File, +StartIndex, -Line) is det
True when Line is the 1-based line offset in which we find character StartIndex.
  199file_line(File, Start, Line) :-
  200    setup_call_cleanup(
  201        prolog_clause:try_open_source(File, In),
  202        stream_line(In, Start, 1, Line),
  203        close(In)).
  204
  205stream_line(In, _, Line0, Line) :-
  206    at_end_of_stream(In),
  207    !,
  208    Line = Line0.
  209stream_line(In, Index, Line0, Line) :-
  210    skip(In, 0'\n),
  211    character_count(In, At),
  212    (   At > Index
  213    ->  Line = Line0
  214    ;   Line1 is Line0+1,
  215        stream_line(In, Index, Line1, Line)
  216    ).
  217
  218
  219                 /*******************************
  220                 *            FEEDBACK          *
  221                 *******************************/
  222
  223:- initialization
  224    prolog_unlisten(break, onbreak),
  225    prolog_listen(break, onbreak).  226
  227onbreak(exist, ClauseRef, PC) :-
  228    known_breakpoint(ClauseRef, PC, _Location, Id),
  229    !,
  230    break_message(breakpoint(exist, Id)).
  231onbreak(true, ClauseRef, PC) :-
  232    !,
  233    debug(break, 'Trap in Clause ~p, PC ~d', [ClauseRef, PC]),
  234    with_mutex('$break', next_break_id(Id)),
  235    (   nb_current('$breakpoint', Location)
  236    ->  true
  237    ;   break_location(ClauseRef, PC, File, A-Z)
  238    ->  Len is Z+1-A,
  239        Location = file_character_range(File, A, Len)
  240    ;   clause_property(ClauseRef, file(File)),
  241        clause_property(ClauseRef, line_count(Line))
  242    ->  Location = file_line(File, Line)
  243    ;   Location = unknown
  244    ),
  245    asserta(known_breakpoint(ClauseRef, PC, Location, Id)),
  246    break_message(breakpoint(set, Id)).
  247onbreak(false, ClauseRef, PC) :-
  248    debug(break, 'Remove breakpoint from ~p, PC ~d', [ClauseRef, PC]),
  249    clause(known_breakpoint(ClauseRef, PC, _Location, Id), true, Ref),
  250    call_cleanup(break_message(breakpoint(delete, Id)), erase(Ref)).
  251onbreak(gc, ClauseRef, PC) :-
  252    debug(break, 'Remove breakpoint from ~p, PC ~d (due to CGC)',
  253          [ClauseRef, PC]),
  254    retractall(known_breakpoint(ClauseRef, PC, _Location, _Id)).
  255
  256break_message(Message) :-
  257    print_message(informational, Message).
 break_location(+ClauseRef, +PC, -File, -AZ) is det
True when File and AZ represent the location of the goal called at PC in ClauseRef.
Arguments:
AZ- is a term A-Z, where A and Z are character positions in File.
  267break_location(ClauseRef, PC, File, A-Z) :-
  268    clause_info(ClauseRef, File, TermPos, _NameOffset),
  269    '$fetch_vm'(ClauseRef, PC, NPC, _VMI),
  270    '$clause_term_position'(ClauseRef, NPC, List),
  271    debug(break, 'ClausePos = ~w', [List]),
  272    range(List, TermPos, A, Z),
  273    debug(break, 'Range: ~d .. ~d', [A, Z]).
  274
  275
  276                 /*******************************
  277                 *            MESSAGES          *
  278                 *******************************/
  279
  280:- multifile
  281    prolog:message/3.  282
  283prolog:message(breakpoint(no_source(ClauseRef, _File, Line))) -->
  284    [ 'Failed to find line ~d in body of clause ~p.  Breaking at start of body.'-
  285      [Line, ClauseRef]
  286    ].
  287prolog:message(breakpoint(SetClear, Id)) -->
  288    setclear(SetClear),
  289    breakpoint(Id).
  290
  291setclear(set) -->
  292    ['Breakpoint '].
  293setclear(exist) -->
  294    ['Existing breakpoint '].
  295setclear(delete) -->
  296    ['Deleted breakpoint '].
  297
  298breakpoint(Id) -->
  299    breakpoint_name(Id),
  300    (   { breakpoint_property(Id, file(File)),
  301          breakpoint_property(Id, line_count(Line))
  302        }
  303    ->  [ ' at ', url(File:Line) ]
  304    ;   []
  305    ).
  306
  307breakpoint_name(Id) -->
  308    { breakpoint_property(Id, clause(ClauseRef)) },
  309    (   { clause_property(ClauseRef, erased) }
  310    ->  ['~w'-[Id]]
  311    ;   { clause_name(ClauseRef, Name) },
  312        ['~w in ~w'-[Id, Name]]
  313    )