% ----------------------------------------------------------------------
% BEGIN LICENSE BLOCK
% Version: CMPL 1.1
%
% The contents of this file are subject to the Cisco-style Mozilla Public
% License Version 1.1 (the "License"); you may not use this file except
% in compliance with the License.  You may obtain a copy of the License
% at www.eclipse-clp.org/license.
% 
% Software distributed under the License is distributed on an "AS IS"
% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
% the License for the specific language governing rights and limitations
% under the License. 
% 
% The Original Code is  The ECLiPSe Constraint Logic Programming System. 
% The Initial Developer of the Original Code is  Cisco Systems, Inc. 
% Portions created by the Initial Developer are
% Copyright (C) 1995-2006 Cisco Systems, Inc.  All Rights Reserved.
% 
% Contributor(s): IC-Parc, Imperal College London
% 
% END LICENSE BLOCK
%
% System:	ECLiPSe Constraint Logic Programming System
% Version:	$Id: toplevel.pl,v 1.14 2017/09/09 18:54:26 jschimpf Exp $
% ----------------------------------------------------------------------

%
% This module contains two variants of the Eclipse toplevel loop,
% a command line version (tty) and a server version (gui).
%
% The tty variant works on the following tty streams:
%
%	toplevel_output		prompts and messages
%	toplevel_input		query and command input
%	answer_output		results and variable bindings
%
%
% The server variant communicates with a user interface via
% 4 queues (toplevel_in, toplevel_out, gui_interrupt_request
% and answer_output) and a string/exdr-based protocol:
%
%	toplevel_out		(fromec,exdr)	query result
%	toplevel_in		(toec,exdr)	toplevel command
%	answer_output		(fromec,text)	results and variable bindings
%	gui_interrupt_request	(fromec,exdr)	query interrupt acknowledge
%
%
% After startup, an initial "Idle" message is sent on toplevel_out.
% The toplevel_in queue then accepts the following command syntax
% in exdr-format:
%
% call(GoalString)
%	Execute the specified goal under control of the toplevel.
%
%	The result will be returned on the toplevel_out queue as a
%	string in <exdr>-format, and is one of:
%
%		"Yes"
%		"More"
%		"No"
%		"Abort"
%
%	Variable bindings will be printed (in no particular format)
%	on the answer_output queue.
%
% profile(GoalString)
% port_profile(GoalString)
%	Like call(GoalString), but wrap the call into profile/1 or
%	port_profile/2.
%
% more
%	Request backtracking into the last goal (which must have
%	succeeded with a "More" result). The result is as for call(...).
%
% end
%	Cut and fail the last goal (only useful if the goal had
%	succeeded with a "More" or "Yes" result).
%	
% exit
%	Terminate the toplevel loop.
%
%
% The Eclipse engine can be thought of as being in one of 3 states
% (idle,running,success) with the following transitions:
%
%    OldState	toplevel_in	toplevel_out	NewState
%
%    (initial)			"Idle"		idle
%    idle	call("...")			running
%    idle	end				idle
%    idle	exit				(final)
%    running			"Yes"		success
%    running			"More"		success
%    running			"No"		idle
%    running			"Abort"		idle
%    success	end				idle
%    success	more				running
%    success	exit				(final)
%

%----------------------------------------------------------------------
:- module(toplevel).
%----------------------------------------------------------------------

:- export
	toplevel_init/1,
	toplevel/0,
	break/0.

:- export			% handlers
	toplevel_prompt/2,
	print_values/3,
	tty_ask_more/2,
	tty_banner/2,
	start_break/3,
	end_break/3,
	delayed_goals_handler/3.


:- comment(categories, ["Development Tools"]).
:- comment(summary, "Interactive ECLiPSe toplevel interpreter").
:- comment(date, "$Date: 2017/09/09 18:54:26 $").
:- comment(copyright, "Cisco Systems, Inc").
:- comment(author, "Joachim Schimpf, IC-Parc").
:- comment(desc, html("
	Note that this library is normally loaded automatically
	by the ECLiPSe development environment.
    ")).

%----------------------------------------------------------------------

:- import
	get_cut/1,
	compiled_file_handler/3,
	set_default_error_handler/2,
	stack_overflow_message/1,
	printf_goal/3,
	sepia_version_banner/2,
	call_local/1
   from sepia_kernel.


:- local variable(toplevel_type).	% 'tty' or 'gui'

:- local variable(history_index).	% query history
:- local variable(history_length).
:- local array(history(30)).


:- comment(toplevel_init/1, [
    summary:"Initialize a toplevel",
    args:["Type":"one of the atoms 'tty' or 'gui'"],
    desc:html("
    	Initializes streams and event handlers in preparation to
	a call to toplevel/0. Two variants of toplevel loop can be
	selected: command line oriented (tty) or server oriented
	(gui). The latter is intended for an ECLiPSe which is
	embedded or remote-interfaced to a GUI-based host program.
    "),
    see_also:[toplevel/0,break/0]
]).

toplevel_init(Type) :-
	getval(toplevel_type, Old),
	( var(Old) ->
	    ( Type = tty -> tty_toplevel_init, setval(toplevel_type, tty)
	    ; Type = gui -> gui_toplevel_init, setval(toplevel_type, gui)
	    ; Type = peer -> gui_toplevel_init, setval(toplevel_type, gui)
	    ; error(6, toplevel_init(Type))
	    )
	; Old = Type ->
	    true
	;
	    printf(error, "%w toplevel already running in %w%n",
		[Old,toplevel_init(Type)])
	).


:- comment(toplevel/0, [
    summary:"Run a toplevel interpreter",
    desc:html("
    	Run a toplevel query interpreter. This is a program that
	allows to input ECLiPSe queries, runs them, and displays
	the results. Which variant of this interpreter is invoked
	depends on how it was initialized in toplevel_init/1.
    "),
    see_also:[toplevel_init/1,break/0]
    ]).

toplevel :-
	getval(toplevel_type, Type),
	( var(Type) ->
	    printf(error, "toplevel not yet initialized%n", [toplevel]),
	    abort
	; Type = tty ->
	    tty_toplevel
	; Type = gui ->
	    gui_toplevel
	;
	    abort
	).


%----------------------------------------------------------------------
% Command line based 'tty' toplevel
%----------------------------------------------------------------------

% A version of tyi/2 which allows an optional newline when used on non-tty
% streams (for pseudo-terminals that don't have raw mode, e.g. inside emacs)
:- local tyi/2.
tyi(S, C) :-
	eclipse_language:tyi(S, C),
	( get_stream_info(S, device, tty) ->
	    true
	; newline(C) ->
	    true
	;
	    eclipse_language:tyi(S, NL),
	    ( newline(NL) -> true ; unget(S) )
	).

newline(10).
newline(13).

flush_standard_streams :-
	flush(output),
	flush(warning_output),
	flush(log_output),
	flush(error).


% If we just read a term that ended in a fullstop,
% discard the blank space that follows the fullstop.
% (not necessary for tty, since tyi discards pending input anyway)
discard_fullstop_space(S, Goal) :-
	( Goal == end_of_file -> true
	; get_stream_info(S, device, tty) -> true
	; get(S, -1) -> unget(S)
	; true	% discard the space that was part of the fullstop
	).


tty_toplevel_init :-

	% make the three streams for the toplevel protocol
	( get_stream_info(input, fd, FD) ->
	    open(dup(FD), read, toplevel_input)
	;
	    set_stream(toplevel_input, input)
	),
	set_stream(answer_output, output),
	set_stream(toplevel_output, output),
%	set_stream_property(error, flush, end_of_line),

	% optional: init debugging tools and interrupt handling
	ensure_loaded(library(tracer_tty)),

	disable_console_interrupt,

	% toplevel event handlers
	set_default_error_handler(133, library_loading_notifier/2),
	reset_event_handler(133),
	set_default_error_handler(139, compiled_file_handler/3),
	reset_event_handler(139),
	set_default_error_handler(153, toplevel_prompt/2),
	reset_event_handler(153),
	set_default_error_handler(154, true/0),
	reset_event_handler(154),
	set_default_error_handler(155, print_values/3),
	reset_event_handler(155),
	set_default_error_handler(156, tty_ask_more/2),
	reset_event_handler(156),
	set_default_error_handler(158, start_break/3),
	reset_event_handler(158),
	set_default_error_handler(159, end_break/3),
	reset_event_handler(159),
	set_default_error_handler(164, tty_banner/2),
	reset_event_handler(164),
	set_default_error_handler(273, delayed_goals_handler/3),
	reset_event_handler(273).


enable_console_interrupt :-
%	( get_flag(system_object_suffix, ".dll") ->	% Windows
%	( true ->
	( false ->
	    % Handle interrupt at least synchronously
	    set_interrupt_handler(int, event/1),
	    set_event_handler(int, interrupt_prolog/0)
	;
	    set_interrupt_handler(int, interrupt_prolog/0)
	).

disable_console_interrupt :-
	set_interrupt_handler(int, true/0).


interrupt_prolog :-
	get_stream(toplevel_input, Handle),
	with_mutex(Handle, (
	    repeat,
	    ask_option(Option),
	    do_option(Option, _Worker),
	    !
	)).

% move printing of interrupt message to warning_output and receive option
% on input, as debug_input and debug_output may be used differently by
% user's application. Kish Shen 2000-8-11
ask_option(Option) :-
	repeat,
	nl(debug_output),
	(
	    write(warning_output, 'Interrupt: '),
	    current_option(_Option, valid, Msg),
	    printf(warning_output, '%a ', Msg),
	    fail
	;
	    write(warning_output, '? '),
	    flush(warning_output)
	),
	tyi(input, AnyCase),
	lower_case(AnyCase, Option),
	( current_option(Option, valid, Message) ->
	    writeln(warning_output, Message),
	    !
	;
	    writeln(warning_output, 'invalid option'),
	    fail	% to repeat
	).

lower_case(Case, LowerCase) :-
	(Case >= 0'a ->
	    LowerCase = Case
	;
	    LowerCase is Case + (0'a - 0'A)
	).

current_option(0'a, valid, '[a]bort').
current_option(0'b, valid, '[b]reak').
current_option(0'c, valid, '[c]ontinue').
current_option(0'd, Error, Message) :-
	( get_flag(worker, 0) ->		% sequential
	    (get_flag(debugging, nodebug) ->
		Error = invalid,
		Message = 'debugger is off'
	    ;
		Error = valid,
		Message = '[d]ebug:creep'
	    )
	;
	    Error = invalid,
	    Message = 'not available in parallel execution'
	).
current_option(0'e, valid, '[e]ngines').
current_option(0'x, valid, 'e[x]it').


% Option handling:
%	abort	- post abort to all engines
%	break	- get a toplevel in a new engine
%	debug	- switch to creep mode
%	cont	- continue
%	engines	- print list of engines with status
%	exit	- halt/0
do_option(0'a, _) :-
	sepia_kernel:current_engines(Es),
	Exit = abort,
	( foreach(E,Es), count(I,1,_), param(Exit,AbortLast) do
	    ( engine_self(E) ->
		% shouldn't happen if self is signal engine
	    	AbortLast = E
	    ; get_engine_property(E, status, running) ->
		printf(warning_output, 'Posting abort to running engine %d%n', [I]),
	    	engine_post(E, Exit)
	    ;
		true
	    )
	),
	( var(AbortLast) -> true ;
	    engine_post(AbortLast, Exit)
	).
do_option(0'b, Worker) :-
	get_flag(worker, Worker),
	% Run break/0 in separate engine, to free up signal engine again
	engine_create(BreakEngine, [thread,detached]),
	engine_resume_thread(BreakEngine, break).
do_option(0'c, _).
do_option(0'd, 0) :-
	tracer_tty:async_force_creep_mode.
do_option(0'e, _) :-
	sepia_kernel:current_engines(Es),
	( foreach(E,Es), count(I,1,_) do
	    get_engine_property(E, all, Ps),
	    memberchk(status(Status),Ps),
	    ( I==1 -> This=" (main)"
	    ; This=""
	    ),
	    printf(log_output, "%w: %w%s%n", [E,Status,This])
	),
	fail.	% repeat prompt
do_option(0'x, Worker) :- (get_flag(worker, Worker) -> halt ; true).



tty_toplevel :-
	compile_rc,
	sepia_version_banner(Message, _Date),
	error(164, Message),		% print the banner
	history_init,
	( get_flag(gc, off) ->
		true
	;
		garbage_collect		% to start with small variable numbers
	),
	tty_abort_loop,
	writeln(log_output, "\nbye").


tty_abort_loop :-
	repeat,				% we start here after abort
	    set_stream_property(toplevel_input, prompt, '\t'),
	    set_stream_property(toplevel_input, prompt_stream, toplevel_output),
	    catch(tty_fail_loop, Tag, top_abort(Tag)),
	    enable_console_interrupt,	% in case it was disabled
	!.


tty_fail_loop :-
	repeat,				% we start here after every query
	    disable_console_interrupt,	% while no goal running
	    (get_flag(break_level, 0) -> trimcore ; true),
	    flush_standard_streams,
	    get_flag(toplevel_module, M),
	    % print prompt and read next goal/command
	    get_stream(toplevel_input, Handle),
	    with_mutex(Handle, (
		( get_stream_info(toplevel_input, prompt_stream, Out) ->
		    history_next(I),
		    printf(Out, "[%a %d]: %b", [M,I])@M
		;
		    true
		),
		set_stream_property(toplevel_input, reprompt_only, on),
		readvar(toplevel_input, Goal, VL)@M,
		discard_fullstop_space(toplevel_input, Goal)
	    )),
	    tty_run_command(Goal, VL, M),
	fail.


% tty_run_command(+Goal, +VarList, +Module)
% this deals with the history and goal expansion

tty_run_command(Var, _, _) :-
	var(Var), !,
	error(4, Var).
tty_run_command(N, _, M) :-
        integer(N),
        !,
        ( history_retrieve(N, Goal-VL) ->
	    printf_goal(toplevel_output, "FL", Goal)@M,
            tty_run_command(Goal, VL, M)
        ;
            history_print(M)
        ).
tty_run_command(h, _, M) :- !,
	history_print(M).
tty_run_command(end_of_file, _, _) :- !,
	( get_flag(ignore_eof, on), get_flag(break_level, 0) ->
	    writeln(toplevel_output, "Use \"halt.\" to exit ECLiPSe.")
	;
	    throw(end)
	).
tty_run_command(Goal0, VL0, M) :-
        history_append(Goal0-VL0),
	error(154, goal(Goal0, VL0, NewGoal, NewVL), M), % extension hook
	( is_list(NewVL) -> Goal1=NewGoal, VL=NewVL ; Goal1=Goal0,VL=VL0 ),
	enable_console_interrupt,
	( get_flag(goal_expansion,on) ->
	    expand_goal(Goal1, Goal2)@M,
	    tty_run_command1(Goal2, VL, M)
	;
	    tty_run_command1(Goal1, VL, M)
	).


% tty_run_command1(+Goal, +VarList, +Module)
% this interprets some toplevel commands and calls run_goal otherwise

tty_run_command1(Var, _, _) :-
	var(Var), !,
	error(4, Var).
tty_run_command1('?-'(Goal), VL, M) :- !,
	tty_run_command1(Goal, VL, M).
tty_run_command1(':-'(Goal), _, M) :- !,
	call_local(Goal)@M,
	!.
tty_run_command1(notrace, _, _):- !,
	tty_run_command1(nodebug, _, _).
tty_run_command1(nodebug, _, _):- !,
	set_flag(debugging, nodebug),
	writeln(toplevel_output, "Debugger switched off").
tty_run_command1(trace, _, _):- !,
	set_flag(debugging, creep),
	writeln(toplevel_output, "Debugger switched on - creep mode").
tty_run_command1(debug, _, _):- !,
	set_flag(debugging, leap),
	writeln(toplevel_output, "Debugger switched on - leap mode").
tty_run_command1(module(M), _, _):- !,
	set_flag(toplevel_module, M).
tty_run_command1(Goal, VL, M) :-			% execute a general goal
        run_goal(Goal, VL, M, call).


tty_ask_more(_, yes) :- !,
	tty_ask_more(_, more_answers).
tty_ask_more(_, more_answers) :- !,
	write(toplevel_output, ' ? '),
	flush(toplevel_output),
	tyi(toplevel_input, C),
	( C == 0'; ->
		put(toplevel_output, C),
		nl(toplevel_output),
		fail
	; newline(C) ->
		nl(toplevel_output)
	;
		put(toplevel_output, C),
		printf(toplevel_output, "%nType ; for more solutions, otherwise <return>", []),
		tty_ask_more(_, more_answers)
	).
tty_ask_more(_, _).



toplevel_prompt(_, M) :-		% default handler for event 153
	is_predicate(toplevel_prompt/1)@M,
	!,
	M:toplevel_prompt(M).		% for compatibility
toplevel_prompt(_, M) :-
	get_stream_info(toplevel_input, prompt_stream, Out),
	!,
	history_next(I),
	put(Out, 0'[),
	write(Out, M),
	put(Out, 0' ),
	write(Out, I),
	write(Out, "]: "),
	flush(Out).
toplevel_prompt(_, _).



delayed_goals_handler(_, [], _):- !.
delayed_goals_handler(_, List, _):-
	length(List, Num), Num > 5,
	printf(toplevel_output, "\nThere are %d delayed goals. Do you want to see them? (y/n) %b", Num),
	no_typed, !,
	nl(toplevel_output).
delayed_goals_handler(_, List, M):-
	nl(toplevel_output),
	writeln(answer_output, "\nDelayed goals:"),
	get_flag(output_mode, Mode),
	concat_string(['\t%', Mode, 'Gw%n'], Format),
	print_delayed_list(answer_output, List, Format, M),
	flush(answer_output).

    no_typed :-
	tyi(toplevel_input, C),
	( C = 0'n -> true ; C = 0'y -> fail ; no_typed ).

    print_delayed_list(_, [], _, _).
    print_delayed_list(Stream, [Susp|Rest], Format, M) :-
	suspension_to_qualgoal(Susp, Goal, M),
	printf(Stream, Format, [Goal])@M,
	print_delayed_list(Stream, Rest, Format, M).

    suspension_to_qualgoal(Susp, Goal, M) :-
	get_suspension_data(Susp, qualified_goal, LM:UGoal),
	functor(UGoal, N, A),
	( get_flag(N/A, definition_module, DM)@LM,
	  get_flag(N/A, definition_module, DM)@M ->
	    Goal=UGoal
	;
	    Goal=LM:UGoal
	).


tty_banner(_, Message) :-		% default handler for event 164
	write(log_output, Message),
	flush(log_output).


%----------------------------------------------------------------------
% Embedded/Remote toplevel
%----------------------------------------------------------------------

gui_toplevel_init :-
	% Assume that these streams have been created by the GUI:
%	peer_queue_create(toplevel_in, host, sync, toec, ''),
%	peer_queue_create(toplevel_out, host, sync, fromec, ''),
%	peer_queue_create(answer_output, host, sync, fromec, ''),
%	peer_queue_create(gui_interrupt_request, host, sync, fromec, ''),

	% redirect standard i/o streams to queues if not already done
	( peer_queue_get_property(output, peer_type, _) ->
	    % assume we already have those queues (e.g. when the
	    % remote-eclipse was created with the UseQueues option)
	    true
	;
	    peer_queue_create(input, host, sync, toec, ''),
	    peer_queue_create(output, host, sync, fromec, ''),
	    peer_queue_create(error, host, sync, fromec, '')
	),
	set_stream_property(output, flush, end_of_line),
	set_stream_property(error, flush, end_of_line),
	set_stream_property(warning_output, flush, end_of_line),

	% Interrupt handling
	% Protocol:
	%	Eclipse is running and has control
	%	remote:
	%		remote sends exdr 'int' on async gui_pause_request
	%		post_events_from_stream/1 handler posts 'int' event
	%	embedded:
	%		host posts 'int' event asynchronously
	%	user_stop_request_handler/1 replies 'int' on sync gui_interrupt_request
	%	remote optionally sends 'abort' on async gui_pause_request
	%	Once Eclipse has control back, it executes 'abort' handler
	% 
	( peer_get_property(host, type, remote) ->
	    event_create(post_events_from_stream(gui_pause_request), [defers], GIWEvent)@sepia_kernel,
	    peer_queue_create(gui_pause_request, host, async, bidirect, GIWEvent)
	;
	    % int-event is posted directly
	    true
	),
	set_interrupt_handler(int, event/1),
	set_event_handler(int, user_stop_request_handler/1),

	% toplevel event handlers
	set_default_error_handler(139, compiled_file_handler/3),
	reset_event_handler(139),
	set_default_error_handler(155, print_values/3),
	reset_event_handler(155),
	set_default_error_handler(156, gui_ask_more/2),
	reset_event_handler(156),
	set_default_error_handler(273, gui_delayed_goals_handler/2),
	reset_event_handler(273),

	% optional: init debugging tools and interrupt handling
	set_event_handler(139, true/0),	% suppress messages
	ensure_loaded(library(tracer_tcl)),
	tracer_tcl:install_guitools,
	reset_event_handler(139).


gui_toplevel :-
	write_exdr(toplevel_out, "Idle"),
	repeat,
	    catch(gui_fail_loop, Tag, top_abort(Tag)),
	!.

gui_fail_loop :-
	repeat,
	    (get_flag(break_level, 0) -> trimcore ; true),
	    read_command(Command),
	    state_idle(Command),
	fail.


state_idle(call(GoalString)) :- !,
	exec_goal(GoalString, call).
state_idle(profile(GoalString)) :- !,
	exec_goal(GoalString, profile).
state_idle(port_profile(GoalString)) :- !,
	exec_goal(GoalString, port_profile).
state_idle(end) :- !.
state_idle(exit) :- !,
	throw(end).
state_idle(Junk) :- !,
	error(6, state_idle(Junk)).


state_success :-
	read_command(Command),
	state_success(Command).

state_success(more) :- !,
	fail.
state_success(end) :- !.
state_success(exit) :- !,
	throw(end).
state_success(Junk) :-
	error(6, state_success(Junk)).
	

read_goal(GoalString,Goal,Vars) :-
	open(GoalString,string,Stream),
	get_flag(toplevel_module, Module),
	readvar(Stream,Goal,Vars)@Module,
	close(Stream).

% exec_goal(GoalString, RunMode)
% read a goal from toplevel_in, run it, do the more-dialog
% and always succeed in the end
exec_goal(GoalString, RunMode) :-
	read_goal(GoalString,Goal,Vars),	% fail or throw on syntax error
	get_flag(toplevel_module, Module),
	tracer_tcl:register_inspected_term(Goal, Module),
	get_flag(output_options, Options),
	write_term(answer_output, (?- Goal),
		[fullstop(true),nl(true)|Options])@Module,
	flush(answer_output),
	( get_flag(goal_expansion,on) ->
	    expand_goal(Goal, TransGoal)@Module	% might fail
	;
	    TransGoal=Goal
	),
	!,
	run_goal(TransGoal, Vars, Module, RunMode).
exec_goal(_, _) :-
	throw(abort).


gui_ask_more(_, no) :- !,
	gui_ask_more(_, no_answer).
gui_ask_more(_, no_answer) :- !,
	write_exdr(toplevel_out, "No").
gui_ask_more(_, last_yes) :- !,
	gui_ask_more(_, last_answer).
gui_ask_more(_, last_answer) :- !,
	write_exdr(toplevel_out, "Yes"),
	state_success.			% fail if more requested
gui_ask_more(_, yes) :- !,
	gui_ask_more(_, more_answers).
gui_ask_more(_, more_answers) :-
	write_exdr(toplevel_out, "More"),
	state_success.			% fail if more requested


read_command(C) :-
	flush_standard_streams,
	flush(toplevel_out),		% send query result
	read_exdr(toplevel_in,C).	% gui interaction happens here


drain_stream(S) :-
	( stream_select([S], 0, []) -> true ; get(S, _), drain_stream(S) ).
	

gui_delayed_goals_handler(_, Susps) :-	% handler for 273
	suspensions(Susps),
	length(Susps, N),
	( N > 1 ->
	    printf(answer_output, "There are %d delayed goals.%n", N)
	; N > 0 ->
	    printf(answer_output, "There is 1 delayed goal.%n", [])
	;
	    true
	).


% The handler which is executed when the user hits the interrupt button.
% This must be untraceable/skipped to prevent internals from being traced
% when selecting the 'continue in creep mode' option.

user_stop_request_handler(Event) :-
	write_exdr(gui_interrupt_request, Event),
	flush(gui_interrupt_request).

:- skipped(user_stop_request_handler/1).
:- untraceable(user_stop_request_handler/1).


%----------------------------------------------------------------------
% Common toplevel predicates
%----------------------------------------------------------------------

% the break/0 predicate - it only works in the tty version

:- comment(break / 0, [
    summary:"A new invocation of a top level loop is called as a subgoal",
    desc:html("
    Used to start a new invocation of the top level loop.  Compiled
    procedures and event handler redefinitions remain valid within the new
    invocation level.  Exiting with a Control-D (or end_of_file) or
    calling throw(end) will return the control to the previous level.
    "),
    args:[],
    resat:"   No.",
    fail_if:"   None.\n\n",
    eg:"
[eclipse]: [user].
 go:- writeln(\"**** Enter prolog goals and press ^D\"),
      break,
      writeln(\"**** Execution continues...\").
 user compiled 144 bytes in 0.02 seconds

yes.
[eclipse]: go.
**** Enter prolog goals and press ^D

Entering break level 1
[eclipse]: date(X).

X = \"Wed Aug  9 12:21:48 1989\\n\"
yes.
[eclipse]: ^D

Leaving break level 1
**** Execution continues...

yes.
[eclipse]:
",
	see_also:[toplevel/0, throw / 1]]).

break :-
	getval(toplevel_type, tty),
	!,
	get_flag(break_level, Level0),
	Level is Level0+1,
	set_flag(break_level, Level),
	get_flag(toplevel_module, M),
	error(158, Level, M),		% calls start_break/3
	tty_abort_loop,
	set_flag(break_level, Level0),
	error(159, Level, M).		% calls end_break/3
break :-
	error(141, break).


    start_break(_, Level, M) :-
	printf(toplevel_output, "\nEntering break level %d\n", Level)@M.

    end_break(_, Level, M) :-
	set_flag(toplevel_module, M),
	printf(toplevel_output, "\n\nLeaving break level %d\n%b",Level)@M.


print_values(_, Vars, Module) :-		% handler for 155
	Stream = answer_output,
	( get_stream(Stream, output) -> nl(answer_output) ; true ),
	( foreach([Name|Value],Vars),param(Stream,Module) do
	    write(Stream, Name),
	    write(Stream, ' = '),
	    get_flag(output_options, Options),
	    write_term(Stream, Value, [precedence(699),nl(true)|Options])@Module
	).


run_goal(TransGoal, Vars, Module, RunMode) :-
	shelf_create(count(0,false), Count),
	% reset invocation numbers etc. unless in break level
	( debug_reset -> true ; true ),
	cputime(Tstart),
	(
	    get_cut(Before),
	    top_call_local(TransGoal, Module, RunMode),
	    Time is cputime-Tstart,
	    get_cut(After),
	    % reporting a solution and prompting for more
	    % is in a mutex region for the parallel system
	    with_mutex(Count, (
		( shelf_get(Count, 2, false) ->
		    shelf_inc_and_get(Count, 1, NSol),
		    error(155, Vars, Module),	% print bindings
		    suspensions(Susps),
		    error(273, Susps, Module),	% print delayed goals
		    ( Before == After ->
			( NSol == 1 ->
			    printf(answer_output, "Yes (%.2fs cpu)%n%b", [Time])
			;
			    printf(answer_output, "Yes (%.2fs cpu, solution %d)%n%b", [Time,NSol])
			)
		    ;
			printf(answer_output, "Yes (%.2fs cpu, solution %d, maybe more)", [Time,NSol]),
			( getval(toplevel_type, tty) -> true ; nl(answer_output) ),
			flush(answer_output)
		    ),
		    answer(Vars, Before, After, AnswerCode),
		    error(156, AnswerCode),		% fail if 'more' requested
		    shelf_set(Count, 2, true)
		;
		    true
		)
	    ))
	->
	    true
	;
	    Time is cputime-Tstart,
	    ( getval(toplevel_type, tty) -> nl(answer_output) ; true ),
	    printf(answer_output, "No (%.2fs cpu)%n%b", [Time]),
	    error(156, no)
	).


    top_call_local(Goal, Module, call) :-
	call_local((
	    get_flag(debugging, TraceMode),
	    ( TraceMode = creep ->
		trace(Goal)@Module
	    ; TraceMode = leap ->
		debug(Goal)@Module
	    ;
		call(Goal)@Module
	    )
	)).
    top_call_local(Goal, Module, profile) :-
	call_local((
	    profile:profile(Goal)@Module
	)).
    top_call_local(Goal, Module, port_profile) :-
	call_local((
	    port_profiler:port_profile(Goal,[])@Module
	)).


    :- mode answer(++,++,++,-).
    answer([], Cut, Cut, last_yes    ) :- !.
    answer([], _,   _,   yes         ) :- !.
    answer(_,  Cut, Cut, last_answer ) :- !.
    answer(_,  _,   _,   more_answers).


top_abort(end) ?- !.
top_abort(abort) ?- !,
	writeln(answer_output, "Abort"),
	flush(answer_output),
	( current_stream(toplevel_in) ->
	    drain_stream(toplevel_in),
	    write_exdr(toplevel_out, "Abort")
	;
	    true
	),
	fail.
top_abort(Tag) :-
	stack_overflow_message(Tag), !,
	top_abort(abort).
top_abort(Tag) :-
	catch(error(230, throw(Tag)), T, top_abort(T)),
	fail.


library_loading_notifier(_, Library) :-
	printf(warning_output,
	    "WARNING: module '%w' does not exist, loading library...%n",
	    [Library]).


%-------------------------------
% compile .rc file
%-------------------------------

compile_rc :-
	initfile(File),
	!,
	get_flag(toplevel_module, M),
	compile(File, M).
compile_rc.

initfile(File) :-
	getenv("ECLIPSEINIT", OsFile),
	!,
        OsFile \= "",   % otherwise ignore all default init files
        File = "$ECLIPSEINIT".
initfile(File) :-
	( Dir = "."
        ; Dir = "$HOME"
        ; Dir = "$HOMEPATH"
        ),
	concat_string([Dir,"/.eclipserc"], Candidate),
	existing_file(Candidate, [""], [readable], File),
	!.


%-------------------------------
% history
%-------------------------------

history_next(I) :-
	getval(history_index, I).

history_oldest(I) :-
	I is max(1, getval(history_index) - getval(history_length)).

history_append(Goal) :-
	getval(history_index, N),
	I is N mod getval(history_length),
	setval(history(I), Goal),
	N1 is N + 1,
	setval(history_index, N1).

history_print(M) :-
	getval(history_index, Next),
	First is max(1, Next - getval(history_length)),
	history_print(First, Next, M).

history_print(I, N, M) :-
	I < N ->
	    history_retrieve(I, Goal-_),
	    write(toplevel_output, I),
	    put(toplevel_output, 0'	),
	    printf_goal(toplevel_output, "FL", Goal)@M,
	    I1 is I+1,
	    history_print(I1, N, M)
	;
	    true.

history_retrieve(N, Goal) :-
	history_next(Next),
	history_oldest(Oldest),
	( N < 0 ->
	    I is Next + N
	;
	    I = N
	),
	I >= Oldest, I < Next,
	I1 is I mod getval(history_length),
	getval(history(I1), Goal).

history_collect(List) :-
	history_oldest(Oldest),
	history_next(Next),
	history_collect(Oldest, Next, List).

history_collect(N, N, []) :- !.
history_collect(N, Lim, [Goal|List]) :-
	history_retrieve(N, Goal),
	N1 is N+1,
	history_collect(N1, Lim, List).

history_init :-
	current_array(history(Max), _),
	setval(history_index, 1),
	setval(history_length, Max),
	( existing_file(".eclipse_history", [""], [readable], File) ->
	    read_history(File)
	; existing_file("~/.eclipse_history", [""], [readable], File) ->
	    read_history(File)
	;
		true
	).

read_history(File) :-
	open(File, read, S),
	readvar(S, Term, VL),
	read_history(S, Term, VL),
	close(S).

read_history(_, end_of_file, _) :- !.
read_history(Stream, Goal, VL) :-
	history_append(Goal-VL),
	readvar(Stream, NewTerm, NewVL),
	read_history(Stream, NewTerm, NewVL).


:- export write_history/0.
:- comment(write_history/0, [template:"write_history",
    summary:"Writes the current command history into the .eclipse_history file"
    ]).

write_history :-
	history_collect(List),
	open('.eclipse_history', write, Stream),
	write_history(Stream, List).

write_history(Stream, []) :-
	close(Stream).
write_history(Stream, [Goal-_|More]) :-
	printf(Stream, "%OQDw.\n", [Goal]),
	write_history(Stream, More).

