View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2023, SWI-Prolog Solutions b.v.
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(janus,
   36          [ py_version/0,
   37
   38            py_call/1,                  % +Call
   39            py_call/2,                  % +Call, -Return
   40            py_call/3,                  % +Call, -Return, +Options
   41	    py_iter/2,			% +Call, -Return
   42	    py_iter/3,			% +Call, -Return, +Options
   43            py_free/1,			% +Obj
   44	    py_is_object/1,		% @Term
   45	    py_with_gil/1,		% :Goal
   46
   47            py_func/3,                  % +Module, +Func, -Return
   48            py_func/4,                  % +Module, +Func, -Return, +Options
   49            py_dot/4,                   % +Module, +ObjRef, +Meth, ?Ret
   50            py_dot/5,                   % +Module, +ObjRef, +Meth, -Ret, +Options
   51
   52            values/3,                   % +Dict, +Path, ?Val
   53            keys/2,                     % +Dict, ?Keys
   54            key/2,                      % +Dict, ?Key
   55            items/2,                    % +Dict, ?Items
   56
   57            py_shell/0,
   58
   59	    py_pp/1,                    % +Term
   60            py_pp/2,                    % +Stream, +Term
   61            py_pp/3,                    % +Stream, +Term, +Options
   62
   63            py_obj_dir/2,               % +ObjRef,-List
   64            py_obj_dict/2,              % obj_dict(+ObjRef, -Dict)
   65
   66            py_initialize/3,            % +Program, +Argv, +Options
   67            py_lib_dirs/1,              % -Dirs
   68            py_add_lib_dir/1,           % +Dir
   69            py_add_lib_dir/2,           % +Dir,+Where
   70
   71            op(200, fy, @),             % @constant
   72            op(50,  fx, #)              % #Value
   73          ]).   74:- use_module(library(apply_macros), []).   75:- autoload(library(lists), [append/3, member/2]).   76:- autoload(library(apply), [maplist/2, exclude/3, maplist/3]).   77:- autoload(library(error), [must_be/2, domain_error/2]).   78:- autoload(library(dicts), [dict_keys/2]).   79:- autoload(library(option), [dict_options/2]).   80:- autoload(library(prolog_code), [comma_list/2]).   81:- autoload(library(readutil), [read_line_to_string/2]).   82:- autoload(library(wfs), [call_delays/2, delays_residual_program/2]).   83
   84:- if(\+current_predicate(py_call/1)).   85:- if(current_prolog_flag(windows, true)).   86:- use_module(library(shlib), [win_add_dll_directory/1]).   87
   88% Just having the Python dir in PATH seems insufficient. We also need to
   89% add the directory to the DLL search path.
   90add_python_dll_dir :-
   91    absolute_file_name(path('python3.dll'), DLL, [access(read)]),
   92    file_directory_name(DLL, Dir),
   93    win_add_dll_directory(Dir).
   94:- initialization(add_python_dll_dir, now).   95:- endif.   96
   97:- use_foreign_library(foreign(janus), [visibility(global)]).   98:- endif.   99
  100:- meta_predicate py_with_gil(0).  101
  102:- predicate_options(py_call/3, 3,
  103                     [ py_object(boolean),
  104                       py_string_as(oneof([string,atom]))
  105                     ]).  106:- predicate_options(py_func/4, 4,
  107                     [ sizecheck(boolean),
  108                       pass_to(py_call/3, 3)
  109                     ]).  110:- predicate_options(py_dot/5, 5,
  111                     [ sizecheck(boolean),
  112                       pass_to(py_call/3, 3)
  113                     ]).  114
  115:- public
  116    py_initialize/0,
  117    py_call_string/3,
  118    py_write/2,
  119    py_readline/4.  120
  121/** <module> Call Python from Prolog
  122
  123This library implements calling Python  from   Prolog.  It  is available
  124directly from Prolog if the janus   package is bundled, providing access
  125to an _embedded_ Python instance. If  SWI-Prolog is embedded into Python
  126using the Python package ``janus-swi``, this  library is provided either
  127from Prolog or from the Python package.
  128
  129Normally,  the  Prolog  user  can  simply  start  calling  Python  using
  130py_call/2 or friends. In special cases it   may  be needed to initialize
  131Python with options using  py_initialize/3   and  optionally  the Python
  132search path may be extended using py_add_lib_dir/1.
  133*/
  134
  135%!  py_version is det.
  136%
  137%   Print version  info on the  embedded Python installation  based on
  138%   Python `sys.version`.  If a Python _virtual environment_ (venv) is
  139%   active, indicate this with the location of this environment found.
  140
  141py_version :-
  142    py_call(sys:version, X),
  143    print_message(information, janus(version(X))),
  144    (   py_venv(VEnvDir, EnvSiteDir)
  145    ->  print_message(information, janus(venv(VEnvDir, EnvSiteDir)))
  146    ;   true
  147    ).
  148
  149
  150%!  py_call(+Call) is det.
  151%!  py_call(+Call, -Return) is det.
  152%!  py_call(+Call, -Return, +Options) is det.
  153%
  154%   Call Python and return the result of   the called function. Call has
  155%   the shape `[Target][:Action]*`, where `Target`   is  either a Python
  156%   module name or a Python object reference. Each `Action` is either an
  157%   atom to get the denoted attribute from   current `Target` or it is a
  158%   compound term where the first  argument   is  the function or method
  159%   name  and  the  arguments  provide  the  parameters  to  the  Python
  160%   function. On success, the returned Python   object  is translated to
  161%   Prolog.  `Action` without a `Target` denotes a buit-in function.
  162%
  163%   Arguments to Python  functions  use   the  Python  conventions. Both
  164%   _positional_  and  _keyword_  arguments    are   supported.  Keyword
  165%   arguments are written as `Name = Value`   and  must appear after the
  166%   positional arguments.
  167%
  168%   Below are some examples.
  169%
  170%       % call a built-in
  171%	?- py_call(print("Hello World!\n")).
  172%	true.
  173%       % call a built-in (alternative)
  174%	?- py_call(builtins:print("Hello World!\n")).
  175%	true.
  176%	% call function in a module
  177%	?- py_call(sys:getsizeof([1,2,3]), Size).
  178%	Size = 80.
  179%	% call function on an attribute of a module
  180%       ?- py_call(sys:path:append("/home/bob/janus")).
  181%       true
  182%       % get attribute from a module
  183%       ?- py_call(sys:path, Path)
  184%       Path = ["dir1", "dir2", ...]
  185%
  186%   Given a class in a file `dog.py`  such as the following example from
  187%   the Python documentation
  188%
  189%   ```
  190%   class Dog:
  191%       tricks = []
  192%
  193%       def __init__(self, name):
  194%           self.name = name
  195%
  196%       def add_trick(self, trick):
  197%           self.tricks.append(trick)
  198%   ```
  199%
  200%   We can interact with this class as  below. Note that ``$Doc`` in the
  201%   SWI-Prolog toplevel refers to the  last   toplevel  binding  for the
  202%   variable `Dog`.
  203%
  204%       ?- py_call(dog:'Dog'("Fido"), Dog).
  205%       Dog = <py_Dog>(0x7f095c9d02e0).
  206%       ?- py_call($Dog:add_trick("roll_over")).
  207%       Dog = <py_Dog>(0x7f095c9d02e0).
  208%       ?- py_call($Dog:tricks, Tricks).
  209%       Dog = <py_Dog>(0x7f095c9d02e0),
  210%       Tricks = ["roll_over"]
  211%
  212%   py_call/1 can also be used to set an attribute on a module or object
  213%   using the syntax py_call(Obj:Attr = Value). For example:
  214%
  215%       ?- py_call(dog:'Dog'("Fido"), Dog),
  216%          py_call(Dog:owner = "Bob"),
  217%          py_call(Doc:owner, Owner).
  218%       Dog = <py_Dog>(0x7ffff7112170),
  219%       Owner = "Bob".
  220%
  221%   If the principal term of the   first  argument is not `Target:Func`,
  222%   The argument is evaluated as the initial target, i.e., it must be an
  223%   object reference or a module.   For example:
  224%
  225%       ?- py_call(dog:'Dog'("Fido"), Dog),
  226%          py_call(Dog, X).
  227%          Dog = X, X = <py_Dog>(0x7fa8cbd12050).
  228%       ?- py_call(sys, S).
  229%          S = <py_module>(0x7fa8cd582390).
  230%
  231%   Options processed:
  232%
  233%     - py_string_as(+Type)
  234%       If Type is `atom` (default), translate a Python String into a
  235%       Prolog atom.  If Type is `string`, translate into a Prolog string.
  236%	Strings are more efficient if they are short lived.
  237%     - py_object(Boolean)
  238%       If `true` (default `false`), translate the return as a Python
  239%       object reference. Some objects are _always_ translated to
  240%       Prolog, regardless of this flag.  These are the Python constants
  241%       ``None``, ``True`` and ``False`` as well as instances of the
  242%       Python base classes long, float, string or tuple.  Instances of
  243%       sub classes of these base classes are controlled by this option.
  244
  245%!  py_iter(+Iterator, -Value) is nondet.
  246%!  py_iter(+Iterator, -Value, +Options) is nondet.
  247%
  248%   True when Value is returned by the Python Iterator. Python iterators
  249%   may be used to implement   non-deterministic foreign predicates. The
  250%   implementation uses these steps:
  251%
  252%     1. Evaluate Iterator as py_call/2 evaluates its first argument,
  253%        except the ``Obj:Attr = Value`` construct is not accepted.
  254%     2. Call ``__iter__`` on the result to get the iterator itself.
  255%     3. Get the ``__next__`` function of the iterator.
  256%     4. Loop over the return values of the _next_ function.  If
  257%        the Python return value unifies with Value, succeed with
  258%        a choicepoint.  Abort on Python or unification exceptions.
  259%     5. Re-satisfaction continues at (4).
  260%
  261%   The example below uses the built-in iterator range():
  262%
  263%       ?- py_iter(range(1,3), X).
  264%       X = 1 ;
  265%       X = 2.
  266%
  267%   Note that the implementation performs a   _look  ahead_, i.e., after
  268%   successful unification it calls `__next__()`   again. On failure the
  269%   Prolog predicate succeeds deterministically. On   success,  the next
  270%   candidate is stored.
  271%
  272%   Note that a Python _generator_  is   a  Python _iterator. Therefore,
  273%   given  the  Python  generator   expression    below,   we   can  use
  274%   py_iter(squares(1,5),X) to generate the squares on backtracking.
  275%
  276%   ```
  277%   def squares(start, stop):
  278%        for i in range(start, stop):
  279%            yield i * i
  280%   ```
  281%
  282%   @arg Options is processed as with py_call/3.
  283%   @bug Iterator may not depend on janus.Query()
  284
  285%!  py_run(+String, +Globals, +Locals, -Result, +Options) is det.
  286%
  287%   Interface  to  Py_CompileString()  followed   by  PyEval_EvalCode().
  288%   Options:
  289%
  290%       - file_name(String)
  291%         Errors are reported against this pseudo file name
  292%       - start(Token)
  293%         One of `eval`, `file` (default) or `single`.
  294%
  295%   @arg Globals is a dict
  296%   @arg Locals is a dict
  297
  298%!  py_is_object(@Term) is semidet.
  299%
  300%   True when Term is a Python object reference. Fails silently if @Term
  301%   is any other Prolog term.
  302%
  303%   @error existence_error(py_object, Term) is raised of Term is a
  304%   Python object, but it has been freed using py_free/1.
  305
  306%!  py_free(+Obj) is det.
  307%
  308%   Immediately free (decrement  the  reference   count)  for  th Python
  309%   object Obj. Further reference to Obj  using py_call/1,2 or py_free/1
  310%   raises an `existence_error`. Note that by decrementing the reference
  311%   count, we make the  reference  invalid   from  Prolog.  This may not
  312%   actually delete the object because the   object  may have references
  313%   inside Python.
  314%
  315%   Prolog references to Python objects  are   subject  to  atom garbage
  316%   collection and thus normally do not need to be freed explicitly.
  317
  318%!  py_with_gil(:Goal) is semidet.
  319%
  320%   Run Goal as  once(Goal)  while  holding   the  Phyton  GIL  (_Global
  321%   Interpreter Lock_). Note that py_call/1,2 also   locks the GIL. This
  322%   predicate is only required if we  wish   to  make  multiple calls to
  323%   Python while keeping the GIL. The GIL is a _recursive_ lock and thus
  324%   calling py_call/1,2 while holding the GIL does not _deadlock_.
  325
  326
  327		 /*******************************
  328		 *         COMPATIBILIY		*
  329		 *******************************/
  330
  331%!  py_func(+Module, +Function, -Return) is det.
  332%!  py_func(+Module, +Function, -Return, +Options) is det.
  333%
  334%   XSB  compatible  wrappers  for  py_call/2.  Note  that  the  wrapper
  335%   supports more call patterns.  Options
  336%
  337%     - sizecheck(+Boolean)
  338%       Used by XSB for memory management.  Ignored in SWI-Prolog.
  339%     - py_object(+Boolean)
  340%       Passed to py_call/3.
  341
  342py_func(Module, Function, Return) :-
  343    py_call(Module:Function, Return).
  344py_func(Module, Function, Return, Options) :-
  345    py_call(Module:Function, Return, Options).
  346
  347%!  py_dot(+Module, +ObjRef, +MethAttr, -Ret) is det.
  348%!  py_dot(+Module, +ObjRef, +MethAttr, -Ret, +Options) is det.
  349%
  350%   XSB compatible wrappers for py_call/2.
  351%
  352%   @arg Module is ignored (why do we need that if we have ObjRef?)
  353
  354py_dot(_Module, ObjRef, MethAttr, Ret) :-
  355    py_call(ObjRef:MethAttr, Ret).
  356py_dot(_Module, ObjRef, MethAttr, Ret, Options) :-
  357    py_call(ObjRef:MethAttr, Ret, Options).
  358
  359
  360		 /*******************************
  361		 *   PORTABLE ACCESS TO DICTS	*
  362		 *******************************/
  363
  364%!  values(+Dict, +Path, ?Val) is semidet.
  365%
  366%   Get the value associated with Dict at  Path. Path is either a single
  367%   key or a list of keys.
  368
  369values(Dict, Key, Val), is_dict(Dict), atom(Key) =>
  370    get_dict(Key, Dict, Val).
  371values(Dict, Keys, Val), is_dict(Dict), is_list(Keys) =>
  372    get_dict_path(Keys, Dict, Val).
  373values(py({CommaDict}), Key, Val) =>
  374    comma_values(CommaDict, Key, Val).
  375values({CommaDict}, Key, Val) =>
  376    comma_values(CommaDict, Key, Val).
  377
  378get_dict_path([], Val, Val).
  379get_dict_path([H|T], Dict, Val) :-
  380    get_dict(H, Dict, Val0),
  381    get_dict_path(T, Val0, Val).
  382
  383comma_values(CommaDict, Key, Val), atom(Key) =>
  384    comma_value(Key, CommaDict, Val).
  385comma_values(CommaDict, Keys, Val), is_list(Keys) =>
  386    comma_value_path(Keys, CommaDict, Val).
  387
  388comma_value(Key, Key:Val0, Val) =>
  389    Val = Val0.
  390comma_value(Key, (_,Tail), Val) =>
  391    comma_value(Key, Tail, Val).
  392
  393comma_value_path([], Val, Val).
  394comma_value_path([H|T], Dict, Val) :-
  395    comma_value(H, Dict, Val0),
  396    comma_value_path(T, Val0, Val).
  397
  398%!  keys(+Dict, ?Keys) is det.
  399%
  400%   True when Keys is a list of keys that appear in Dict.
  401
  402keys(Dict, Keys), is_dict(Dict) =>
  403    dict_keys(Dict, Keys).
  404keys(py({CommaDict}), Keys) =>
  405    comma_dict_keys(CommaDict, Keys).
  406keys({CommaDict}, Keys) =>
  407    comma_dict_keys(CommaDict, Keys).
  408
  409comma_dict_keys((Key:_,T), Keys) =>
  410    Keys = [Key|KT],
  411    comma_dict_keys(T, KT).
  412comma_dict_keys(Key:_, Keys) =>
  413    Keys = [Key].
  414
  415%!  key(+Dict, ?Key) is nondet.
  416%
  417%   True when Key is a key in   Dict.  Backtracking enumerates all known
  418%   keys.
  419
  420key(Dict, Key), is_dict(Dict) =>
  421    dict_pairs(Dict, _Tag, Pairs),
  422    member(Key-_, Pairs).
  423key(py({CommaDict}), Keys) =>
  424    comma_dict_key(CommaDict, Keys).
  425key({CommaDict}, Keys) =>
  426    comma_dict_key(CommaDict, Keys).
  427
  428comma_dict_key((Key:_,_), Key).
  429comma_dict_key((_,T), Key) :-
  430    comma_dict_key(T, Key).
  431
  432%!  items(+Dict, ?Items) is det.
  433%
  434%   True when Items is a list of Key:Value that appear in Dict.
  435
  436items(Dict, Items), is_dict(Dict) =>
  437    dict_pairs(Dict, _, Pairs),
  438    maplist(pair_item, Pairs, Items).
  439items(py({CommaDict}), Keys) =>
  440    comma_dict_items(CommaDict, Keys).
  441items({CommaDict}, Keys) =>
  442    comma_dict_items(CommaDict, Keys).
  443
  444pair_item(K-V, K:V).
  445
  446comma_dict_items((Key:Value,T), Keys) =>
  447    Keys = [Key:Value|KT],
  448    comma_dict_items(T, KT).
  449comma_dict_items(Key:Value, Keys) =>
  450    Keys = [Key:Value].
  451
  452
  453		 /*******************************
  454		 *             SHELL		*
  455		 *******************************/
  456
  457%!  py_shell
  458%
  459%   Start an interactive Python REPL  loop   using  the  embedded Python
  460%   interpreter. The interpreter first imports `janus` as below.
  461%
  462%       from janus import *
  463%
  464%   So, we can do
  465%
  466%       ?- py_shell.
  467%       ...
  468%       >>> once("writeln(X)", {"X":"Hello world"})
  469%       Hello world
  470%       {'truth': True}
  471%
  472%   If possible, we enable command line   editing using the GNU readline
  473%   library.
  474%
  475%   When used in an environment  where  Prolog   does  not  use the file
  476%   handles 0,1,2 for  the  standard   streams,  e.g.,  in  `swipl-win`,
  477%   Python's I/O is rebound to use  Prolog's I/O. This includes Prolog's
  478%   command line editor, resulting in  a   mixed  history  of Prolog and
  479%   Pythin commands.
  480
  481py_shell :-
  482    import_janus,
  483    py_call(janus_swi:interact(), _).
  484
  485import_janus :-
  486    py_call(sys:hexversion, V),
  487    V >= 0x030A0000,                    % >= 3.10
  488    !,
  489    py_run("from janus_swi import *", py{}, py{}, _, []).
  490import_janus :-
  491    print_message(warning, janus(py_shell(no_janus))).
  492
  493
  494		 /*******************************
  495		 *          UTILITIES           *
  496		 *******************************/
  497
  498%!  py_pp(+Term) is det.
  499%!  py_pp(+Term, +Options) is det.
  500%!  py_pp(+Stream, +Term, +Options) is det.
  501%
  502%   Pretty prints the Prolog translation of   a Python data structure in
  503%   Python syntax. This  exploits  pformat()   from  the  Python  module
  504%   `pprint` to do the actual  formatting.   Options  is translated into
  505%   keyword arguments passed to pprint.pformat().  For example:
  506%
  507%   ```
  508%   ?- py_pp(py{a:1, l:[1,2,3], size:1000000},
  509%            [underscore_numbers(true)]).
  510%   {'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
  511%   ```
  512
  513py_pp(Term) :-
  514    py_pp(current_output, Term, []).
  515
  516py_pp(Term, Options) :-
  517    py_pp(current_output, Term, Options).
  518
  519py_pp(Stream, Term, Options) :-
  520    opts_kws(Options, Kws),
  521    PFormat =.. [pformat, Term|Kws],
  522    py_call(pprint:PFormat, String),
  523    write(Stream, String).
  524
  525opts_kws(Options, Kws) :-
  526    dict_options(Dict, Options),
  527    dict_pairs(Dict, _, Pairs),
  528    maplist(pair_kws, Pairs, Kws).
  529
  530pair_kws(Name-Value, Name=Value).
  531
  532
  533%!  py_obj_dir(+ObjRef, -List) is det.
  534%!  py_obj_dict(+ObjRef, -Dict) is det.
  535%
  536%   Examine attributes of an object.  The predicate py_obj_dir/2 fetches
  537%   the names of all attributes, while   py_obj_dict/2  gets a dict with
  538%   all attributes and their values.
  539
  540py_obj_dir(ObjRef, List) :-
  541    py_call(ObjRef:'__dir__'(), List).
  542
  543py_obj_dict(ObjRef, Dict) :-
  544    py_call(ObjRef:'__dict__', Dict).
  545
  546
  547		 /*******************************
  548		 *            INIT		*
  549		 *******************************/
  550
  551:- dynamic py_venv/2 as volatile.  552
  553%   py_initialize is det.
  554%
  555%   Used as a callback from C for lazy initialization of Python.
  556
  557py_initialize :-
  558    getenv('VIRTUAL_ENV', VEnv),
  559    prolog_to_os_filename(VEnvDir, VEnv),
  560    atom_concat(VEnvDir, '/pyvenv.cfg', Cfg),
  561    access_file(Cfg, read),
  562    !,
  563    current_prolog_flag(executable, Program),
  564    py_initialize(Program, ['-I'], []),
  565    py_call(sys:prefix = VEnv),
  566    venv_update_path(VEnvDir).
  567py_initialize :-
  568    current_prolog_flag(executable, Program),
  569    current_prolog_flag(argv, Argv),
  570    py_initialize(Program, Argv, []).
  571
  572venv_update_path(VEnvDir) :-
  573    py_call(sys:version_info, Info),    % Tuple
  574    Info =.. [_,Major,Minor|_],
  575    format(string(EnvSiteDir),
  576           '~w/lib/python~w.~w/site-packages',
  577           [VEnvDir, Major, Minor]),
  578    prolog_to_os_filename(EnvSiteDir, PyEnvSiteDir),
  579    (   exists_directory(EnvSiteDir)
  580    ->  true
  581    ;   print_message(warning,
  582                      janus(venv(no_site_package_dir(VEnvDir, EnvSiteDir))))
  583    ),
  584    py_call(sys:path, Path0),
  585    exclude(is_site_dir, Path0, Path1),
  586    append(Path1, [PyEnvSiteDir], Path),
  587    py_call(sys:path = Path),
  588    print_message(silent, janus(venv(VEnvDir, EnvSiteDir))),
  589    asserta(py_venv(VEnvDir, EnvSiteDir)).
  590
  591is_site_dir(OsDir) :-
  592    prolog_to_os_filename(PlDir, OsDir),
  593    file_base_name(PlDir, Dir0),
  594    downcase_atom(Dir0, Dir),
  595    no_env_dir(Dir).
  596
  597no_env_dir('site-packages').
  598no_env_dir('dist-packages').
  599
  600%!  py_initialize(+Program, +Argv, +Options) is det.
  601%
  602%   Initialize  and configure  the  embedded Python  system.  If  this
  603%   predicate is  not called before any  other call to Python  such as
  604%   py_call/2, it is called _lazily_, passing the Prolog executable as
  605%   Program, the  non-Prolog arguments  as Argv  and an  empty Options
  606%   list.
  607%
  608%   Calling this predicate while the  Python is already initialized is
  609%   a no-op.   This predicate is  thread-safe, where the  first thread
  610%   initializes Python.
  611%
  612%   In addition to initializing the Python system, it
  613%
  614%     - Adds the directory holding `janus.py` to the Python module
  615%       search path.
  616%     - If Prolog I/O is not connected to the file handles 0,1,2,
  617%       it rebinds Python I/O to use the Prolog I/O.
  618%
  619%   @arg Options is currently ignored.  It will be used to provide
  620%   additional configuration options.
  621
  622py_initialize(Program, Argv, Options) :-
  623    (   py_initialize_(Program, Argv, Options)
  624    ->  absolute_file_name(library('python/janus.py'), Janus,
  625			   [ access(read) ]),
  626	file_directory_name(Janus, PythonDir),
  627	py_add_lib_dir(PythonDir, first),
  628	py_connect_io
  629    ;   true
  630    ).
  631
  632%!  py_connect_io is det.
  633%
  634%   If SWI-Prolog console streams are bound to something non-standard,
  635%   bind the Python console I/O to our streans.
  636
  637py_connect_io :-
  638    maplist(non_file_stream,
  639	    [0-user_input, 1-user_output, 2-user_error],
  640	    NonFiles),
  641    Call =.. [connect_io|NonFiles],
  642    py_call(janus_swi:Call).
  643
  644non_file_stream(Expect-Stream, Bool) :-
  645    (   stream_property(Stream, file_no(Expect))
  646    ->  Bool = @false
  647    ;   Bool = @true
  648    ).
  649
  650		 /*******************************
  651		 *            PATHS		*
  652		 *******************************/
  653
  654%!  py_lib_dirs(-Dirs) is det.
  655%
  656%   True when Dirs is a list of directories searched for Python modules.
  657%   The elements of Dirs are in Prolog canonical notation.
  658
  659py_lib_dirs(Dirs) :-
  660    py_call(sys:path, Dirs0),
  661    maplist(prolog_to_os_filename, Dirs, Dirs0).
  662
  663%!  py_add_lib_dir(+Dir) is det.
  664%!  py_add_lib_dir(+Dir, +Where) is det.
  665%
  666%   Add a directory to the Python  module   search  path.  In the second
  667%   form, Where is one of `first`   or `last`. py_add_lib_dir/1 adds the
  668%   directory as last.
  669%
  670%   Dir is in Prolog notation. The added   directory  is converted to an
  671%   absolute path using the OS notation.
  672%
  673%   A flexible way to add the directory  holding the current Prolog file
  674%   to the Python search path is  in   the  template below. The `here/0`
  675%   predicate can be replaced by  any   predicate  defined  in the file,
  676%   either above or below the initializing/1   directive.  A simple name
  677%   like here/0 is good style when this code is part of a Prolog module.
  678%
  679%   ```
  680%   here.
  681%   :- initialization
  682%       (   source_file(here, File),
  683%           file_directory_name(File, Dir),
  684%           py_add_lib_dir(Dir, first)
  685%       ).
  686%   ```
  687
  688py_add_lib_dir(Dir) :-
  689    py_add_lib_dir(Dir, last).
  690
  691py_add_lib_dir(Dir, Where) :-
  692    absolute_file_name(Dir, AbsDir),
  693    prolog_to_os_filename(AbsDir, OSDir),
  694    (   Where == last
  695    ->  py_call(sys:path:append(OSDir), _)
  696    ;   Where == first
  697    ->  py_call(sys:path:insert(0, OSDir), _)
  698    ;   must_be(oneof([first,last]), Where)
  699    ).
  700
  701
  702		 /*******************************
  703		 *           CALLBACK		*
  704		 *******************************/
  705
  706:- dynamic py_call_cache/8 as volatile.  707
  708:- meta_predicate py_call_string(:, +, -).  709
  710%   py_call_string(:String, +DictIn, -Dict) is nondet.
  711%
  712%   Support janus.once() and janus.Query(). Parses   String  into a goal
  713%   term. Next, all variables from the goal   term that appear in DictIn
  714%   are bound to the value from  this   dict.  Dict  is created from the
  715%   remaining variables, unless they  start   with  an underscore (e.g.,
  716%   `_Time`) and the key `truth. On   success,  the Dict values contain
  717%   the bindings from the  answer  and   `truth`  is  either  `true` or
  718%   `Undefined`. On failure, the Dict values are bound to `None` and the
  719%   `truth` is `false`.
  720%
  721%   Parsing and distributing the variables over the two dicts is cached.
  722
  723py_call_string(M:String, Input, Dict) :-
  724    py_call_cache(String, Input, TV, M, Goal, Dict, Truth, OutVars),
  725    !,
  726    py_call(TV, M:Goal, Truth, OutVars).
  727py_call_string(M:String, Input, Dict) :-
  728    term_string(Goal, String, [variable_names(Map)]),
  729    unbind_dict(Input, VInput),
  730    exclude(not_in_projection(VInput), Map, OutBindings),
  731    dict_create(Dict, bindings, [truth=Truth|OutBindings]),
  732    maplist(arg(2), OutBindings, OutVars),
  733    TV = Input.get(truth, 'PLAIN_TRUTHVALS'),
  734    asserta(py_call_cache(String, VInput, TV, M, Goal, Dict, Truth, OutVars)),
  735    VInput = Input,
  736    py_call(TV, M:Goal, Truth, OutVars).
  737
  738py_call('NO_TRUTHVALS', M:Goal, Truth, OutVars) =>
  739    (   call(M:Goal)
  740    *-> bind_status_no_no_truthvals(Truth)
  741    ;   Truth = @false,
  742	maplist(bind_none, OutVars)
  743    ).
  744py_call('PLAIN_TRUTHVALS', M:Goal, Truth, OutVars) =>
  745    (   call(M:Goal)
  746    *-> bind_status_plain_truthvals(Truth)
  747    ;   Truth = @false,
  748	maplist(bind_none, OutVars)
  749    ).
  750py_call('DELAY_LISTS', M:Goal, Truth, OutVars) =>
  751    (   call_delays(M:Goal, Delays)
  752    *-> bind_status_delay_lists(Delays, Truth)
  753    ;   Truth = @false,
  754	maplist(bind_none, OutVars)
  755    ).
  756py_call('RESIDUAL_PROGRAM', M:Goal, Truth, OutVars) =>
  757    (   call_delays(M:Goal, Delays)
  758    *-> bind_status_residual_program(Delays, Truth)
  759    ;   Truth = @false,
  760	maplist(bind_none, OutVars)
  761    ).
  762
  763not_in_projection(Input, Name=Value) :-
  764    (   get_dict(Name, Input, Value)
  765    ->  true
  766    ;   sub_atom(Name, 0, _, _, '_')
  767    ).
  768
  769bind_none(@none).
  770
  771bind_status_no_no_truthvals(@true).
  772
  773bind_status_plain_truthvals(Truth) =>
  774    (   '$tbl_delay_list'([])
  775    ->  Truth = @true
  776    ;   py_undefined(Truth)
  777    ).
  778
  779bind_status_delay_lists(true, Truth) =>
  780    Truth = @true.
  781bind_status_delay_lists(Delays, Truth) =>
  782    py_call(janus:'Undefined'(prolog(Delays)), Truth).
  783
  784bind_status_residual_program(true, Truth) =>
  785    Truth = @true.
  786bind_status_residual_program(Delays, Truth) =>
  787    delays_residual_program(Delays, Program),
  788    py_call(janus:'Undefined'(prolog(Program)), Truth).
  789
  790py_undefined(X) :-
  791    py_call(janus:undefined, X).
  792
  793unbind_dict(Dict0, Dict) :-
  794    dict_pairs(Dict0, Tag, Pairs0),
  795    maplist(unbind, Pairs0, Pairs),
  796    dict_pairs(Dict, Tag, Pairs).
  797
  798unbind(Name-_, Name-_) :-
  799    sub_atom(Name, 0, 1, _, Char1),
  800    char_type(Char1, prolog_var_start),
  801    !.
  802unbind(NonVar, NonVar).
  803
  804
  805		 /*******************************
  806		 *     SUPPORT PYTHON CALLS     *
  807		 *******************************/
  808
  809:- public
  810       px_cmd/3,
  811       px_qdet/4,
  812       px_comp/7.  813
  814% These predicates are helpers  for the corresponding Python functions
  815% in janus.py.
  816
  817px_cmd(M, P, Tuple) :-
  818    atom_string(Pred, P),
  819    atom_string(Module, M),
  820    (   compound(Tuple)
  821    ->  compound_name_arguments(Tuple, _, Args),
  822	Goal =.. [Pred|Args]
  823    ;   Goal = Pred
  824    ),
  825    call(Module:Goal).
  826
  827px_qdet(M, P, Tuple, Ret) :-
  828    atom_string(Pred, P),
  829    atom_string(Module, M),
  830    (   compound(Tuple)
  831    ->  compound_name_arguments(Tuple, _, Args),
  832	append(Args, [Ret], GArgs),
  833	Goal =.. [Pred|GArgs],
  834	call(Module:Goal)
  835    ;   call(Module:Pred, Ret)
  836    ).
  837
  838px_comp(M, P, Tuple, Vars, Set, TV, Ret) :-
  839    atom_string(Pred, P),
  840    atom_string(Module, M),
  841    length(Out, Vars),
  842    (   compound(Tuple)
  843    ->  compound_name_arguments(Tuple, _, Args),
  844	append(Args, Out, GArgs),
  845	Goal =.. [Pred|GArgs]
  846    ;   Goal =.. [Pred|Out]
  847    ),
  848    compound_name_arguments(OTempl0, -, Out),
  849    tv_goal_and_template(TV, Module:Goal, FGoal, OTempl0, OTempl),
  850    findall(OTempl, FGoal, Ret0),
  851    (   Set == @true
  852    ->  sort(Ret0, Ret)
  853    ;   Ret = Ret0
  854    ).
  855
  856:- meta_predicate
  857    call_delays_py(0, -).  858
  859% 0,1,2: TruthVal(Enum) from janus.py
  860tv_goal_and_template('NO_TRUTHVALS',
  861                     Goal, Goal, Templ, Templ) :- !.
  862tv_goal_and_template('PLAIN_TRUTHVALS',
  863                     Goal, ucall(Goal, TV), Templ, -(Templ,TV)) :- !.
  864tv_goal_and_template('DELAY_LISTS',
  865                     Goal, call_delays_py(Goal, TV), Templ, -(Templ,TV)) :- !.
  866tv_goal_and_template(Mode, _, _, _, _) :-
  867    domain_error("px_comp() truth", Mode).
  868
  869:- public
  870    ucall/2,
  871    call_delays_py/2.  872
  873ucall(Goal, TV) :-
  874    call(Goal),
  875    (   '$tbl_delay_list'([])
  876    ->  TV = 1
  877    ;   TV = 2
  878    ).
  879
  880call_delays_py(Goal, PyDelays) :-
  881    call_delays(Goal, Delays),
  882    (   Delays == true
  883    ->  PyDelays = []
  884    ;   comma_list(Delays, Array),
  885        maplist(term_string, Array, PyDelays)
  886    ).
  887
  888
  889		 /*******************************
  890		 *          PYTHON I/O          *
  891		 *******************************/
  892
  893%   py_write(+Stream, -String) is det.
  894%   py_readline(+Stream, +Size, +Prompt, +Line) is det.
  895%
  896%   Called from redefined Python console  I/O   to  write/read using the
  897%   Prolog streams.
  898
  899:- '$hide'((py_write/1,
  900	    py_readline/4)).  901
  902py_write(Stream, String) :-
  903    notrace(format(Stream, '~s', [String])).
  904
  905py_readline(Stream, Size, Prompt, Line) :-
  906    notrace(py_readline_(Stream, Size, Prompt, Line)).
  907
  908py_readline_(Stream, _Size, Prompt, Line) :-
  909    prompt1(Prompt),
  910    read_line_to_string(Stream, Read),
  911    (   Read == end_of_file
  912    ->  Line = ""
  913    ;   string_concat(Read, "\n", Line),
  914	py_add_history(Read)
  915    ).
  916
  917py_add_history(Line) :-
  918    ignore(catch(prolog:history(user_input, add(Line)), _, true)).
  919
  920
  921		 /*******************************
  922		 *          COMPILING           *
  923		 *******************************/
  924
  925%   py_consult(+File, +Data, +Module) is det.
  926%
  927%   Support janus.consult(file, data=None, module='user').
  928
  929:- public py_consult/3.  930py_consult(File, @none, Module) =>
  931    consult(Module:File).
  932py_consult(File, Data, Module) =>
  933    setup_call_cleanup(
  934	open_string(Data, In),
  935	load_files(Module:File, [stream(In)]),
  936	close(In)).
  937
  938
  939		 /*******************************
  940		 *           MESSAGES		*
  941		 *******************************/
  942
  943:- multifile
  944    prolog:error_message//1,
  945    prolog:message//1.  946
  947prolog:error_message(python_error(Class, Value, _Stack)) -->
  948    { py_str(Value, Message)
  949    },
  950    [ 'Python ', ansi(code, "'~w'", [Class]), ':', nl,
  951      '  ~w'-[Message]
  952    ].
  953
  954prolog:message(janus(Msg)) -->
  955    message(Msg).
  956
  957message(version(V)) -->
  958    [ 'Janus embeds Python ~w'-[V] ].
  959message(venv(Dir, _EnvSiteDir)) -->
  960    [ 'Janus: using venv from ~p'-[Dir] ].
  961message(venv(no_site_package_dir(VEnvDir, Dir))) -->
  962    [ 'Janus: venv dirrectory ~p does not contain ~p'-[VEnvDir, Dir] ].
  963message(py_shell(no_janus)) -->
  964    [ 'Janus: py_shell/0: Importing janus into the Python shell requires Python 3.10 or later.', nl,
  965      'Run "', ansi(code, 'from janus import *', []), '" in the Python shell to import janus.'
  966    ]