diff --git a/boot/init.pl b/boot/init.pl index 8c75a506c1..8ec939da80 100644 --- a/boot/init.pl +++ b/boot/init.pl @@ -999,9 +999,9 @@ !. :- dynamic - user:portray/1. + user:portray/1, system:portray_var/2. :- multifile - user:portray/1. + user:portray/1, system:portray_var/2. /******************************* diff --git a/boot/toplevel.pl b/boot/toplevel.pl index 7f6ecee4a5..61fae1924a 100644 --- a/boot/toplevel.pl +++ b/boot/toplevel.pl @@ -606,6 +606,7 @@ Keep), create_prolog_flag(toplevel_residue_vars, false, Keep), create_prolog_flag(toplevel_list_wfs_residual_program, true, Keep), + create_prolog_flag(auto_name_variables, false, Keep), '$set_debugger_write_options'(print). %! setup_backtrace @@ -840,6 +841,10 @@ ), trim_stacks, trim_heap, + ( BreakLev == 0 + -> reset_variable_names + ; true + ), repeat, read_query(Prompt, Query, Bindings), prompt(_, Old), @@ -1283,6 +1288,68 @@ diff21(T2, H1, T1, Diff). +:- multifile system:portray_var/2, user:portray_var/2. +:- dynamic system:portray_var/2, user:portray_var/2. + +system:portray_var(Var, Index) :- + predicate_property(user:portray_var(_,_), ssu), + catch( + \+ \+ user:portray_var(Var, Index), + error(existence_error(matching_rule, _), _), + fail + ), + !. + +system:portray_var(Var, _Index) :- + current_prolog_flag(auto_name_variables, true), + $, + ( nb_current('$named_variables', NVars) + -> true + ; NVars = [end] + ), + + var_nth0(Index, NVars, Var), + phrase(portray_var_name(Index), Codes), + format('~s', [Codes]), + nb_linkval('$named_variables', NVars). + +var_nth0(N, List, Var) :- + var_nth(List, Var, 0, N). +var_nth(End, Var, N0, N) :- + End = [end], + !, + nb_linkarg(1, End, Var), + nb_setarg(2, End, [end]), + N = N0. +var_nth([H|_], Var, N0, N) :- + H == Var, + !, + N = N0. +var_nth([_|T], Var, N0, N) :- + succ(N0, N1), + var_nth(T, Var, N1, N). + +portray_var_name(Index) --> + `__`, + { divmod(Index, 26, Rank, Letter), + LCode is Letter + 0'A + }, + [LCode], + ( { Rank > 0, number_codes(Rank, RCodes) } + -> RCodes + ; [] + ). + +%! reset_variable_names is det. +% +% Reset the current list of "variables seen" by the Prolog flag +% `auto_name_variables`, causing the next variable it prints to be +% named `__A`. This is called automatically by the toplevel whenever +% a query prompt is issued at break level 0. + +reset_variable_names :- + nb_delete('$named_variables'). + %! project_constraints(+Bindings, +ResidueVars) is det. % % Call :project_attributes/2 if the Prolog flag diff --git a/customize/init.pl b/customize/init.pl index c984b514c0..dec54bec57 100644 --- a/customize/init.pl +++ b/customize/init.pl @@ -80,6 +80,11 @@ % :- set_prolog_flag(toplevel_print_anon, false). +% If you want unnamed variables to be printed using names (__A, __B) +% instead of ids (_1246, _1248). + +% :- set_prolog_flag(auto_name_variables, true). + % If you do not want the tracer to stop at at the exit port. % :- leash(-exit). diff --git a/man/builtin.doc b/man/builtin.doc index 309db9427d..becbaa7e7a 100644 --- a/man/builtin.doc +++ b/man/builtin.doc @@ -6314,7 +6314,8 @@ current priority. \termitem{portrayed}{Bool} If \const{true}, the hook portray/1 is called before printing a term that is not a variable. If portray/1 succeeds, the term is considered -printed. See also print/1. The default is \const{false}. This option +printed. Similarly, the hook portray_var/3 is called before printing +a variable. See also print/1. The default is \const{false}. This option is an extension to the ISO write_term options. \termitem{priority}{Integer} @@ -6472,6 +6473,21 @@ is not a variable print/1 first calls portray/1 using the term as argument. For lists, only the list as a whole is given to portray/1. If portray/1 succeeds print/1 assumes the term has been written. + \predicate{portray_var}{3}{+Var, +Id, +WriteOptions} +A dynamic predicate, which can be defined by the user to change the +behavior of print/1 on unbound variables. It is used by the Prolog flag +\prologflag{auto_name_variables} to print variables with names. If defined +by the user, it will be called before the auto_name_variables functionality +regardless of the state of that flag, and passed the variable (which the +hook should be careful not to instantiate), the variable's ID (the number +that would be printed following an underscore) and the options to write_term/2 +that caused it to be printed. If portray_var/3 fails, Prolog will fall back +to the \prologflag{auto_name_variables} functionality if enabled, or otherwise +just print the default numeric variable ID following an underscore. + +This predicate must be defined using SSU (=>)/2 rules, to avoid accidental +instantiation of the variable being portrayed. + \predicate[ISO]{read}{1}{-Term} Read the next \textbf{Prolog term} from the current input stream and unify it with \arg{Term}. On reaching end-of-file \arg{Term} is unified diff --git a/man/overview.doc b/man/overview.doc index 08a0746a51..b171337cc7 100644 --- a/man/overview.doc +++ b/man/overview.doc @@ -1165,6 +1165,13 @@ definition is more practical.} Set if Prolog was started with a prolog file as argument. Used by e.g., edit/0 to edit the initial file. + \prologflagitem{auto_name_variables}{bool}{rw} +If \const{true}, variables printed with the \const{portray(true)} option +of write_term/2 will be tracked and named by identity in the order they +appear, in the sequence (\const{__A} .. \const{__Z}, \const{__A1} .. +\const{__Z1}, etc). The sequence will be reset every time a query prompt +is issued at break level 0. + \prologflagitem{autoload}{atom}{rw} This flag controls autoloading predicates based on autoload/1 and autoload/2 as well as predicates from \jargon{autoload libraries}. @@ -2299,6 +2306,9 @@ portability. \begin{itemlist} \item [portray/1] Hook into write_term/3 to alter the way terms are printed (ISO). + \item [portray_var/3] +Extension to the portray/1 functionality to alter the way variables +are printed. \item [message_hook/3] Hook into print_message/2 to alter the way system messages are printed (Quintus/SICStus). diff --git a/src/pl-global.h b/src/pl-global.h index 6c794cec7d..fc9d3c8a71 100644 --- a/src/pl-global.h +++ b/src/pl-global.h @@ -321,6 +321,7 @@ struct PL_global_data Procedure prolog_trace_interception4; Procedure prolog_break_hook6; /* prolog:break_hook/6 */ Procedure portray; /* portray/1 */ + Procedure portray_var2; /* system:portray_var/2 */ Procedure dcall1; /* $call/1 */ Procedure call3; /* call/3*/ Procedure setup_call_catcher_cleanup4; /* setup_call_catcher_cleanup/4 */ diff --git a/src/pl-write.c b/src/pl-write.c index dfd0c19a7d..828124baf2 100644 --- a/src/pl-write.c +++ b/src/pl-write.c @@ -76,6 +76,7 @@ typedef struct } write_options; #if USE_LD_MACROS +#define var_index_ptr(p) LDFUNC(var_index_ptr, p) #define enterPortray(_) LDFUNC(enterPortray, _) #define leavePortray(_) LDFUNC(leavePortray, _) #endif /*USE_LD_MACROS*/ @@ -96,8 +97,8 @@ static void leavePortray(void); #undef LDFUNC_DECLARATIONS -char * -var_name_ptr(DECL_LD Word p, char *name) +static int64_t +var_index_ptr(DECL_LD Word p) { size_t iref; deRef(p); @@ -106,8 +107,12 @@ var_name_ptr(DECL_LD Word p, char *name) iref = ((Word)p - (Word)lBase)*2+1; else iref = ((Word)p - (Word)gBase)*2; + return (int64_t) iref; +} - Ssprintf(name, "_%lld", (int64_t)iref); +char * +var_name_ptr(DECL_LD Word p, char *name) +{ Ssprintf(name, "_%lld", var_index_ptr(p)); return name; } @@ -1315,7 +1320,9 @@ callPortray(term_t arg, int prec, write_options *options) if ( GD->cleaning > CLN_PROLOG ) fail; /* avoid dangerous callbacks */ - if ( options->portray_goal ) + if ( PL_is_variable(arg) ) + { pred = _PL_predicate("portray_var", 2, "system", &GD->procedures.portray_var2); + } else if ( options->portray_goal ) { pred = _PL_predicate("call", 3, "user", &GD->procedures.call3); } else { pred = _PL_predicate("portray", 1, "user", &GD->procedures.portray); @@ -1334,7 +1341,11 @@ callPortray(term_t arg, int prec, write_options *options) if ( !saveWakeup(&wstate, TRUE) ) return -1; Scurout = options->out; - if ( options->portray_goal ) + if ( PL_is_variable(arg) ) + { av = PL_new_term_refs(2); + PL_put_term(av+0, arg); + PL_put_int64(av+1, var_index_ptr(valTermRef(arg))); + } else if ( options->portray_goal ) { av = PL_new_term_refs(3); PL_put_term(av+0, options->portray_goal); @@ -1510,8 +1521,7 @@ writeTerm2(term_t t, int prec, write_options *options, bool arg) atom_t a; IOSTREAM *out = options->out; - if ( !PL_is_variable(t) && - true(options, PL_WRT_PORTRAY) ) + if ( true(options, PL_WRT_PORTRAY) ) { switch( callPortray(t, prec, options) ) { case TRUE: return TRUE;