spcompat.pl
来自「SRI international 发布的OAA框架软件」· PL 代码 · 共 1,075 行 · 第 1/3 页
PL
1,075 行
% spcompat.pl
% Extra code needed by Sicstus, for functionality from Quintus library
% not available in Sicstus one. This file is created by David Carter
% (d.m.carter@bigfoot.com); code is written by David Carter and (earlier)
% by Ralph Becket.
% This has been tested under Sicstus 3.7.1.
% A few modifications made by Chris Culy for increased compatibility. This has been tested under Sicstus 3.8.4
%%CC sicstus complained about memberchk/2 not existing
:- use_module(library(lists)).
%%% ======================================================================
%%% Implementations of predicates with no simple equivalents in SICStus 3#7.
%%% ======================================================================
% can_open_file exists in Quintus library(file). We implement just the
% behaviour for Mode=read, because that's all that's used by OAA, and
% because the Quintus documentation points out the following:
% "Under operating systems which do not support version numbers (as
% UNIX does not), file_exists/2 could fail (because there is no
% such FileName) and can_open_file/2 could succeed (because you are
% allowed to create one). Conversely, file_exists/2 could succeed
% (because there is such a FileName) and can_open_file/2 fail
% (because you have so many files open that you cannot open
% another)."
% We assume the "too many open files" condition won't arise.
can_open_file(_FileName, Mode, _) :-
\+(Mode==read),
!,
format(user_error,
"~N* WARNING: can_open_file called with Mode=~w (not implemented under Sicstus\n"),
fail.
% Succeed if can open FileName in this Mode.
can_open_file(FileName, read, _) :-
file_exists(FileName, read),
!.
% No, fail after printing a warning if Quiet = warn.
can_open_file(FileName, read, warn) :-
format(user_error,
"~N* could not open ~w in mode read~n",
[FileName]),
fail.
% ------------------------------------------------------------------------
% Emulation of Quintus library(ctr).
ctr_is(Ctr, Old) :-
ctr(Ctr, Old1),
!,
Old=Old1.
ctr_is(_,0).
ctr_set(Ctr, N) :-
retractall(ctr(Ctr, _)),
assert(ctr(Ctr, N)).
ctr_inc(Ctr, N, Old) :-
ctr(Ctr, Old),
retractall(ctr(Ctr, _)),
M is Old + N,
assert(ctr(Ctr, M)).
ctr_inc(Ctr) :-
ctr_inc(Ctr, 1, _Old).
ctr_inc(Ctr, N) :-
ctr_inc(Ctr, N, _Old).
% ------------------------------------------------------------------------
% Partial emulation of library(tcp) ...
:- dynamic tcp_connectionid_stream/2.
:- dynamic ctr/2.
:- dynamic tcp_watch_user/0.
:- dynamic tcp_listener/1.
:- dynamic tcp_scheduled_wakeups/1.
% Just a stub to warn of non-existence:
tcp_trace(off, OnOff) :-
OnOff == on,
!,
format(user_error,
"~NWarning: tcp_trace/2 has no equivalent in SICStus Prolog 3.7~n",
[]).
tcp_trace(_,_).
% ctr 14 is used to generate socket #'s like what we get from the QP
% tcp lib.
% :- ctr_set(14, 1).
% Not sure if the above works, so do this:
ctr(14, 1).
% Connect to a server that is waiting at port Port on host Host. We return a
% socket #, ConnectionID.
tcp_connect(Address, ConnectionID) :-
tcp_connect(Address, ConnectionID, _).
% This predicate returns a Stream which we can read and write.
tcp_connect(address(Port, Host), ConnectionID, Stream) :-
socket('AF_INET', Socket),
socket_connect(Socket, 'AF_INET'(Host, Port), Stream),
socket_buffering(Stream, read, _Old, unbuf),
% Socket is an int, but since we don't know that it always will be,
% let's play it safe and generate our own.
ctr_inc(14, 1, ConnectionID),
set_tcp_connection_name(ConnectionID, Stream).
set_tcp_connection_name(ConnectionID, Stream) :-
retractall(tcp_connectionid_stream(ConnectionID, _)),
assert(tcp_connectionid_stream(ConnectionID, Stream)).
% Succeed on the current connection name, or return such a name.
tcp_connected(ConnectionID) :-
tcp_connectionid_stream(ConnectionID, _).
% Send a Term down the stream corresponding to the named connection.
% NB: in Sicstus, this is done with simple portray_clause, i.e. we can't
% emulate the Quintus term-compression trickery. This is why an OAA Sicstus
% process will post its language as 'sicstus', not as 'prolog', because
% the latter will attract compressed terms.
%
% This probably isn't called by any Sicstus code, and probably shouldn't be,
% because it doesn't escape single quotes the right way.
% Use com:com_SendData. (DLM, 6/2000)
tcp_send(ConnectionID, Term) :-
debug_format("~N% tcp_send(~q, ~q)~n", [ConnectionID, term(Term)]),
tcp_connectionid_stream(ConnectionID, Stream),
portray_clause(Stream, term(Term)),
flush_output(Stream).
% Comment out the first clause to get debug_format/2 to behave like
% format/2.
debug_format(_,_) :-
!.
debug_format(Template,Args) :-
format(Template,Args),
current_output(CO),
flush_output(CO).
% These are simple under Sicstus. Just look up the stream (bidirectional)
% for the named connection.
tcp_input_stream(ConnectionID, Stream) :-
tcp_connectionid_stream(ConnectionID, Stream).
tcp_output_stream(ConnectionID, Stream) :-
tcp_connectionid_stream(ConnectionID, Stream).
flush_all_streams :-
tcp_connectionid_stream(_ConnectionID, Stream),
flush_output(Stream),
fail.
flush_all_streams.
% This is Ralph Becket's Sicstus emulation of the Quintus
% tcp_select/[1,2], using the Sicstus socket_select/5 predicate in
% library(sockets). I've cleaned it up a bit (shortening predicate
% bodies, etc) and have also added an emulation of the Quintus
% "wakeup" code, whereby timers can be set with
% tcp_schedule_wakeup(When,Value) which will later cause tcp_select to
% return with wakeup(Value). The abbreviation "SW" in variable names
% here stands for "ScheduledWakeup". DMC.
% Default timeout is 0, which (under Quintus) means no timeout.
tcp_select(X) :-
tcp_select(0, X).
tcp_select(QPTimeout, X) :-
% flush_all_streams,
debug_format("~N% Call: tcp_select(~q, ~q)...~n", [QPTimeout,X]),
% Convert to Sicstus timeout representation.
sicstus_timeout_structure(QPTimeout,SPTimeout),
% If there's a wakeup due before our timeout, use the wakeup time instead.
adjust_timeout_from_wakeup(SPTimeout,SWTimeval,SWTerm,
SPTimeoutToUse),
% Find all the sockets we are listening on (as server)
findall(Listener, tcp_listener(Listener), Listeners),
% Find all the streams we are connected to (as client)
find_all_connected_streams(Streams),
debug_format("call: socket_select(~q, _, ~q, ~q, _)~n",
[Listeners, SPTimeout, Streams]),
% See the Sicstus documentation for what socket_select does.
socket_select(Listeners, NewStreams, SPTimeoutToUse, Streams, ReadStreams),
debug_format("exit: socket_select(~q, ~q, ~q, ~q, ~q)~n",
[Listeners, NewStreams, SPTimeout, Streams, ReadStreams]),
% Pick through what socket_select has given us and return X1 as an
% initial return value.
tcp_select_return_value(NewStreams,ReadStreams,SPTimeout,X1),
% If X1 was a timeout, see if it's because we've been using an adjusted
% timeout (for a wakeup). If so, return the wakeup term. Otherwise,
% return X1.
adjust_return_value_from_wakeup(X1,SWTimeval,SWTerm,X),
debug_format("~N% Exit: tcp_select(~q, ~q)...~n", [QPTimeout, X]).
% If we got a timeout from socket_select, and SWTimeval did get set by
% adjust_timeout_from_wakeup (i.e. the timeout was really a wakeup, then
% return the wakeup value. Otherwise, pass back the value from
% tcp_select_return_value unchanged.
adjust_return_value_from_wakeup(In,SWTimeval,SWTerm,wakeup(SWTerm)) :-
In == timeout,
nonvar(SWTimeval),
!,
tcp_cancel_wakeup(SWTimeval,SWTerm).
adjust_return_value_from_wakeup(Value,_,_,Value).
% Here's where the clever wakeup stuff is done. We look at the next scheduled
% wakeup, if any, and see if it's before the Timeout provided as arg1. If so,
% we return TimeoutToUse as a more imminent timeout value, and set timeval
% and term to return as args 2 and 3 as well. If we don't find a wakeup, or
% if we do but it's after the timeout, we just return the timeout as we got it.
adjust_timeout_from_wakeup(Timeout,SWTimeval,SWTerm,
TimeoutToUse) :-
(tcp_scheduled_wakeup(SWTimeval,SWTerm) -> true;
fail),
tcp_now(Now),
(Timeout=off; % any wakeup is more imminent that 'off'
Timeout=Secs:Micros,
TimeoutSecs is Secs+Micros/1000000,
tcp_time_plus(Now,TimeoutSecs,TimeoutTimeval),
TimeoutTimeval @> SWTimeval),
!, % scheduled wakeup will happen earlier than Timeout we were given
tcp_time_plus(Now,TimeoutToUse1,SWTimeval),
sicstus_timeout_structure(TimeoutToUse1,TimeoutToUse).
adjust_timeout_from_wakeup(Timeout,_,_,Timeout).
% A Quintus timeout structure is just a number (of seconds to wait). Sicstus
% uses 'off' for no timeout, and a structure of the form Seconds:Microseconds
% for other amounts of time.
sicstus_timeout_structure(0,Timeout) :-
!,
Timeout=off.
sicstus_timeout_structure(Secs,Secs:0) :-
integer(Secs),
!.
sicstus_timeout_structure(Secs1,Secs:Micros) :-
number(Secs1),
!,
Micros1 is Secs1*1000000,
Secs is Micros1 // 1000000,
Micros is integer(Micros1 - (Secs*1000000)).
% Assume already converted:
sicstus_timeout_structure(Timeout,Timeout).
find_all_connected_streams(Streams) :-
findall(Stream, tcp_connectionid_stream(_, Stream), Streams0),
(tcp_watch_user ->
Streams = [user_input | Streams0];
Streams = Streams0).
% We got a new stream. Register it and return the KS ID.
tcp_select_return_value(NewStreams,_ReadStreams,_Timeout,X) :-
member(NewStream, NewStreams),
tcp_register_new_stream(NewStream, KSID),
!,
X = connected(KSID).
% No read-streams: must be a timeout.
tcp_select_return_value(_,[],_,X) :-
!,
X = timeout.
% We have either a proper term to read, or some user input.
tcp_select_return_value(_,ReadStreams,_,X) :-
member(ReadStream, ReadStreams),
( tcp_connectionid_stream(ConnectionID, ReadStream),
debug_format("call: tcp_select(_); reading ~q stream ~q~n",
[ConnectionID, ReadStream]),
% Character escapes (e.g., \n) are *not* employed in ICL expressions.
% (icldataq, among other things, depends on this.)
prolog_flag(character_escapes,CE,off),
read(ReadStream, Input),
prolog_flag(character_escapes,_,CE),
( Input == end_of_file ->
tcp_shutdown(ConnectionID),
debug_format("exit: tcp_select(~q)~n", [end_of_file(ConnectionID)]),
X = end_of_file(ConnectionID)
| Input = term(Event) ->
debug_format("exit: tcp_select(~q)~n", [term(ConnectionID, Event)]),
X = term(ConnectionID, Event)
% Not sure if this case occurs (DLM):
| otherwise ->
debug_format("exit: tcp_select(~q)~n", [term(ConnectionID, Input)]),
X = term(ConnectionID, Input)
)
;
% ReadStream = user_input,
X = user_input).
% Otherwise, try again.
% Ralph says: "I *think* this is the correct semantics for
% tcp_select/2, but the Quintus manual isn't very explicit (i.e. can
% tcp_select/2 ever fail? If not, what are the timeout semantics?)"
tcp_select_return_value(_,_,Timeout,X) :-
tcp_select(Timeout, X).
tcp_register_new_stream(Stream, KSID) :-
next_number(KSID),
\+ tcp_connectionid_stream(KSID, _),
!,
socket_buffering(Stream, read, _Old, unbuf),
assert(tcp_connectionid_stream(KSID, Stream)).
next_number(1).
next_number(N) :-
next_number(M),
N is M + 1.
% Start up as a server on the given port (and host, which should be our
% hostname). This both returns and asserts a Socket number; at the time of
% writing, the OAA code ignores the returned one and relies on the asserted
% one.
tcp_listen_at_port(Port,Host,Socket) :-
socket('AF_INET', Socket),
socket_bind(Socket, 'AF_INET'(Host, Port)),
socket_listen(Socket, 5), % Max no. of pending connection req's.
assert(tcp_listener(Socket)).
% This should only ever be called on one Socket, so allow for the argument
% being variable:
tcp_destroy_listener(Socket) :-
(var(Socket) -> tcp_listener(Socket); true),
socket_close(Socket).
tcp_watch_user(Old, New) :-
(tcp_watch_user -> Old = on; Old = off),
retractall(tcp_watch_user),
(New = on -> assert(tcp_watch_user); true).
% Shut down the specified connection(s).
tcp_shutdown(ConnectionID) :-
retract(tcp_connectionid_stream(ConnectionID, Stream)),
!,
close(Stream).
% At least shout if no associated stream...
tcp_shutdown(ConnectionID) :-
ground(ConnectionID),
!,
format('~NWARNING: tcp_shutdown/1: ~w has no associated stream.~n',[ConnectionID]).
tcp_shutdown(_).
% I hope this is sufficiently drastic...DMC
tcp_reset :-
tcp_shutdown(_),
retractall(tcp_listener(_)),
tcp_cancel_wakeups.
% Simulate some of the timing predicates...these work with "timevals", which
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?