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(). 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(, ). 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 947prologerror_message(python_error(Class, Value, _Stack)) --> 948 { py_str(Value, Message) 949 }, 950 [ 'Python ', ansi(code, "'~w'", [Class]), ':', nl, 951 ' ~w'-[Message] 952 ]. 953 954prologmessage(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 ]