├── .gitignore ├── LICENSE ├── Makefile ├── Manifest.in ├── README.md ├── logtalk_kernel ├── __init__.py ├── __main__.py ├── install.py ├── kernel.py ├── kernelspec │ └── kernel.js ├── logtalk_kernel_base_implementation.py ├── logtalk_kernel_config.py ├── logtalk_server │ ├── jupyter.lgt │ ├── jupyter_jsonrpc.lgt │ ├── jupyter_logging.lgt │ ├── jupyter_preferences.lgt │ ├── jupyter_query_handling.lgt │ ├── jupyter_request_handling.lgt │ ├── jupyter_server.lgt │ ├── jupyter_term_handling.lgt │ ├── jupyter_variable_bindings.lgt │ └── loader.lgt ├── sicstus_kernel_implementation.py └── swi_kernel_implementation.py ├── notebooks ├── JupyterKernelForLogtalkOverview.ipynb └── LogtalkTutorial.ipynb ├── pyproject.toml └── setup.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | # Logtalk temporary file directories 2 | .lgt_tmp/ 3 | lgt_tmp/ 4 | 5 | # Logtalk default unit testing and doclet results and logs directories 6 | logtalk_tester_logs/ 7 | logtalk_doclet_logs/ 8 | 9 | # backend Prolog compiler temporary files 10 | .pl-history 11 | *.out 12 | *.xwam 13 | *.qo 14 | *.ql 15 | *.itf 16 | *.po 17 | 18 | .DS_Store 19 | .ipynb_checkpoints/ 20 | .jupyter/ 21 | .jupyter_ystore.db 22 | build/ 23 | logtalk_kernel.egg-info/ 24 | logtalk_kernel/__pycache__/ 25 | 26 | notebooks/*.lgt 27 | notebooks/*.pl 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022-2025 Paulo Moura 2 | Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | install: 29 | python3 -m pip install -e . 30 | python3 -m logtalk_kernel.install 31 | 32 | clean: 33 | python3 -m pip uninstall logtalk_kernel 34 | jupyter kernelspec remove logtalk_kernel 35 | -------------------------------------------------------------------------------- /Manifest.in: -------------------------------------------------------------------------------- 1 | include notebooks/JupyterKernelForLogtalkOverview.ipynb 2 | include notebooks/LogtalkTutorial.ipynb 3 | include notebooks/*.png 4 | include logtalk_kernel/logtalk_server/*.lgt 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Hercutalk - A Jupyter Kernel for Logtalk 3 | 4 | A [Jupyter](https://jupyter.org/) kernel for [Logtalk](https://logtalk.org/) based on [prolog-jupyter-kernel](https://github.com/hhu-stups/prolog-jupyter-kernel) and [IPython kernel](https://github.com/ipython/ipykernel). 5 | 6 | This project is a fork of the [prolog-jupyter-kernel](https://github.com/hhu-stups/prolog-jupyter-kernel) project (developed by Anne Brecklinghaus in her Master's thesis at the University of Düsseldorf under the supervision of Michael Leuschel and Philipp Körner) and still under development. It includes back-ports of recent patches and improvements by Michael Leuschel, dgelessus, and Silas Kraume. Major changes are committed and more are expected. Furthermore, no liability is accepted for correctness and completeness (see the [LICENSE](LICENSE) file). 7 | 8 | 🙏 Sponsored by [Permion](https://permion.ai/) and [GitHub Sponsors](https://github.com/sponsors/pmoura). 9 | 10 | 11 | ## Supported Logtalk version 12 | 13 | Logtalk 3.81.0 (or later version) plus at least one of the supported Prolog backends. The `LOGTALKHOME` and `LOGTALKUSER` environment variables **must** be defined. 14 | 15 | 16 | ## Supported Prolog backends and versions 17 | 18 | - [ECLiPSe 7.0 #57 or later](http://eclipseclp.org/) 19 | - [GNU Prolog 1.6.0 or later](http://www.gprolog.org/) (use git version until 1.6.0 is released) 20 | - [SICStus Prolog 4.5.1 or later](https://sicstus.sics.se/) 21 | - [SWI-Prolog 8.4.3 or later](https://www.swi-prolog.org/) (default) 22 | - [Trealla Prolog 2.65.5 or later](https://github.com/trealla-prolog/trealla) 23 | - [XVM 10.0.0 or later](https://permion.ai/) 24 | - [YAP 7.2.1 or later](https://github.com/vscosta) 25 | 26 | Note that a public online use of this kernel (instead of private or local) may be restricted to a subset of these backends (notably, due to some systems requiring commercial licenses). 27 | 28 | The kernel is implemented in a way that basically all functionality except the loading of configuration files can easily be overridden. This is especially useful for **extending the kernel for further Prolog backends** or running code with a different version of a backend. For further information about this, see [Configuration](#configuration). 29 | 30 | Also see the [JupyterLab Logtalk CodeMirror Extension](https://github.com/LogtalkDotOrg/jupyterlab-logtalk-codemirror-extension) for *syntax highlighting* of Logtalk code in JupyterLab (forked from the [JupyterLab Prolog CodeMirror Extension](https://github.com/hhu-stups/jupyterlab-prolog-codemirror-extension)). 31 | 32 | 33 | ## Examples 34 | 35 | The directory [notebooks](./notebooks) contains some example Juypter notebooks, including a Logtalk short tutorial and a notebook giving an overview of the kernel's features and its implementation. Note that all of them can be viewed with [nbviewer](https://nbviewer.org/) without having to install the kernel. 36 | 37 | 38 | ## Install 39 | 40 | The kernel is provided as a Python package on the Python Package Index and can be installed with `pip`: 41 | 42 | python3 -m pip install --upgrade logtalk-jupyter-kernel 43 | python3 -m logtalk_kernel.install 44 | 45 | There are the following options which can be seen when running `python3 -m logtalk_kernel.install --help` 46 | 47 | - `--user`: install to the per-user kernel registry instead of `sys.prefix` (use if you get permission errors during installation) 48 | - `--prefix PREFIX`: install to the given prefix: `PREFIX/share/jupyter/kernels/` 49 | 50 | 51 | ## Uninstall 52 | 53 | python3 -m pip uninstall logtalk_kernel 54 | jupyter kernelspec remove logtalk_kernel 55 | 56 | 57 | ## Running 58 | 59 | Logtalk notebooks can be run using JupyterLab, JupyterLab Desktop, Jupyter notebook, or VSCode. 60 | 61 | ### Running using JupyterLab 62 | 63 | Simply start JupyterLab (e.g. by typing `jupyter-lab` in a shell) and then click on the Logtalk Notebook (or Logtalk Console) icon in the Launcher or open an existing notebook. 64 | 65 | ### Running using JupyterLab Desktop 66 | 67 | On macOS, JupyterLab Desktop **must** be started from a shell where the `LOGTALKHOME` and `LOGTALKUSER` environment variables are defined so that they are inherited by the JupyterLab Desktop process. Typically: 68 | 69 | $ open /Applications/JupyterLab.app 70 | 71 | This is not an issue on Linux or Windows where, assuming that the `LOGTALKHOME` and `LOGTALKUSER` environment variables are defined, JupyterLab Desktop can be started by double-clicking its icon. 72 | 73 | ### Running using Jupyter notebook 74 | 75 | Simply start Jupyter notebook (e.g. by typing `jupyter notebook` in a shell) and then open an existing notebook. 76 | 77 | ### Running using VSCode 78 | 79 | Simply open an existing notebook. Ensure that the [Logtalk plug-in for VSCode](https://github.com/LogtalkDotOrg/logtalk-for-vscode) for syntax highlighting in code cells. 80 | 81 | ### Configuration 82 | 83 | The kernel can be configured by defining a Python config file named `logtalk_kernel_config.py`. The kernel will look for this file in the Jupyter config path (can be retrieved with `jupyter --paths`) and the current working directory. An **example** of such a configuration file with an explanation of the options and their default values commented out can be found [here](./logtalk_kernel/logtalk_kernel_config.py). 84 | 85 | **Note:** If a config file exists in the current working directory, it overrides values from other configuration files. 86 | 87 | In general, the kernel can be configured to use a different Prolog backend (which is responsible for code execution) or kernel implementation. Furthermore, it can be configured to use another Prolog backend altogether which might not be supported by default. The following options can be configured: 88 | - `jupyter_logging`: If set to `True`, the logging level is set to DEBUG by the kernel so that **Python debugging messages** are logged. 89 | - Note that this way, logging debugging messages can only be enabled after reading a configuration file. Therefore, for instance, the user cannot be informed that no configuration file was loaded if none was defined at one of the expected locations. 90 | - In order to switch on debugging messages by default, the development installation described in the GitHub repository can be followed and the logging level set to `DEBUG` in the file `kernel.py` (which contains a corresponding comment). 91 | - However, note that this causes messages to be printed in the Jupyter console applications, which interferes with the other output. 92 | 93 | - `server_logging`: If set to `True`, a **Logtalk server log file** is created. 94 | - The name of the file consists of the implementation ID preceded by `.logtalk_server_log_`. 95 | - `backend`: The name of the **Prolog backend integration script** with which the server is started. 96 | - `backend_data`: The **Prolog backend-specific data** which is needed to run the server for code execution. 97 | - This is required to be a dictionary containing at least an entry for the configured `backend`. 98 | - Each entry needs to define values for 99 | - `failure_response`: The output which is displayed if a query **fails** 100 | - `success_response`: The output which is displayed if a query **succeeds without any variable bindings** 101 | - `error_prefix`: The prefix that is output for **error messages** 102 | - `informational_prefix`: The prefix that is output for **informational messages** 103 | - `program_arguments`: **Command line arguments** with which the Logtalk server can be started 104 | - All supported Prolog backends can be used by configuring the string `"default"`. 105 | - Additionally, a `kernel_implementation_path` can be provided, which needs to be an **absolute path to a Python file**: 106 | - The corresponding module is required to define a subclass of `LogtalkKernelBaseImplementation` named `LogtalkKernelImplementation`. This can be used to override some of the kernel's basic behavior (see [Overriding the Kernel Implementation](#overriding-the-kernel-implementation)). 107 | 108 | If the given **`program_arguments` are invalid**, the kernel waits for a response from the server which it will never receive. In that state it is **not able to log any exception** and instead, nothing happens. To facilitate finding the cause of the error, before trying to start the Logtalk server, the arguments and the directory from which they are tried to be executed are logged. 109 | 110 | 111 | ### Defining environment variables for notebooks 112 | 113 | Notebooks may require defining environment variables. For example, a notebook running one of the Java integration examples found in the Logtalk distribution may require the `CLASSPATH` environment variable to be set. This can be easily accomplished by adding a `logtalk_kernel_config.py` file to the notebook directory and using the `os.environ` Python dictionary. For the Logtalk `document_converter` example, which uses Apache Tika, assuming we copied the JAR file to the notebook directory, we could write: 114 | 115 | os.environ['CLASSPATH'] = './tika-app-2.8.0.jar' 116 | 117 | 118 | ### Using virtual environment for Logtalk packs 119 | 120 | Notebooks may require loading Logtalk packs. Ideally, when sharing notebooks with other users, those packs should be installed in a virtual environment to avoid any conflicts with user installed packs or pack versions. The `lgtenv` script provided by the Logtalk distribution can be used to create the packs virtual environment in the same directory as the notebook. For example: 121 | 122 | $ cd my_notebook_directory 123 | $ lgtenv -p logtalk_packs 124 | 125 | The packs can be pre-installed before sharing e.g. an archive with the notebook directory contents. Alternatively, installing the packs can be left to the user by providing a `requirements.lgt` file. For example: 126 | 127 | registry(talkshow, 'https://github.com/LogtalkDotOrg/talkshow'). 128 | pack(talkshow, lflat, 2:1:0). 129 | 130 | In this case, the user will need to run (possibly from a notebook code cell) the query: 131 | 132 | ?- logtalk_load(packs(loader)), packs::restore('requirements.lgt'). 133 | 134 | We also must ensure that the virtual environment will be used when the notebook runs. The best solution is to create a `settings.lgt` file in the same directory as the notebook defining the `logtalk_packs` library alias. For example, assuming a `logtalk_packs` sub-directory for the virtual environment: 135 | 136 | :- multifile(logtalk_library_path/2). 137 | :- dynamic(logtalk_library_path/2). 138 | 139 | :- initialization(( 140 | logtalk_load_context(directory, Directory), 141 | atom_concat(Directory, logtalk_packs, VirtualEnvironment), 142 | asserta(logtalk_library_path(logtalk_packs, VirtualEnvironment)) 143 | )). 144 | 145 | 146 | ### Changing the Prolog backend in the fly 147 | 148 | In most cases, the following shortcuts can be used: 149 | 150 | - ECLiPSe: `eclipse` 151 | - GNU Prolog: `gnu` 152 | - SICStus Prolog: `sicstus` 153 | - SWI-Prolog (default backend): `swi` 154 | - Trealla Prolog: `trealla` 155 | - XVM : `xvm` 156 | - YAP: `yap` 157 | 158 | If the shortcuts don't work due to some unusal Logtalk or Prolog backend setup, the `jupyter::set_prolog_backend(+Backend)` predicate is provided. In order for this to work, the configured `backend_data` dictionary needs to contain data for more than one Prolog backend. For example (in a notebook code cell): 159 | 160 | jupyter::set_prolog_backend('xvmlgt.sh'). 161 | 162 | The predicate argument is the name of the integration script used to run Logtalk. On Windows, always use the PowerShell scripts (e.g. `sicstuslgt.ps1`). On POSIX systems, use the ones that work for your Logtalk installation (e.g. if you're using Logtalk with Trealla Prolog with a setup that requires the `.sh` extension when running the integration script, then use `tplgt.sh` instead of just `tplgt`). 163 | 164 | 165 | ## Development 166 | 167 | ### Requirements 168 | 169 | - At least **Python** 3.7 170 | - Tested with Python 3.11.7 171 | - **Jupyter** installation with JupyterLab and/or Juypter Notebook 172 | - Tested with 173 | - `jupyter_core`: 5.7.2 174 | - `jupyterlab`: 4.2.3 175 | - `notebook`: 7.2.1 176 | - `jupytext`: 1.16.7 177 | - Logtalk and one or more supported Prolog backends (see above) 178 | - Installing **Graphviz** with `python3 -m pip` may not suffice (notably, on Windows) 179 | - Also run the Graphviz [installer](https://graphviz.org/download/) and add its executables to the `PATH` (a reboot may be required afterwards) 180 | 181 | The installation was tested with macOS 14.5, Ubuntu 20.0.4, and Windows 10. 182 | 183 | ### Install 184 | 185 | python3 -m pip install --upgrade jupyterlab 186 | git clone https://github.com/LogtalkDotOrg/logtalk-jupyter-kernel 187 | cd logtalk-jupyter-kernel 188 | make install 189 | 190 | By default, `make install` uses `sys.prefix`. If it fails with a permission error, you can retry using either `sudo make install` or repeat its last step using `python3 -m logtalk_kernel.install --user` or `python3 -m logtalk_kernel.install --prefix PREFIX`. 191 | 192 | On Ubuntu, if `make install` fails with an error, try to update `pip` to its latest version by running `python3 -m pip install --upgrade pip`. 193 | 194 | ### Uninstall 195 | 196 | cd logtalk-jupyter-kernel 197 | make clean 198 | 199 | ### Local Changes 200 | 201 | In general, in order for local code adjustments to take effect, the kernel needs to be reinstalled. When installing the local project in *editable* mode with `python3 -m pip install -e .` (e.g. by running `make`), restarting the kernel suffices. 202 | 203 | Adjustments of the Logtalk server code are loaded when the server is restarted. Thus, when changing Logtalk code only, instead of restarting the whole kernel, it can be interrupted, which causes the Logtalk server to be restarted. 204 | 205 | ### Building and publishing 206 | 207 | Make sure that both the `pyproject.toml` file and the `jupyter` object (in the `logtalk_kernel/logtalk_server/jupyter.lgt` file) report the same kernel version. 208 | 209 | python3 -m build . 210 | twine upload dist/logtalk_jupyter_kernel-VERSION.tar.gz dist/logtalk_jupyter_kernel-VERSION-py3-none-any.whl 211 | 212 | ### Debugging 213 | 214 | If you get a `Failed to start the Kernel.` error after selecting the Logtalk kernel, make sure that the `LOGTALKHOME` and `LOGTALKUSER` environment variables are defined. 215 | 216 | Usually, if the execution of a goal causes an exception, the corresponding Logtalk error message is captured and displayed in the Jupyter frontend. However, in case something goes wrong unexpectedly or the query does not terminate, the **Logtalk server might not be able to send a response to the client**. In that case, the user can only see that the execution does not terminate without any information about the error or output that might have been produced. However, it is possible to write logging messages and access any potential output, which might facilitate finding the cause of the error. 217 | 218 | Debugging the server code is not possible in the usual way by tracing invocations. Furthermore, all messages exchanged with the client are written to the standard streams. Therefore, printing helpful debugging messages does not work either. Instead, if `server_logging` is configured, **messages can be written to a log file** by calling `log/1` or `log/2` from the `jupyter_logging` object. By default, only the responses sent to the client are logged. 219 | 220 | When a query is executed, all its output is written to a file named `.server_output`, which is deleted afterwards by `jupyter_query_handling::delete_output_file`. If an error occurs during the actual execution, the file cannot be deleted and thus, the **output of the goal can be accessed**. Otherwise, the deletion might be prevented. 221 | 222 | Furthermore, the server might send a response which the client cannot handle. In that case, **logging for the Python code** can be enabled by configuring `jupyter_logging`. For instance, the client logs the responses received from the server. 223 | 224 | When the Logtalk code makes calls to foreign language libraries (notably C or C++ code), it's possible that output is generated that is not diverted to a file when the kernel redirects the Prolog output streams. This unexpected output is most likely not a valid JSON payload and thus breaks communication between the notebook and the kernel. In this case, the notebook displays the following error: 225 | 226 | Something went wrong 227 | The Logtalk server needs to be restarted 228 | 229 | These issues can be debugged by running the problematic query in a terminal after diverting the Prolog output streams to a file. For example, assuming in the Prolog backend you're using the stream redirecting uses a `set_stream/2` predicate: 230 | 231 | ?- open(out, write, S), 232 | set_stream(S, alias(current_output)), 233 | set_stream(S, alias(user_output)), 234 | set_stream(S, alias(user_error)), 235 | goal, 236 | close(S). 237 | 238 | If you get any output while the goal is running (e.g. foreign library debugging messages), you will need to find a way to turn off that output. 239 | 240 | ### Prolog backend requirements 241 | 242 | Adding support for other Prolog backends requires: 243 | 244 | - Command-line option(s) to silence (quiet) any banner and informative messages. 245 | - Programatic solution to check if a quiet command-line option was used to start the Logtalk/Prolog process (e.g. by checking a boolean Prolog flag). 246 | - Ability to redirect current output (including `user_output` and `user_error`) to a different stream and restoring the previous stream when the redirection is terminated. 247 | 248 | ### Overriding the Kernel Implementation 249 | 250 | The actual kernel code determining the handling of requests is not implemented by the kernel class itself. Instead, there is the file [logtalk_kernel_base_implementation.py](./logtalk_kernel/logtalk_kernel_base_implementation.py) which defines the class `LogtalkKernelBaseImplementation`. When the kernel is started, a (sub)object of this class is created. It handles the starting of and communication with the Logtalk server. For all requests (execution, shutdown, completion, inspection) the kernel receives, a `LogtalkKernelBaseImplementation` method is called. By **creating a subclass** of this and defining the path to it as `kernel_implementation_path`, the **actual implementation code can be replaced**. If no such path is defined, the path itself or the defined class is invalid, a **default implementation** is used instead. 251 | -------------------------------------------------------------------------------- /logtalk_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | """Jupyter kernel for Logtalk""" 29 | 30 | from .kernel import LogtalkKernel 31 | 32 | __version__ = LogtalkKernel.implementation_version 33 | -------------------------------------------------------------------------------- /logtalk_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | from ipykernel.kernelapp import IPKernelApp 29 | from logtalk_kernel.kernel import LogtalkKernel 30 | 31 | IPKernelApp.launch_instance(kernel_class=LogtalkKernel) 32 | -------------------------------------------------------------------------------- /logtalk_kernel/install.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | import argparse 29 | import json 30 | import logging 31 | import os 32 | import shutil 33 | import sys 34 | import tempfile 35 | 36 | from jupyter_client.kernelspec import KernelSpecManager 37 | 38 | logger = logging.getLogger(__name__) 39 | 40 | KERNELSPEC_FILES = [ 41 | "kernel.js", 42 | ] 43 | 44 | 45 | def get_kernelspec_dir_path(): 46 | """ 47 | Get the path of the kernelspec directory where the static files needed for the installation are. 48 | This currently only includes the kernel.js file, 49 | because the kernelspec (kernel.json) is generated dynamically. 50 | """ 51 | dirname = os.path.dirname(__file__) 52 | kernelspec_dir_path = os.path.join(dirname, "kernelspec") 53 | return kernelspec_dir_path 54 | 55 | 56 | def create_kernelspec(dest_dir): 57 | with open(os.path.join(dest_dir, "kernel.json"), "w", encoding="utf-8") as f: 58 | kernel_json = { 59 | "argv": [sys.executable, "-m", "logtalk_kernel", "-f", "{connection_file}"], 60 | "display_name": "Logtalk", 61 | "language": "logtalk", 62 | } 63 | json.dump(kernel_json, f, ensure_ascii=False, indent=4) 64 | 65 | kernelspec_dir = get_kernelspec_dir_path() 66 | for file in KERNELSPEC_FILES: 67 | shutil.copyfile( 68 | os.path.join(kernelspec_dir, file), os.path.join(dest_dir, file) 69 | ) 70 | 71 | 72 | def main(argv=None): 73 | logging.basicConfig( 74 | format="%(levelname)s: %(message)s", 75 | level=logging.INFO, 76 | ) 77 | 78 | ap = argparse.ArgumentParser() 79 | ap.add_argument( 80 | "--user", 81 | action="store_true", 82 | help="install to the per-user kernel registry instead of sys.prefix (use if you get permission errors during installation)", 83 | ) 84 | ap.add_argument( 85 | "--prefix", help="install to the given prefix: PREFIX/share/jupyter/kernels/" 86 | ) 87 | args = ap.parse_args(argv) 88 | 89 | if not args.user and not args.prefix: 90 | args.prefix = sys.prefix 91 | 92 | with tempfile.TemporaryDirectory() as temp_dir: 93 | create_kernelspec(temp_dir) 94 | KernelSpecManager().install_kernel_spec( 95 | temp_dir, "logtalk_kernel", user=args.user, prefix=args.prefix 96 | ) 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /logtalk_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2025 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | """ 29 | A Logtalk Jupyter kernel communicating with a Logtalk server with JSON-RPC 2.0 messages. 30 | The communication is based on 'jsonrpc_client.py' from SICStus Prolog 4.5.1. 31 | 32 | Several Prolog backends are supported. By default, the SWI-Prolog backend is used. 33 | By defining a 'logtalk_kernel_config.py' file, the Prolog backend to be used can be defined. 34 | In addition to providing an backend (the name of the used Logtalk integration script), 35 | further implementation specific data (a dictionary 'backend_data' with the backend as key) can be defined. 36 | This includes the command line arguments with which the Logtalk server can be started. 37 | 38 | Additionally, there is the Logtalk predicate 'jupyter::set_prolog_backend(+Backend)' with which the implementation can be changed 39 | (the argument is the name of the used Logtalk integration script). 40 | In order for this to work, the configured 'backend_data' dictionary needs to contain data for more than one Prolog backend. 41 | 42 | An example of a configuration file with an explanation of the options and their default values commented out can be found in the current directory. 43 | When defined, this file needs to be present in one of the Jupyter config paths (can be retrieved with 'jupyter --paths') or the current working directory. 44 | 45 | The actual kernel code is not implemented by this kernel class itself. 46 | Instead, there is the file 'logtalk_kernel_base_implementation.py' which defines the class 'LogtalkKernelBaseImplementation'. 47 | When the kernel is started, a (sub)object of this class is created. 48 | It handles the starting of and communication with the Logtalk server. 49 | For all requests (execution, shutdown, completion, inspection) the kernel receives, a 'LogtalkKernelBaseImplementation' method is called. 50 | By creating a subclass of this and defining the path to it as 'kernel_backend_path', the actual implementation code can be replaced. 51 | 52 | If no such path is defined, the path itself or the defined class is invalid, a default implementation is used instead. 53 | In case of SWI- and SICStus Prolog, the files 'swi_kernel_implementation.py' and 'sicstus_kernel_implementation.py' are used, which can be found in the current directory. 54 | Otherwise, the base implementation from the file 'logtalk_kernel_base_implementation.py' is loaded. 55 | 56 | The Logtalk Jupyter kernel is implemented in a way that basically all functionality except the loading of the configuration can easily be overriden. 57 | This is especially useful for extending the kernel for further Prolog backends. 58 | """ 59 | 60 | 61 | import importlib.util 62 | import logging 63 | import os 64 | import platform 65 | import sys 66 | 67 | from inspect import getmembers, isclass 68 | from ipykernel.kernelbase import Kernel 69 | from jupyter_core.paths import jupyter_config_path 70 | from traitlets import Bool, Dict, Unicode 71 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader 72 | 73 | #import logtalk_kernel.swi_kernel_implementation 74 | #import logtalk_kernel.sicstus_kernel_implementation 75 | 76 | from logtalk_kernel.logtalk_kernel_base_implementation import LogtalkKernelBaseImplementation 77 | 78 | # Constants 79 | DEFAULT_ERROR_PREFIX = "! " 80 | DEFAULT_INFORMATIONAL_PREFIX = "% " 81 | DEFAULT_PROGRAM_ARGS = "default" 82 | 83 | 84 | class LogtalkKernel(Kernel): 85 | """Jupyter kernel implementation for Logtalk.""" 86 | 87 | kernel_name = 'logtalk_kernel' 88 | implementation = kernel_name 89 | implementation_version = '1.0' 90 | language_info = { 91 | 'name': 'Logtalk', 92 | 'file_extension': '.lgt', 93 | 'mimetype': 'text/x-logtalk', 94 | 'codemirror_mode': 'logtalk', 95 | } 96 | banner = kernel_name 97 | 98 | # Define default configuration options 99 | 100 | # If set to True, the logging level is set to DEBUG by the kernel so that Python debugging messages are logged. 101 | jupyter_logging = Bool(False).tag(config=True) 102 | 103 | # If set to True, a log file is created by the Logtalk server. 104 | server_logging = Bool(False).tag(config=True) 105 | 106 | # The Prolog backend integration script with which the server is started. 107 | # It is required that the backend_data dictionary contains an item with the script name. 108 | if platform.system() == "Windows": 109 | EXTENSION = ".ps1" 110 | elif ( 111 | "LOGTALKHOME" in os.environ 112 | and "LOGTALKUSER" in os.environ 113 | and os.environ["LOGTALKHOME"] == os.environ["LOGTALKUSER"] 114 | ): 115 | EXTENSION = ".sh" 116 | else: 117 | EXTENSION = "" 118 | 119 | # backend = Unicode('eclipselgt' + EXTENSION).tag(config=True) 120 | # backend = Unicode('gplgt' + EXTENSION).tag(config=True) 121 | # backend = Unicode('sicstuslgt' + EXTENSION).tag(config=True) 122 | backend = Unicode('swilgt' + EXTENSION).tag(config=True) 123 | # backend = Unicode('tplgt' + EXTENSION).tag(config=True) 124 | # backend = Unicode('xvmlgt' + EXTENSION).tag(config=True) 125 | # backend = Unicode('yaplgt' + EXTENSION).tag(config=True) 126 | 127 | # The default program arguments for supported Prolog backends 128 | default_program_arguments = { 129 | "eclipselgt": ["eclipselgt", 130 | "-P", 131 | "-e", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt.", 132 | "--", "--quiet"], 133 | "eclipselgt.sh": ["eclipselgt.sh", 134 | "-P", 135 | "-e", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt.", 136 | "--", "--quiet"], 137 | "eclipselgt.ps1": ["eclipselgt.ps1", 138 | "-P", 139 | "-e", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt.\"", 140 | "--%", "--quiet"], 141 | "gplgt": ["gplgt", 142 | "--quiet", 143 | "--entry-goal", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 144 | "gplgt.sh": ["gplgt.sh", 145 | "--quiet", 146 | "--entry-goal", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 147 | "gplgt.ps1": ["gplgt.ps1", 148 | "--quiet", 149 | "--entry-goal", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt\""], 150 | "sicstuslgt": ["sicstuslgt", 151 | "--noinfo", 152 | "--goal", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt.", 153 | "--nologo"], 154 | "sicstuslgt.sh": ["sicstuslgt.sh", 155 | "--noinfo", 156 | "--goal", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt.", 157 | "--nologo"], 158 | "sicstuslgt.ps1": ["sicstuslgt.ps1", 159 | "--noinfo", 160 | "--goal", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,`'::`'`(jupyter_server`,start`);halt.\"", 161 | "--nologo"], 162 | "swilgt": ["swilgt", 163 | "-q", 164 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 165 | "swilgt.sh": ["swilgt.sh", 166 | "-q", 167 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 168 | "swilgt.ps1": ["swilgt.ps1", 169 | "-q", 170 | "-g", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt\""], 171 | "tplgt": ["tplgt", 172 | "-q", 173 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 174 | "tplgt.sh": ["tplgt.sh", 175 | "-q", 176 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 177 | "tplgt.ps1": ["tplgt.ps1", 178 | "-q", 179 | "-g", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt\""], 180 | "xvmlgt": ["xvmlgt", 181 | "-q", 182 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt."], 183 | "xvmlgt.sh": ["xvmlgt.sh", 184 | "-q", 185 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt."], 186 | "xvmlgt.ps1": ["xvmlgt.ps1", 187 | "-q", 188 | "-g", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt.\""], 189 | "yaplgt": ["yaplgt", 190 | "-q", 191 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 192 | "yaplgt.sh": ["yaplgt.sh", 193 | "-q", 194 | "-g", "set_logtalk_flag(report,off),logtalk_load('logtalk_server/loader.lgt'),'::'(jupyter_server,start);halt"], 195 | "yaplgt.ps1": ["yaplgt.ps1", 196 | "-q", 197 | "-g", "\"set_logtalk_flag`(report`,off`)`,logtalk_load`(`'logtalk_server/loader.lgt`'`)`,'::'`(jupyter_server`,start`);halt\""], 198 | } 199 | 200 | # The implementation specific data which is needed to run the Logtalk server for code execution. 201 | # This is required to be a dictionary containing at least an entry for the configured backend. 202 | # Each entry needs to define values for 203 | # - "failure_response": The output which is displayed if a query fails 204 | # - "success_response": The output which is displayed if a query succeeds without any variable bindings 205 | # - "error_prefix": The prefix output for error messages 206 | # - "informational_prefix": The prefix output for informational messages 207 | # - "program_arguments": The command line arguments (a list of strings) with which the Logtalk server can be started 208 | # Additionally, a "kernel_backend_path" can be provided, which needs to be an absolute path to a Python file. 209 | # The corresponding module is required to define a subclass of LogtalkKernelBaseImplementation named LogtalkKernelImplementation. 210 | # This can be used to override some of the kernel's basic behavior. 211 | backend_data = Dict({ 212 | "eclipselgt": { 213 | "failure_response": "No", 214 | "success_response": "Yes", 215 | "error_prefix": DEFAULT_ERROR_PREFIX, 216 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 217 | "program_arguments": DEFAULT_PROGRAM_ARGS 218 | }, 219 | "eclipselgt.sh": { 220 | "failure_response": "No", 221 | "success_response": "Yes", 222 | "error_prefix": DEFAULT_ERROR_PREFIX, 223 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 224 | "program_arguments": DEFAULT_PROGRAM_ARGS 225 | }, 226 | "eclipselgt.ps1": { 227 | "failure_response": "No", 228 | "success_response": "Yes", 229 | "error_prefix": DEFAULT_ERROR_PREFIX, 230 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 231 | "program_arguments": DEFAULT_PROGRAM_ARGS 232 | }, 233 | "gplgt": { 234 | "failure_response": "no", 235 | "success_response": "yes", 236 | "error_prefix": DEFAULT_ERROR_PREFIX, 237 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 238 | "program_arguments": DEFAULT_PROGRAM_ARGS 239 | }, 240 | "gplgt.sh": { 241 | "failure_response": "no", 242 | "success_response": "yes", 243 | "error_prefix": DEFAULT_ERROR_PREFIX, 244 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 245 | "program_arguments": DEFAULT_PROGRAM_ARGS 246 | }, 247 | "gplgt.ps1": { 248 | "failure_response": "no", 249 | "success_response": "yes", 250 | "error_prefix": DEFAULT_ERROR_PREFIX, 251 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 252 | "program_arguments": DEFAULT_PROGRAM_ARGS 253 | }, 254 | "sicstuslgt": { 255 | "failure_response": "no", 256 | "success_response": "yes", 257 | "error_prefix": DEFAULT_ERROR_PREFIX, 258 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 259 | "program_arguments": DEFAULT_PROGRAM_ARGS 260 | }, 261 | "sicstuslgt.sh": { 262 | "failure_response": "no", 263 | "success_response": "yes", 264 | "error_prefix": DEFAULT_ERROR_PREFIX, 265 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 266 | "program_arguments": DEFAULT_PROGRAM_ARGS 267 | }, 268 | "sicstuslgt.ps1": { 269 | "failure_response": "no", 270 | "success_response": "yes", 271 | "error_prefix": DEFAULT_ERROR_PREFIX, 272 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 273 | "program_arguments": DEFAULT_PROGRAM_ARGS 274 | }, 275 | "swilgt": { 276 | "failure_response": "false", 277 | "success_response": "true", 278 | "error_prefix": DEFAULT_ERROR_PREFIX, 279 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 280 | "program_arguments": DEFAULT_PROGRAM_ARGS 281 | }, 282 | "swilgt.sh": { 283 | "failure_response": "false", 284 | "success_response": "true", 285 | "error_prefix": DEFAULT_ERROR_PREFIX, 286 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 287 | "program_arguments": DEFAULT_PROGRAM_ARGS 288 | }, 289 | "swilgt.ps1": { 290 | "failure_response": "false", 291 | "success_response": "true", 292 | "error_prefix": DEFAULT_ERROR_PREFIX, 293 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 294 | "program_arguments": DEFAULT_PROGRAM_ARGS 295 | }, 296 | "tplgt": { 297 | "failure_response": "false", 298 | "success_response": "true", 299 | "error_prefix": DEFAULT_ERROR_PREFIX, 300 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 301 | "program_arguments": DEFAULT_PROGRAM_ARGS 302 | }, 303 | "tplgt.sh": { 304 | "failure_response": "false", 305 | "success_response": "true", 306 | "error_prefix": DEFAULT_ERROR_PREFIX, 307 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 308 | "program_arguments": DEFAULT_PROGRAM_ARGS 309 | }, 310 | "tplgt.ps1": { 311 | "failure_response": "false", 312 | "success_response": "true", 313 | "error_prefix": DEFAULT_ERROR_PREFIX, 314 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 315 | "program_arguments": DEFAULT_PROGRAM_ARGS 316 | }, 317 | "xvmlgt": { 318 | "failure_response": "false", 319 | "success_response": "true", 320 | "error_prefix": DEFAULT_ERROR_PREFIX, 321 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 322 | "program_arguments": DEFAULT_PROGRAM_ARGS 323 | }, 324 | "xvmlgt.sh": { 325 | "failure_response": "false", 326 | "success_response": "true", 327 | "error_prefix": DEFAULT_ERROR_PREFIX, 328 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 329 | "program_arguments": DEFAULT_PROGRAM_ARGS 330 | }, 331 | "xvmlgt.ps1": { 332 | "failure_response": "false", 333 | "success_response": "true", 334 | "error_prefix": DEFAULT_ERROR_PREFIX, 335 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 336 | "program_arguments": DEFAULT_PROGRAM_ARGS 337 | }, 338 | "yaplgt": { 339 | "failure_response": "no", 340 | "success_response": "yes", 341 | "error_prefix": DEFAULT_ERROR_PREFIX, 342 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 343 | "program_arguments": DEFAULT_PROGRAM_ARGS 344 | }, 345 | "yaplgt.sh": { 346 | "failure_response": "no", 347 | "success_response": "yes", 348 | "error_prefix": DEFAULT_ERROR_PREFIX, 349 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 350 | "program_arguments": DEFAULT_PROGRAM_ARGS 351 | }, 352 | "yaplgt.ps1": { 353 | "failure_response": "no", 354 | "success_response": "yes", 355 | "error_prefix": DEFAULT_ERROR_PREFIX, 356 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 357 | "program_arguments": DEFAULT_PROGRAM_ARGS 358 | } 359 | }).tag(config=True) 360 | 361 | # The keys which are required for each entry in the backend_data dict. 362 | required_backend_data_keys = [ 363 | "failure_response", 364 | "success_response", 365 | "error_prefix", 366 | "informational_prefix", 367 | "program_arguments" 368 | ] 369 | 370 | logger = None 371 | 372 | # A dictionary with implementation ids as keys and the corresponding LogtalkKernelBaseImplementation as value. 373 | # When a Prolog backend is started, it is added to the dictionary. 374 | # On kernel shutdown or interruption, all implementations are shutdown/interrupted. 375 | active_kernel_implementations = {} 376 | 377 | 378 | def __init__(self, **kwargs): 379 | """Initialize the kernel with logging and configuration.""" 380 | super().__init__(**kwargs) 381 | 382 | # Configure logging 383 | logging.basicConfig( 384 | format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s' 385 | ) 386 | self.logger = logging.getLogger(__name__) 387 | # For development, the logging level can be set to level DEBUG, so that all debug messages (including the ones about loading a configuration file) are output 388 | #self.logger.setLevel(logging.DEBUG) 389 | 390 | # Load configuration and backend data 391 | self.load_config_file() 392 | load_exception_message = self.load_backend_data(self.backend) 393 | if load_exception_message: 394 | # The configured backend_data is invalid 395 | raise Exception(load_exception_message) 396 | 397 | # Create an implementation object which starts the Logtalk server 398 | self.load_kernel_implementation() 399 | 400 | 401 | def load_config_file(self): 402 | """Load Logtalk kernel configuration from config files. 403 | 404 | Searches Jupyter config paths and current directory for logtalk_kernel_config.py. 405 | Config in current directory overrides other locations. 406 | """ 407 | CONFIG_FILE = 'logtalk_kernel_config.py' 408 | 409 | # Get config search paths 410 | config_paths = jupyter_config_path() 411 | config_paths.insert(0, os.getcwd()) 412 | 413 | # Find existing config files 414 | existing_paths = [ 415 | p for p in config_paths 416 | if os.path.exists(os.path.join(p, CONFIG_FILE)) 417 | ] 418 | existing_paths.reverse() # Higher priority paths first 419 | 420 | if not existing_paths: 421 | self.logger.debug( 422 | f"No {CONFIG_FILE} found in: {config_paths}. Using defaults." 423 | ) 424 | return 425 | 426 | # Load each config file 427 | for path in existing_paths: 428 | loader = PyFileConfigLoader( 429 | CONFIG_FILE, 430 | path=path, 431 | log=self.logger 432 | ) 433 | 434 | try: 435 | config = loader.load_config() 436 | except ConfigFileNotFound: 437 | self.logger.error( 438 | f"Config file not found: {os.path.join(path, CONFIG_FILE)}", 439 | exc_info=True 440 | ) 441 | except Exception: 442 | self.logger.error( 443 | f"Error loading config: {os.path.join(path, CONFIG_FILE)}", 444 | exc_info=True 445 | ) 446 | else: 447 | self.update_config(config) 448 | if self.jupyter_logging: 449 | self.logger.setLevel(logging.DEBUG) 450 | self.logger.debug(f"Loaded config: {loader.full_filename}") 451 | 452 | 453 | def load_backend_data(self, backend): 454 | """ 455 | Tries to set the implementation data for the Prolog backend with ID backend. 456 | If no such data is provided for the given backend ID or the dictionary does not contain all required keys, a corresponding message is returned. 457 | """ 458 | 459 | # Check if there is an item for the backend 460 | if backend not in self.backend_data: 461 | return f"No configured backend_data entry for Prolog backend '{backend}'" 462 | 463 | # Check if all required keys are contained in the dictionary 464 | missing_keys = [ 465 | key for key in self.required_backend_data_keys 466 | if key not in self.backend_data[backend] 467 | ] 468 | 469 | if missing_keys == []: 470 | # The implementation data is valid 471 | self.active_backend_data = self.backend_data[backend] 472 | elif len(missing_keys) == 1: 473 | return f"Backend data for '{backend}' missing entry '{missing_keys[0]}'" 474 | else: 475 | return f"Backend data for '{backend}' missing entries: {', '.join(missing_keys)}" 476 | 477 | 478 | def load_kernel_implementation(self): 479 | """ 480 | In order for the kernel to be able to execute code, a (sub)object of 'LogtalkKernelBaseImplementation' is needed. 481 | If the configured backend_data contains an entry for 'kernel_backend_path', tries to load the corresponding module and create a 'LogtalkKernelImplementation' defined in it. 482 | This causes the Logtalk server to be started so that code can be executed. 483 | 484 | If no 'kernel_backend_path' is given or it is invalid, a default implementation is used instead. 485 | For the Prolog backends with ID 'swi' or 'sicstus', there is a module defining the class 'LogtalkKernelImplementation' in the current directory. 486 | Otherwise, the 'LogtalkKernelBaseImplementation' is used. 487 | """ 488 | 489 | use_default = False 490 | 491 | if 'kernel_backend_path' in self.active_backend_data: 492 | file_path = self.active_backend_data['kernel_backend_path'] 493 | 494 | if not os.path.exists(file_path): 495 | use_default = True 496 | self.logger.debug("The configured kernel_backend_path '" + str(file_path) + "' does not exist") 497 | else: 498 | self.logger.debug("Loading kernel specific code from '" + str(file_path) + "'") 499 | # Load the module from the specified file 500 | (module_name, file_extension)= os.path.splitext(os.path.basename(file_path)) 501 | spec = importlib.util.spec_from_file_location(module_name, file_path) 502 | kernel_implementation_module = importlib.util.module_from_spec(spec) 503 | sys.modules[module_name] = kernel_implementation_module 504 | spec.loader.exec_module(kernel_implementation_module) 505 | 506 | # Try to get the class with name 'LogtalkKernelImplementation' and check if it is valid 507 | implementation_classes = list(class_pair[1] for class_pair in getmembers(kernel_implementation_module, isclass) if class_pair[0]=='LogtalkKernelImplementation') 508 | if len(implementation_classes) == 0: 509 | use_default = True 510 | self.logger.debug("The module at the configured kernel_backend_path needs to define the class 'LogtalkKernelImplementation'") 511 | else: 512 | # Try loading the specific implementation 513 | try: 514 | self.active_kernel_implementation = kernel_implementation_module.LogtalkKernelImplementation(self) 515 | if not isinstance(self.active_kernel_implementation, kernel_implementation_module.LogtalkKernelBaseImplementation): 516 | use_default = True 517 | self.logger.debug("The class 'LogtalkKernelImplementation' needs to be a subclass of 'LogtalkKernelBaseImplementation'") 518 | except Exception: 519 | use_default = True 520 | self.logger.debug("Exception while creating a 'LogtalkKernelImplementation' object" , exc_info=True) 521 | else: 522 | use_default = True 523 | self.logger.debug('No kernel_backend_path configured') 524 | 525 | if use_default: 526 | # The configured implementation could not be loaded 527 | # A default implementation is used instead 528 | # if self.backend == 'swi': 529 | # self.logger.debug("Using the default implementation for SWI-Prolog") 530 | # self.active_kernel_implementation = logtalk_kernel.swi_kernel_implementation.LogtalkKernelImplementation(self) 531 | # elif self.backend == 'sicstus': 532 | # self.logger.debug("Using the default implementation for SICStus Prolog") 533 | # self.active_kernel_implementation = logtalk_kernel.sicstus_kernel_implementation.LogtalkKernelImplementation(self) 534 | # else: 535 | self.logger.debug("Using the base implementation") 536 | self.active_kernel_implementation = LogtalkKernelBaseImplementation(self) 537 | 538 | # Add the Prolog backend specific implementation class to the dictionary of active implementations 539 | self.active_kernel_implementations[self.backend] = self.active_kernel_implementation 540 | 541 | 542 | def change_prolog_backend(self, prolog_backend): 543 | """ 544 | Change the Prolog backend to the one with ID prolog_backend. 545 | If there is a running server for that backend, it is activated. 546 | Otherwise, the backend-specific data is loaded (which starts a new server) and set as the active one. 547 | Returns False if the new backend is successfully used, True otherwise. 548 | """ 549 | 550 | self.logger.debug(f'Change Prolog backend to {prolog_backend}') 551 | 552 | if prolog_backend in self.active_kernel_implementations: 553 | # There is a running Logtalk server for the provided implementation ID 554 | # Make it the active one 555 | self.backend = prolog_backend 556 | self.active_kernel_implementation = self.active_kernel_implementations[self.backend] 557 | else: 558 | # Try to load the implementation specific data 559 | load_exception_message = self.load_backend_data(prolog_backend) 560 | if load_exception_message: 561 | self.logger.debug(load_exception_message) 562 | # The configured backend_data is invalid 563 | # Display an error message 564 | error_message = self.active_backend_data["error_prefix"] + load_exception_message 565 | 566 | display_data = { 567 | 'data': { 568 | 'text/plain': error_message, 569 | 'text/markdown': '
' + error_message + '
' }, 570 | 'metadata': {}} 571 | self.send_response(self.iopub_socket, 'display_data', display_data) 572 | return True 573 | else: 574 | self.backend = prolog_backend 575 | # Create an implementation object which starts the Logtalk server 576 | self.load_kernel_implementation() 577 | return False 578 | 579 | 580 | def interrupt_all(self): 581 | # Interrupting the kernel interrupts the running Logtalk processes, so all of them need to be restarted 582 | for backend, kernel_implementation in self.active_kernel_implementations.items(): 583 | kernel_implementation.kill_logtalk_server() 584 | 585 | 586 | ############################################################################ 587 | # Overriden kernel methods 588 | ############################################################################ 589 | 590 | 591 | def do_shutdown(self, restart): 592 | # Shutdown all active Logtalk servers so that no processes are kept running 593 | for kernel_implementation in self.active_kernel_implementations.values(): 594 | kernel_implementation.do_shutdown(restart) 595 | 596 | return {'status': 'ok', 'restart': restart} 597 | 598 | 599 | def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): 600 | return self.active_kernel_implementation.do_execute(code, silent, store_history, user_expressions, allow_stdin) 601 | 602 | 603 | def do_complete(self, code, cursor_pos): 604 | return self.active_kernel_implementation.do_complete(code, cursor_pos) 605 | 606 | 607 | def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): 608 | return self.active_kernel_implementation.do_inspect(code, cursor_pos, detail_level, omit_sections) 609 | -------------------------------------------------------------------------------- /logtalk_kernel/kernelspec/kernel.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2022-2023 Paulo Moura 4 | // Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | // SPDX-License-Identifier: MIT 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | /////////////////////////////////////////////////////////////////////////////// 26 | 27 | 28 | define(["codemirror/lib/codemirror"], function(CodeMirror) { 29 | "use strict"; 30 | 31 | return { 32 | onload: function() { 33 | console.info("Loading Logtalk kernel.js"); 34 | CodeMirror.defineMode("logtalk", function(config, parserConfig) { 35 | return { 36 | startState: function() { 37 | return { 38 | state: "initial", 39 | }; 40 | }, 41 | 42 | token: function(stream, state) { 43 | switch (state.state) { 44 | case "initial": 45 | if (stream.match(/^\%/)) { 46 | // Line comment -> consume the rest of the line 47 | stream.match(/^.+/); 48 | return "comment"; 49 | } else if (stream.match(/^\/\*/)) { 50 | // Block comment start -> switch to comment state 51 | state.state = "comment"; 52 | return "comment"; 53 | } else if (stream.match(/^"(?:[^"])*"/)) { 54 | return "string"; 55 | } else if (stream.match(/^'(?:[^'])*'/)) { 56 | return "atom"; 57 | } else if (stream.match(/^(?:[0-9]+)/)) { 58 | return "number"; 59 | } else if (stream.match(/^(?:!)/)) { 60 | return "builtin"; 61 | } else if (stream.match(/^(?:=:=|:-|@<|@>|@=<|@>=|[-+\\/><=*#$?^])/)) { 62 | return "operator"; 63 | } else if (stream.match(/^[\s()\[\]{},\.|;@]+/)) { 64 | return null; 65 | } else if (stream.match(/^:+/)) { 66 | // ":" needs to be checked on its own 67 | // Otherwise, if ":-" occurs after one of the characters above (e.g. ")"), ":" would not be highlighted correctly 68 | return null; 69 | } else { 70 | const atom_or_variable = stream.match(/^[A-Za-z_]+[A-Za-z_0-9]*/); 71 | if (atom_or_variable && atom_or_variable.toString() !== "") { 72 | var firstCharacter = atom_or_variable.toString().charAt(0); 73 | if (firstCharacter !== "_" && firstCharacter === firstCharacter.toLowerCase()) { 74 | // If the token starts with a lower case letter, it is an atom 75 | if (stream.peek() === "(") { 76 | // Atoms which are preceded by "(" are highlighted differently 77 | return "builtin"; 78 | } else { 79 | return "atom"; 80 | } 81 | } else { 82 | return "variable-2"; 83 | } 84 | } else { 85 | // Consume the rest of the line and mark it as an error 86 | stream.match(/^.+/); 87 | return "error"; 88 | } 89 | } 90 | 91 | case "comment": 92 | while (!stream.eol()) { 93 | // Consume everything except for "*" 94 | stream.match(/^[^\*]+/); 95 | if (stream.match(/^\*\//)) { 96 | // "*/" -> switch back to initial state 97 | state.state = "initial"; 98 | return "comment"; 99 | } else { 100 | // "*" without "/" -> consume and stay in comment state 101 | stream.match(/^\*/); 102 | } 103 | } 104 | return "comment"; 105 | 106 | default: 107 | throw new Error("Unhandled state: " + state.state); 108 | } 109 | }, 110 | }; 111 | }); 112 | 113 | CodeMirror.defineMIME("text/x-logtalk", "logtalk"); 114 | }, 115 | }; 116 | }); 117 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_kernel_config.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2025 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | """Logtalk Jupyter Kernel Configuration. 28 | 29 | This module contains default configuration settings for the Logtalk Jupyter 30 | kernel, including backend-specific settings and logging options. 31 | """ 32 | 33 | import platform 34 | import os 35 | 36 | # Constants 37 | DEFAULT_ERROR_PREFIX = "! " 38 | DEFAULT_INFORMATIONAL_PREFIX = "% " 39 | DEFAULT_PROGRAM_ARGS = "default" 40 | 41 | c = get_config() 42 | 43 | # If set to True, the logging level is set to DEBUG by the kernel so that Python debugging messages are logged. 44 | c.LogtalkKernel.jupyter_logging = False 45 | # If set to True, a log file is created by the Logtalk server 46 | c.LogtalkKernel.server_logging = False 47 | 48 | # The Prolog backend integration script with which the server is started. 49 | if platform.system() == "Windows": 50 | EXTENSION = ".ps1" 51 | elif ( 52 | "LOGTALKHOME" in os.environ 53 | and "LOGTALKUSER" in os.environ 54 | and os.environ["LOGTALKHOME"] == os.environ["LOGTALKUSER"] 55 | ): 56 | EXTENSION = ".sh" 57 | else: 58 | EXTENSION = "" 59 | # c.LogtalkKernel.backend = "eclipselgt" + EXTENSION 60 | # c.LogtalkKernel.backend = "gplgt" + EXTENSION 61 | # c.LogtalkKernel.backend = "sicstuslgt" + EXTENSION 62 | c.LogtalkKernel.backend = "swilgt" + EXTENSION 63 | # c.LogtalkKernel.backend = "tplgt" + EXTENSION 64 | # c.LogtalkKernel.backend = "xvmlgt" + EXTENSION 65 | # c.LogtalkKernel.backend = "yaplgt" + EXTENSION 66 | 67 | # The implementation specific data which is needed to run the Logtalk server for code execution. 68 | # This is required to be a dictionary containing at least an entry for the configured backend. 69 | # Each entry needs to define values for 70 | # - "failure_response": The output which is displayed if a query fails 71 | # - "success_response": The output which is displayed if a query succeeds without any variable bindings 72 | # - "error_prefix": The prefix output for error messages 73 | # - "informational_prefix": The prefix output for informational messages 74 | # - "program_arguments": The command line arguments (a list of strings) with which the Logtalk server can be started 75 | # For all backends, the default Logtalk server can be used by configuring the string "default" 76 | # Additionally, a "kernel_backend_path" can be provided, which needs to be an absolute path to a Python file. 77 | # The corresponding module is required to define a subclass of LogtalkKernelBaseImplementation named LogtalkKernelImplementation. 78 | # This can be used to override some of the kernel's basic behavior. 79 | c.LogtalkKernel.backend_data = { 80 | "eclipselgt": { 81 | "failure_response": "No", 82 | "success_response": "Yes", 83 | "error_prefix": DEFAULT_ERROR_PREFIX, 84 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 85 | "program_arguments": DEFAULT_PROGRAM_ARGS, 86 | }, 87 | "eclipselgt.sh": { 88 | "failure_response": "No", 89 | "success_response": "Yes", 90 | "error_prefix": DEFAULT_ERROR_PREFIX, 91 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 92 | "program_arguments": DEFAULT_PROGRAM_ARGS, 93 | }, 94 | "eclipselgt.ps1": { 95 | "failure_response": "No", 96 | "success_response": "Yes", 97 | "error_prefix": DEFAULT_ERROR_PREFIX, 98 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 99 | "program_arguments": DEFAULT_PROGRAM_ARGS, 100 | }, 101 | "gplgt": { 102 | "failure_response": "no", 103 | "success_response": "yes", 104 | "error_prefix": DEFAULT_ERROR_PREFIX, 105 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 106 | "program_arguments": DEFAULT_PROGRAM_ARGS, 107 | }, 108 | "gplgt.sh": { 109 | "failure_response": "no", 110 | "success_response": "yes", 111 | "error_prefix": DEFAULT_ERROR_PREFIX, 112 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 113 | "program_arguments": DEFAULT_PROGRAM_ARGS, 114 | }, 115 | "gplgt.ps1": { 116 | "failure_response": "no", 117 | "success_response": "yes", 118 | "error_prefix": DEFAULT_ERROR_PREFIX, 119 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 120 | "program_arguments": DEFAULT_PROGRAM_ARGS, 121 | }, 122 | "sicstuslgt": { 123 | "failure_response": "no", 124 | "success_response": "yes", 125 | "error_prefix": DEFAULT_ERROR_PREFIX, 126 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 127 | "program_arguments": DEFAULT_PROGRAM_ARGS, 128 | }, 129 | "sicstuslgt.sh": { 130 | "failure_response": "no", 131 | "success_response": "yes", 132 | "error_prefix": DEFAULT_ERROR_PREFIX, 133 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 134 | "program_arguments": DEFAULT_PROGRAM_ARGS, 135 | }, 136 | "sicstuslgt.ps1": { 137 | "failure_response": "no", 138 | "success_response": "yes", 139 | "error_prefix": DEFAULT_ERROR_PREFIX, 140 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 141 | "program_arguments": DEFAULT_PROGRAM_ARGS, 142 | }, 143 | "swilgt": { 144 | "failure_response": "false", 145 | "success_response": "true", 146 | "error_prefix": DEFAULT_ERROR_PREFIX, 147 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 148 | "program_arguments": DEFAULT_PROGRAM_ARGS, 149 | }, 150 | "swilgt.sh": { 151 | "failure_response": "false", 152 | "success_response": "true", 153 | "error_prefix": DEFAULT_ERROR_PREFIX, 154 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 155 | "program_arguments": DEFAULT_PROGRAM_ARGS, 156 | }, 157 | "swilgt.ps1": { 158 | "failure_response": "false", 159 | "success_response": "true", 160 | "error_prefix": DEFAULT_ERROR_PREFIX, 161 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 162 | "program_arguments": DEFAULT_PROGRAM_ARGS, 163 | }, 164 | "tplgt": { 165 | "failure_response": "false", 166 | "success_response": "true", 167 | "error_prefix": DEFAULT_ERROR_PREFIX, 168 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 169 | "program_arguments": DEFAULT_PROGRAM_ARGS, 170 | }, 171 | "tplgt.sh": { 172 | "failure_response": "false", 173 | "success_response": "true", 174 | "error_prefix": DEFAULT_ERROR_PREFIX, 175 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 176 | "program_arguments": DEFAULT_PROGRAM_ARGS, 177 | }, 178 | "tplgt.ps1": { 179 | "failure_response": "false", 180 | "success_response": "true", 181 | "error_prefix": DEFAULT_ERROR_PREFIX, 182 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 183 | "program_arguments": DEFAULT_PROGRAM_ARGS, 184 | }, 185 | "xvmlgt": { 186 | "failure_response": "false", 187 | "success_response": "true", 188 | "error_prefix": DEFAULT_ERROR_PREFIX, 189 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 190 | "program_arguments": DEFAULT_PROGRAM_ARGS, 191 | }, 192 | "xvmlgt.sh": { 193 | "failure_response": "false", 194 | "success_response": "true", 195 | "error_prefix": DEFAULT_ERROR_PREFIX, 196 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 197 | "program_arguments": DEFAULT_PROGRAM_ARGS, 198 | }, 199 | "xvmlgt.ps1": { 200 | "failure_response": "false", 201 | "success_response": "true", 202 | "error_prefix": DEFAULT_ERROR_PREFIX, 203 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 204 | "program_arguments": DEFAULT_PROGRAM_ARGS, 205 | }, 206 | "yaplgt": { 207 | "failure_response": "no", 208 | "success_response": "yes", 209 | "error_prefix": DEFAULT_ERROR_PREFIX, 210 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 211 | "program_arguments": DEFAULT_PROGRAM_ARGS, 212 | }, 213 | "yaplgt.sh": { 214 | "failure_response": "no", 215 | "success_response": "yes", 216 | "error_prefix": DEFAULT_ERROR_PREFIX, 217 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 218 | "program_arguments": DEFAULT_PROGRAM_ARGS, 219 | }, 220 | "yaplgt.ps1": { 221 | "failure_response": "no", 222 | "success_response": "yes", 223 | "error_prefix": DEFAULT_ERROR_PREFIX, 224 | "informational_prefix": DEFAULT_INFORMATIONAL_PREFIX, 225 | "program_arguments": DEFAULT_PROGRAM_ARGS, 226 | }, 227 | } 228 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2025 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | :- object(jupyter). 29 | 30 | :- info([ 31 | version is 0:17:0, 32 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 33 | date is 2025-05-01, 34 | comment is 'This object provides special predicates which can be used in call requests by the client. Some of these predicates need to be the only goal of a query. Otherwise, they cannot be determined as special predicates and do not work as expected.' 35 | ]). 36 | 37 | :- initialization(debugger::leash(none)). 38 | 39 | :- public(help/0). 40 | :- mode(help, one). 41 | :- info(help/0, [ 42 | comment is 'Prints the documentation for all public predicates defined in ``jupyter`` object.' 43 | ]). 44 | 45 | :- public(version/0). 46 | :- mode(version, one). 47 | :- info(version/0, [ 48 | comment is 'Prints the kernel version.' 49 | ]). 50 | 51 | :- public(versions/0). 52 | :- mode(versions, one). 53 | :- info(versions/0, [ 54 | comment is 'Prints the Logtalk, Prolog backend, and kernel versions.' 55 | ]). 56 | 57 | :- public(version/4). 58 | :- mode(version(-integer, -integer, -integer, -atom), one). 59 | :- info(version/4, [ 60 | comment is 'Returns the current version.', 61 | argnames is ['Major', 'Minor', 'Patch', 'Status'] 62 | ]). 63 | 64 | :- public(set_prolog_backend/1). 65 | :- mode(set_prolog_backend(+atom), zero_or_more). 66 | :- info(set_prolog_backend/1, [ 67 | comment is 'Activates the given Prolog backend. Must be the only goal of a query.', 68 | argnames is ['Backend'] 69 | ]). 70 | 71 | :- public(magic/0). 72 | :- mode(magic, one). 73 | :- info(magic/0, [ 74 | comment is 'Prints the documentation of all cell and line magic.' 75 | ]). 76 | 77 | :- public(trace/1). 78 | :- mode(trace(+callable), zero_or_more). 79 | :- info(trace/1, [ 80 | comment is 'Prints the trace of the goal ``Goal``.', 81 | argnames is ['Goal'] 82 | ]). 83 | 84 | :- public(pwd/0). 85 | :- mode(pwd, one). 86 | :- info(pwd/0, [ 87 | comment is 'Prints the current working directory.' 88 | ]). 89 | 90 | :- public(cd/1). 91 | :- mode(cd(+atom), zero_or_more). 92 | :- info(cd/1, [ 93 | comment is 'Changes the current working directory.', 94 | argnames is ['Directory'] 95 | ]). 96 | 97 | :- public(retry/0). 98 | :- mode(retry, one). 99 | :- info(retry/0, [ 100 | comment is 'Causes backtracking of the latest active query. Must be the only goal of a query.' 101 | ]). 102 | 103 | :- public(print_queries/0). 104 | :- mode(print_queries, one). 105 | :- info(print_queries/0, [ 106 | comment is 'Prints previous queries.' 107 | ]). 108 | 109 | :- public(print_queries/1). 110 | :- mode(print_queries(?list(callable)), one). 111 | :- info(print_queries/1, [ 112 | comment is 'Prints previous queries which were executed in requests with IDs in ``Ids``.', 113 | argnames is ['Ids'] 114 | ]). 115 | 116 | :- public(print_query_time/0). 117 | :- mode(print_query_time, one). 118 | :- info(print_query_time/0, [ 119 | comment is 'Prints previous query execution time.' 120 | ]). 121 | 122 | :- public(print_variable_bindings/0). 123 | :- mode(print_variable_bindings, one). 124 | :- info(print_variable_bindings/0, [ 125 | comment is 'Prints variable bindings from previous queries. For each variable, the latest value it was bound to is shown. The variable value can be accessed with a ``$Var`` term by any query. In that case, the term is replaced by the value. If there is no previous value, an error message is printed.' 126 | ]). 127 | 128 | :- public(print_table/1). 129 | :- mode(print_table(+callable), one). 130 | :- info(print_table/1, [ 131 | comment is 'Computes all solutions of the goal using ``findall/3`` and prints a table with the solution variable bindings. Values for variable names starting with an underscore are omitted. Must be the only goal of a query.', 132 | argnames is ['Goal'] 133 | ]). 134 | 135 | :- public(print_and_save_table/3). 136 | :- mode(print_and_save_table(+callable, +atom, +atom), one). 137 | :- info(print_and_save_table/3, [ 138 | comment is 'Same as the ``print_table/1`` predicate but also saves the table to a file. Supported formats are ``csv`` and ``tsv``. Must be the only goal of a query.', 139 | argnames is ['Goal', 'Format', 'File'] 140 | ]). 141 | 142 | :- public(print_table/2). 143 | :- mode(print_table(@list(term), @list(atom)), one). 144 | :- info(print_table/2, [ 145 | comment is 'Prints a table of the values using the variable names to fill the header of the table. Must be the only goal of a query.', 146 | argnames is ['Values', 'VariableNames'] 147 | ]). 148 | 149 | :- public(show_term/1). 150 | :- mode(show_term(@term), one). 151 | :- info(show_term/1, [ 152 | comment is 'Displays a term as a graph. Must be the only goal of a query.', 153 | argnames is ['Term'] 154 | ]). 155 | 156 | :- public(show_data/1). 157 | :- mode(show_data(+callable), one). 158 | :- info(show_data/1, [ 159 | comment is 'Displays data produced by a goal. Expects a variable named Data or _Data to be bound to a list fo pairs. Must be the only goal of a query.', 160 | argnames is ['Goal'] 161 | ]). 162 | 163 | :- public([ 164 | %halt/0, 165 | predicate_docs/1, 166 | %print_sld_tree/1, % print_sld_tree(+Goal) 167 | print_transition_graph/4 % print_transition_graph(+PredSpec, +FromIndex, +ToIndex, +LabelIndex) 168 | % update_completion_data/0 169 | ]). 170 | 171 | :- uses(debugger, [leash/1, trace/0, notrace/0]). 172 | :- uses(format, [format/2]). 173 | :- uses(list, [append/3, last/2, member/2]). 174 | :- uses(term_io, [read_term_from_codes/3]). 175 | :- uses(user, [atomic_list_concat/2]). 176 | :- uses(jupyter_logging, [log/1, log/2]). 177 | :- uses(jupyter_query_handling, [query_data/4]). 178 | :- uses(jupyter_variable_bindings, [var_bindings/1]). 179 | 180 | version :- 181 | version(Major, Minor, Patch, Status), 182 | %log('Version ~w.~w.~w-~w~n',[Major,Minor,Patch,Status]), 183 | format('Logtalk Jupyter kernel ~w.~w.~w-~w~n', [Major, Minor, Patch, Status]). 184 | 185 | versions :- 186 | current_logtalk_flag(version_data, logtalk(LogtalkMajor, LogtalkMinor, LogtalkPatch, LogtalkStatus)), 187 | format('Logtalk ~w.~w.~w-~w~n', [LogtalkMajor, LogtalkMinor, LogtalkPatch, LogtalkStatus]), 188 | current_logtalk_flag(prolog_dialect, Backend), 189 | backend(Backend, BackendName), 190 | current_logtalk_flag(prolog_version, v(BackendMajor, BackendMinor, BackendPatch)), 191 | format('~w ~w.~w.~w~n', [BackendName, BackendMajor, BackendMinor, BackendPatch]), 192 | version. 193 | 194 | version(0, 31, 0, beta). 195 | 196 | backend(b, 'B-Prolog'). 197 | backend(ciao, 'Ciao Prolog'). 198 | backend(cx, 'CxProlog'). 199 | backend(eclipse, 'ECLiPSe'). 200 | backend(gnu, 'GNU Prolog'). 201 | backend(ji, 'JIProlog'). 202 | backend(quintus, 'Quintus Prolog'). 203 | backend(sicstus, 'SICStus Prolog'). 204 | backend(swi, 'SWI-Prolog'). 205 | backend(tau, 'Tau Prolog'). 206 | backend(trealla, 'Trealla Prolog'). 207 | backend(xsb, 'XSB'). 208 | backend(xvm, 'XVM'). 209 | backend(yap, 'YAP'). 210 | 211 | % Help 212 | 213 | % jupyter::predicate_docs(-PredDocs) 214 | % 215 | % PredDocs is a list with elements of the form Pred=Doc, where Pred is a predicate exported by this module and Doc is its documentation as an atom. 216 | predicate_docs(PredDocs) :- 217 | findall(Pred=Doc, predicate_doc(Pred, Doc), PredDocs). 218 | 219 | % Prints the documentation for all predicates defined in jupyter object. 220 | help :- 221 | predicate_docs(PredDocs), 222 | log(PredDocs), 223 | print_pred_docs(PredDocs). 224 | 225 | print_pred_docs([]) :- !. 226 | print_pred_docs([_Pred=Doc]) :- 227 | !, 228 | format('~w', [Doc]). 229 | print_pred_docs([_Pred=Doc|PredDocs]) :- 230 | format('~w~n~n--------------------------------------------------------------------------------~n~n', [Doc]), 231 | print_pred_docs(PredDocs). 232 | 233 | magic :- 234 | format('Cell magic:~n~n', []), 235 | format(' %%load FILE.EXT~n', []), 236 | format(' Saves and loads a file using the logtalk_load/2 predicate~n', []), 237 | format(' %%save FILE.EXT~n', []), 238 | format(' Saves a file~n', []), 239 | format(' %%file FILE.EXT~n', []), 240 | format(' Saves and loads a file using the logtalk_load/2 predicate~n', []), 241 | format(' %%file+ FILE.EXT~n', []), 242 | format(' Appends to a file and loads it using the logtalk_load/2 predicate~n', []), 243 | format(' %%user~n', []), 244 | format(' Saves and loads a user.lgt file using the logtalk_load/2 predicate~n', []), 245 | format(' %%user+~n', []), 246 | format(' Appends to a user.lgt file and loads it using the logtalk_load/2 predicate~n~n', []), 247 | format(' %%table~n', []), 248 | format(' Prints a table with a column per variable binding for all goal solutions~n', []), 249 | format(' %%csv FILE.csv~n', []), 250 | format(' Prints a table with a column per variable binding for all goal solutions but also saves it to a CSV file~n', []), 251 | format(' %%tsv FILE.tsv~n', []), 252 | format(' Prints a table with a column per variable binding for all goal solutions but also saves it to a TSV file~n', []), 253 | format(' %%data~n', []), 254 | format(' Data visualization for a goal binding a variable named Data or _Data with a list of pairs~n', []), 255 | format(' %%tree~n', []), 256 | format(' Prints a tree representation of a term~n~n', []), 257 | format(' %%highlight~n', []), 258 | format(' Highlights cell contents as Logtalk code~n~n', []), 259 | format('Line magic:~n~n', []), 260 | format(' %bindings~n', []), 261 | format(' Prints variable bindings from previous queries~n', []), 262 | format(' %queries~n', []), 263 | format(' Prints previous queries~n~n', []), 264 | format(' %pwd~n', []), 265 | format(' Prints the current working directory~n~n', []), 266 | format(' %help~n', []), 267 | format(' Prints documentation for all predicates from object jupyter~n', []), 268 | format(' %versions~n', []), 269 | format(' Prints Logtalk, Prolog backend, and Jupyter kernel versions~n', []), 270 | format(' %flags~n', []), 271 | format(' Prints a table with all Logtalk flags and their values~n~n', []), 272 | format(' %magic~n', []), 273 | format(' Prints help in using cell and line magic~n', []). 274 | 275 | predicate_doc('jupyter::halt/0', Doc) :- 276 | atomic_list_concat([ 277 | 'jupyter::halt or halt', 278 | '\n\n Shuts down the running Logtalk process.', 279 | '\n\n The next time code is to be executed, a new process is started.', 280 | '\n Everything defined in the database before does not exist anymore.', 281 | '\n\n Corresponds to the functionality of halt/0.', 282 | '\n Has the same effect as interrupting or restarting the Jupyter kernel.' 283 | ], Doc). 284 | predicate_doc('jupyter::help/0', Doc) :- 285 | atomic_list_concat([ 286 | 'jupyter::help', 287 | '\n\n Outputs the documentation for all predicates from object jupyter.' 288 | ], Doc). 289 | predicate_doc('jupyter::versions/0', Doc) :- 290 | atomic_list_concat([ 291 | 'jupyter::versions', 292 | '\n\n Prints Logtalk, Prolog backend, and Jupyter kernel versions.' 293 | ], Doc). 294 | predicate_doc('jupyter::magic/0', Doc) :- 295 | atomic_list_concat([ 296 | 'jupyter::magic', 297 | '\n\n Outputs the documentation for all cell and line magic.' 298 | ], Doc). 299 | predicate_doc('jupyter::print_query_time', Doc) :- 300 | atomic_list_concat([ 301 | 'jupyter::print_query_time', 302 | '\n\n Prints the previous query and its runtime in seconds.' 303 | ], Doc). 304 | predicate_doc('jupyter::print_queries/1', Doc) :- 305 | atomic_list_concat([ 306 | 'jupyter::print_queries(+Ids)', 307 | '\n\n Prints previous queries which were executed in requests with IDs in Ids.', 308 | '\n\n Any $Var terms might be replaced by the variable\'s name.', 309 | '\n This is the case if a previous query with ID in Ids contains Var.', 310 | '\n Otherwise, $Var is not replaced.' 311 | ], Doc). 312 | % predicate_doc('jupyter::print_sld_tree/1', Doc) :- 313 | % atomic_list_concat([ 314 | % 'jupyter::print_sld_tree(+Goal)', 315 | % '\n\n Executes the goal Goal and prints a graph resembling its SLD tree.', 316 | % '\n\n Must be the only goal of a query.' 317 | % ], Doc). 318 | predicate_doc('jupyter::print_table/1', Doc) :- 319 | atomic_list_concat([ 320 | 'jupyter::print_table(+Goal)', 321 | '\n\n Computes all results of the goal Goal with findall/3.', 322 | '\n These are printed in a table.', 323 | '\n Values for variable names starting with an underscore are omitted.', 324 | '\n\n Must be the only goal of a query.', 325 | '\n\n Example: jupyter::print_table(current_logtalk_flag(Name, Value)).' 326 | ], Doc). 327 | predicate_doc('jupyter::print_and_save_table/3', Doc) :- 328 | atomic_list_concat([ 329 | 'jupyter::print_and_save_table(+Goal, +Format, File)', 330 | '\n\n Computes all results of the goal Goal with findall/3.', 331 | '\n These are printed in a table but also saved to a file.', 332 | '\n Supported file formats are csv and tsv.', 333 | '\n Values for variable names starting with an underscore are omitted.', 334 | '\n\n Must be the only goal of a query.', 335 | '\n\n Example: jupyter::print_and_save_table(current_logtalk_flag(Name, Value), tsv, \'flags.tsv\').' 336 | ], Doc). 337 | predicate_doc('jupyter::print_table/2', Doc) :- 338 | atomic_list_concat([ 339 | 'jupyter::print_table(+ValuesLists, +VariableNames)', 340 | '\n\n Prints a table of the values in ValuesLists.', 341 | '\n\n ValuesLists is a list of lists of the same length.', 342 | '\n Each list corresponds to one line of the table.', 343 | '\n\n VariableNames is used to fill the header of the table.', 344 | '\n If VariableNames=[], capital letters are used.', 345 | '\n Otherwise, VariableNames needs to be a list of ground terms.', 346 | '\n It needs to be of the same length as the values lists.', 347 | '\n\n Must be the only goal of a query.', 348 | '\n\n Can be used with a predicate like findall/3, but not directly.', 349 | '\n Instead, a previous binding can be accessed with a $Var term.', 350 | '\n\n Examples:', 351 | '\n jupyter::print_table([[10,100],[20,400],[30,900]], [\'X\', \'Y\']).', 352 | '\n jupyter::print_table($ResultLists, []).' 353 | ], Doc). 354 | predicate_doc('jupyter::print_transition_graph/4', Doc) :- 355 | atomic_list_concat([ 356 | 'jupyter::print_transition_graph(+PredSpec, +FromIndex, +ToIndex, +LabelIndex)', 357 | '\n\n Finds all solutions of the predicate with specification PredSpec.', 358 | '\n Prints a graph interpreting the solutions as transitions.', 359 | '\n\n PredSpec needs to be of the form PredName/PredArity.', 360 | '\n\n FromIndex and ToIndex point to predicate arguments used as nodes.', 361 | '\n LabelIndex points to the argument providing a label for an edge.', 362 | '\n If LabelIndex=0, no label is shown.', 363 | '\n\n Must be the only goal of a query.' 364 | ], Doc). 365 | predicate_doc('jupyter::show_term/1', Doc) :- 366 | atomic_list_concat([ 367 | 'jupyter::show_term(+Term)', 368 | '\n\n Displays a term as a graph.', 369 | '\n\n Must be the only goal of a query.' 370 | ], Doc). 371 | predicate_doc('jupyter::show_data/1', Doc) :- 372 | atomic_list_concat([ 373 | 'jupyter::show_data(+Goal)', 374 | '\n\n Displays data produced by a goal.', 375 | '\n\n Expects a variable named Data or _Data to be bound to a list fo pairs.', 376 | '\n\n Must be the only goal of a query.' 377 | ], Doc). 378 | predicate_doc('jupyter::print_variable_bindings/0', Doc) :- 379 | atomic_list_concat([ 380 | 'jupyter::print_variable_bindings', 381 | '\n\n Prints variable bindings from previous queries.', 382 | '\n For each variable, the latest value it was bound to is shown.', 383 | '\n\n The variable value can be accessed with a $Var term by any query.', 384 | '\n In that case, the term is replaced by the value.', 385 | '\n If there is no previous value, an error message is printed.' 386 | ], Doc). 387 | predicate_doc('jupyter::pwd/0', Doc) :- 388 | atomic_list_concat([ 389 | 'jupyter::pwd', 390 | '\n\n Prints the current working directory.' 391 | ], Doc). 392 | predicate_doc('jupyter::cd/1', Doc) :- 393 | atomic_list_concat([ 394 | 'jupyter::cd(+Directory)', 395 | '\n\n Changes the current working directory.' 396 | ], Doc). 397 | predicate_doc('jupyter::retry/0', Doc) :- 398 | atomic_list_concat([ 399 | 'jupyter::retry or retry', 400 | '\n\n Causes backtracking of the latest active query.', 401 | '\n\n Must be the only goal of a query.' 402 | ], Doc). 403 | predicate_doc('jupyter::set_prolog_backend/1', Doc) :- 404 | atomic_list_concat([ 405 | 'jupyter::set_prolog_backend(+Backend)', 406 | '\n\n Activates the given Prolog backend.', 407 | '\n\n must be the only goal of a query.' 408 | ], Doc). 409 | predicate_doc('jupyter::trace/1', Doc) :- 410 | atomic_list_concat([ 411 | 'jupyter::trace(+Goal)', 412 | '\n\n Prints the trace of the goal Goal.', 413 | '\n\n Logtalk code needs to be compiled in debug mode.', 414 | '\n By default, no port is leashed so that no user interaction is requested.', 415 | '\n All previously set breakpoints are still active.', 416 | '\n\n Must be the only goal of a query in order to work as expected.' 417 | ], Doc). 418 | % predicate_doc('jupyter::update_completion_data/0', Doc) :- 419 | % atomic_list_concat([ 420 | % 'jupyter::update_completion_data', 421 | % '\n\n Updates the predicate data used for code completion using Tab.', 422 | % '\n\n This is done by retrieving all built-in and exported predicates.', 423 | % '\n Needed to use completion for predicates from a newly loaded code.', 424 | % '\n\n Must be the only goal of a query.' 425 | % ], Doc). 426 | 427 | % Trace 428 | 429 | % trace(+Goal) 430 | % 431 | % Switch the tracer on, call the goal Goal and stop the tracer. 432 | % Debug mode is switched on so that any breakpoints which might exist can be activated. 433 | :- meta_predicate(trace(*)). 434 | trace(Goal) :- 435 | leash(none), 436 | trace, 437 | ( {Goal} -> 438 | notrace 439 | ; notrace 440 | ), 441 | !. 442 | 443 | pwd :- 444 | os::working_directory(Directory), 445 | format('Working directory: ~q~n', [Directory]). 446 | 447 | cd(Directory) :- 448 | os::change_directory(Directory). 449 | 450 | % Variable bindings 451 | 452 | % print_variable_bindings 453 | % 454 | % Print the previous variable bindings which can be reused with a term of the form $Var. 455 | print_variable_bindings :- 456 | var_bindings(Bindings), 457 | ( Bindings == [] -> 458 | format('No previous variable bindings~n', []) 459 | ; print_variable_bindings(Bindings) 460 | ). 461 | 462 | print_variable_bindings([]). 463 | print_variable_bindings([Name=Value| Bindings]) :- 464 | format('$~w = ~q~n', [Name, Value]), 465 | print_variable_bindings(Bindings). 466 | 467 | % print_query_time 468 | % 469 | % Prints the latest previous query and its runtime in seconds. 470 | print_query_time :- 471 | findall( 472 | Goal-Runtime, 473 | query_data(_CallRequestId, Runtime, term_data(Goal, _NameVarPairs), _OriginalTermData), 474 | GoalRuntimes 475 | ), 476 | last(GoalRuntimes, Goal-Runtime), 477 | format('Query: ~w~nRuntime: ~w s~n', [Goal, Runtime]). 478 | print_query_time :- 479 | format('* There is no previous query', []), 480 | fail. 481 | 482 | % print_queries(+Ids) 483 | % 484 | % Prints the previous queries with ids in Ids in a way that they can be 485 | % - copied to a cell and executed right away or 486 | % - expanded with a head to define a predicate 487 | % If a query contains a term of the form $Var and a previous query contains the variable Var, $Var is replaced by the variable name. 488 | print_queries :- 489 | print_queries(_). 490 | 491 | print_queries(Ids) :- 492 | ( var(Ids) -> 493 | findall(Id, query_data(Id, _, _, _),Ids0), 494 | % cells can contain multiple queries 495 | sort(Ids0, Ids) 496 | ; true 497 | ), 498 | findall( 499 | TermData-OriginalTermData, 500 | (member(Id, Ids), query_data(Id, _Runtime, TermData, OriginalTermData)), 501 | QueriesData 502 | ), 503 | print_queries(QueriesData, []). 504 | 505 | % print_queries(+QueriesData, +PreviousNameVarPairs) 506 | print_queries([], _PreviousNameVarPairs). 507 | print_queries([QueryData], PreviousNameVarPairs) :- 508 | !, 509 | print_previous_query(QueryData, PreviousNameVarPairs, _NewPreviousNameVarPairs, QueryAtom), 510 | format('~w.~n', [QueryAtom]). 511 | print_queries([QueryData|QueriesData], PreviousNameVarPairs) :- 512 | print_previous_query(QueryData, PreviousNameVarPairs, NewPreviousNameVarPairs, QueryAtom), 513 | format('~w,~n', [QueryAtom]), 514 | print_queries(QueriesData, NewPreviousNameVarPairs). 515 | 516 | % print_previous_query(+QueryData, +PreviousNameVarPairs, -NewPreviousNameVarPairs, -QueryAtom) 517 | print_previous_query(term_data(QueryAtom, NameVarPairs)-same, PreviousNameVarPairs, NewPreviousNameVarPairs, QueryAtom) :- 518 | % There is no $Var term in the query 519 | append(NameVarPairs, PreviousNameVarPairs, NewPreviousNameVarPairs), 520 | !. 521 | print_previous_query(_TermData-OriginalTermData, PreviousNameVarPairs, NewPreviousNameVarPairs, ExpandedTerm) :- 522 | OriginalTermData = term_data(OriginalTermAtom, OriginalNameVarPairs), 523 | append(OriginalNameVarPairs, PreviousNameVarPairs, NewPreviousNameVarPairs), 524 | % Read the original term from the atom 525 | atom_codes(OriginalTermAtom, OriginalTermCodes), 526 | append(OriginalTermCodes, [46], OriginalTermCodesWithFullStop), 527 | read_term_from_codes(OriginalTermCodesWithFullStop, OriginalTerm, [variable_names(OriginalNameVarPairs)]), 528 | % Expand the term by replacing variables and terms of the form $Var 529 | expand_term(OriginalTerm, OriginalNameVarPairs, PreviousNameVarPairs, ExpandedTerm). 530 | 531 | 532 | % expand_term(+Term, +NameVarPairs, +PreviousNameVarPairs, -ExpandedTerm) 533 | % 534 | % NameVarPairs is a list of Name=Var pairs, where Name is the name of a variable Var from the current term. 535 | % PreviousNameVarPairs contains Name=Var pairs from previous queries. 536 | % The term Term is expanded to ExpandedTerm in the following way: 537 | % - If Term is a variable, it is replaced by its Name from NameVarPairs. 538 | % - If Term is of the form $Var: 539 | % - If the name of the variable Var occurred in one of the previous queries (is contained in PreviousNameVarPairs), $Var is replaced by the variable name. 540 | % - Otherwise, $Var is replaced by $Name where Name is the name of the variable. 541 | % - If Term is a compound term, its arguments are expanded. 542 | expand_term(Var, NameVarPairs, _PreviousNameVarPairs, Name) :- 543 | var(Var), 544 | member(Name=Var, NameVarPairs), 545 | !. 546 | expand_term(Atomic, _NameVarPairs, _PreviousNameVarPairs, Atomic) :- 547 | atomic(Atomic), 548 | !. 549 | expand_term($(Var), NameVarPairs, PreviousNameVarPairs, ExpandedTerm) :- 550 | !, 551 | % Get the name of the variable 552 | var_name(NameVarPairs, Var, Name), 553 | ( member(Name=_VarValue, PreviousNameVarPairs) -> 554 | % The variable occurred in one of the previous queries 555 | ExpandedTerm = Name 556 | ; ExpandedTerm = $(Name) 557 | ). 558 | expand_term(Term, NameVarPairs, PreviousNameVarPairs, ExpandedTerm) :- 559 | functor(Term, Name, Arity), 560 | !, 561 | functor(ExpandedTerm, Name, Arity), 562 | expand_args(1, NameVarPairs, PreviousNameVarPairs, Term, ExpandedTerm). 563 | 564 | % expand_args(+ArgNum, +NameVarPairs, +PreviousNameVarPairs, +Term, +ExpandedTerm) 565 | expand_args(ArgNum, NameVarPairs, PreviousNameVarPairs, Term, ExpandedTerm) :- 566 | arg(ArgNum, Term, Arg), 567 | arg(ArgNum, ExpandedTerm, ExpandedArg), 568 | !, 569 | NextArgNum is ArgNum + 1, 570 | expand_term(Arg, NameVarPairs, PreviousNameVarPairs, ExpandedArg), 571 | expand_args(NextArgNum, NameVarPairs, PreviousNameVarPairs, Term, ExpandedTerm). 572 | expand_args(_ArgNum, _NameVarPairs, _PreviousNameVarPairs, _Term, _ExpandedTerm). 573 | 574 | % var_name(+NameVarPairs, +Var, -Name) 575 | % 576 | % NameVarPairs is a list of Name=Var pairs, where Name is the name of a variable Var. 577 | var_name([Name=SameVar|_NameVarPairs], Var, Name) :- 578 | Var == SameVar, 579 | !. 580 | var_name([_NameVarPair|NameVarPairs], Var, Name) :- 581 | var_name(NameVarPairs, Var, Name). 582 | 583 | :- end_object. 584 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_jsonrpc.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2023 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | :- object(jupyter_jsonrpc). 29 | 30 | :- info([ 31 | version is 0:3:0, 32 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 33 | date is 2025-03-10, 34 | comment is 'This object andles all reading, writing, and parsing of JSON messages. It is based on jsonrpc_server.pl and jsonrpc_client.pl from SICStus 4.5.1.' 35 | ]). 36 | 37 | :- public(json_error_term/5). 38 | :- mode(json_error_term(+integer, +compound, +atom, +list(pair), -json), one). 39 | :- info(json_error_term/5, [ 40 | comment is 'Returns a JSON representation of the given error.', 41 | argnames is ['ErrorCode', 'ErrorMessageData', 'Output', 'AdditionalData', 'JsonErrorTerm'] 42 | ]). 43 | 44 | :- public(next_jsonrpc_message/1). 45 | :- mode(next_jsonrpc_message(-json), one). 46 | :- info(next_jsonrpc_message/1, [ 47 | comment is 'Returns the next JSON-RPC message.', 48 | argnames is ['Message'] 49 | ]). 50 | 51 | :- public(parse_json_terms_request/3). 52 | :- mode(parse_json_terms_request(+json, -list(pair), -compound), one). 53 | :- info(parse_json_terms_request/3, [ 54 | comment is 'Parses a JSON message.', 55 | argnames is ['Params', 'TermsAndVariables', 'ParsingErrorMessageData'] 56 | ]). 57 | 58 | :- public(send_error_reply/3). 59 | :- mode(send_error_reply(+integer, +integer, +atom), one). 60 | :- info(send_error_reply/3, [ 61 | comment is 'Sends an error reply.', 62 | argnames is ['Id', 'ErrorCode', 'ErrorMessage'] 63 | ]). 64 | 65 | :- public(send_json_request/6). 66 | :- mode(send_json_request(+atom, +list, +integer, +stream_or_alias, +stream_or_alias, -json), one). 67 | :- info(send_json_request/6, [ 68 | comment is 'Sends a JSON request by writing it to the input stream and reading the response from the output stream.', 69 | argnames is ['Method', 'Params', 'Id', 'InputStream', 'OutputStream', 'Reply'] 70 | ]). 71 | 72 | :- public(send_success_reply/2). 73 | :- mode(send_success_reply(+integer, +nonvar), one). 74 | :- info(send_success_reply/2, [ 75 | comment is 'Sends a successful request result.', 76 | argnames is ['Id', 'Result'] 77 | ]). 78 | 79 | :- uses(list, [append/3, member/2]). 80 | :- uses(os, [null_device_path/1]). 81 | :- uses(term_io, [read_term_from_codes/4, write_term_to_atom/3]). 82 | :- uses(jupyter_query_handling, [retrieve_message/2]). 83 | :- uses(jupyter_logging, [log/1]). 84 | :- uses(json(list,dash,atom), [ 85 | generate(stream(Stream),JSON) as json_write(Stream,JSON), 86 | parse(line(Stream),JSON) as json_read(Stream,JSON) 87 | ]). 88 | 89 | % Create JSON-RPC objects 90 | 91 | % Create a JSON-RPC Request object (http://www.jsonrpc.org/specification#request_object) 92 | jsonrpc_request(Method, Params, Id, json([jsonrpc-'2.0',id-Id,method-Method,params-Params])). 93 | 94 | % Create a JSON-RPC success Response object (http://www.jsonrpc.org/specification#response_object) 95 | jsonrpc_response(Result, Id, json([jsonrpc-'2.0',id-Id,result-Result])). 96 | 97 | % Create a JSON-RPC error Response object (http://www.jsonrpc.org/specification#response_object) 98 | jsonrpc_error_response(Error, Id, json([jsonrpc-'2.0',id-Id,error-Error])). 99 | 100 | % Create a JSON-RPC Error object (http://www.jsonrpc.org/specification#error_object) 101 | jsonrpc_error(Code, Message, Data, json([code-Code,message-Message,data-Data])). 102 | 103 | % Create a JSON-RPC Error object (http://www.jsonrpc.org/specification#error_object) 104 | jsonrpc_error(Code, Message, json([code-Code,message-Message])). 105 | 106 | 107 | % json_error_term(+ErrorCode, +ErrorMessageData, +Output, +AdditionalData, -JsonErrorTerm) 108 | % 109 | % ErrorCode is one of the error codes defined by error_object_code/3 (e.g. exception). 110 | % ErrorMessageData is a term of the form message_data(Kind, Term) so that the actual error message can be retrieved with print_message(Kind, jupyter, Term) 111 | % Output is the output of the term which was executed. 112 | % AdditionalData is a list containing Key-Value pairs providing additional data for the client. 113 | json_error_term(ErrorCode, ErrorMessageData, Output, AdditionalData, JsonErrorTerm) :- 114 | jupyter_query_handling::retrieve_message(ErrorMessageData, LogtalkMessage), 115 | error_data(LogtalkMessage, Output, AdditionalData, ErrorData), 116 | error_object_code(ErrorCode, NumericErrorCode, JsonRpcErrorMessage), 117 | jsonrpc_error(NumericErrorCode, JsonRpcErrorMessage, ErrorData, JsonErrorTerm). 118 | 119 | 120 | % error_data(+LogtalkMessage, +Output, +AdditionalData, -ErrorData) 121 | error_data(LogtalkMessage, Output, AdditionalData, json([logtalk_message-LogtalkMessage|AdditionalData])) :- 122 | var(Output), 123 | !. 124 | error_data(LogtalkMessage, '', AdditionalData, json([logtalk_message-LogtalkMessage|AdditionalData])) :- !. 125 | error_data(LogtalkMessage, Output, AdditionalData, json([logtalk_message-LogtalkMessage, output-Output|AdditionalData])). 126 | 127 | 128 | % Send responses 129 | 130 | send_success_reply(Id, Result) :- 131 | nonvar(Id), 132 | !, 133 | jsonrpc_response(Result, Id, JSONResponse), 134 | write_message(JSONResponse). 135 | 136 | 137 | % send_error_reply(+Id, +ErrorCode, +LogtalkMessage) 138 | % 139 | % ErrorCode is one of the error codes defined by error_object_code/3 (e.g. exception). 140 | % LogtalkMessage is an error message as output by print_message/3. 141 | send_error_reply(Id, ErrorCode, LogtalkMessage) :- 142 | error_object_code(ErrorCode, NumericErrorCode, JsonRpcErrorMessage), 143 | json_error_term(NumericErrorCode, JsonRpcErrorMessage, json([logtalk_message-LogtalkMessage]), RPCError), 144 | jsonrpc_error_response(RPCError, Id, RPCResult), 145 | write_message(RPCResult). 146 | 147 | 148 | % json_error_term(+NumericErrorCode, +JsonRpcErrorMessage, +Data, -RPCError) 149 | json_error_term(NumericErrorCode, JsonRpcErrorMessage, Data, RPCError) :- 150 | nonvar(Data), 151 | !, 152 | jsonrpc_error(NumericErrorCode, JsonRpcErrorMessage, Data, RPCError). 153 | json_error_term(NumericErrorCode, JsonRpcErrorMessage, _Data, RPCError) :- 154 | jsonrpc_error(NumericErrorCode, JsonRpcErrorMessage, RPCError). 155 | 156 | 157 | % error_object_code(ErrorCode, NumericErrorCode, JsonRpcErrorMessage) 158 | % 159 | % Pre-defined errorserror_object_code(parse_error, -32700, 'Invalid JSON was received by the server.'). 160 | error_object_code(invalid_request, -32600, 'The JSON sent is not a valid Request object.'). 161 | error_object_code(method_not_found, -32601, 'The method does not exist / is not available.'). 162 | error_object_code(invalid_params, -32602, 'Invalid method parameter(s).'). 163 | error_object_code(internal_error, -32603, 'Internal JSON-RPC error.'). 164 | % Prolog specific errors 165 | error_object_code(failure, -4711, 'Failure'). 166 | error_object_code(exception, -4712, 'Exception'). 167 | error_object_code(no_active_call, -4713, 'No active call'). 168 | error_object_code(invalid_json_response, -4714, 'The Response object is no valid JSON object'). 169 | error_object_code(unhandled_exception, -4715, 'Unhandled exception'). 170 | 171 | 172 | send_json_request(Method, Params, Id, InputStream, OutputStream, Reply) :- 173 | jsonrpc_request(Method, Params, Id, Request), 174 | % Send the request 175 | json_write(InputStream, Request), 176 | nl(InputStream), 177 | flush_output(InputStream), 178 | % Read the response 179 | json_read(OutputStream, Reply). 180 | 181 | 182 | % Read and write json messages 183 | 184 | % next_jsonrpc_message(-Message) 185 | % 186 | % Reads the next message from the standard input stream and parses it. 187 | next_jsonrpc_message(Message) :- 188 | read_message(RPC), 189 | parse_message(RPC, Message). 190 | 191 | % read_message(-JsonRpcMessage) 192 | :- if(current_logtalk_flag(prolog_dialect, eclipse)). 193 | 194 | read_message(JsonRpcMessage) :- 195 | current_input(In), 196 | ( at_end_of_stream(In) -> 197 | peek_code(In, _) 198 | ; true 199 | ), 200 | json_read(In, JsonRpcMessage). 201 | 202 | flush_message(Stream) :- 203 | % write lf instead of crlf always (allows running on Windows) 204 | set_stream_property(Stream, end_of_line, lf), 205 | % Terminate the line (assuming single-line output). 206 | nl(Stream), 207 | flush_output(Stream). 208 | 209 | :- else. 210 | 211 | read_message(JsonRpcMessage) :- 212 | current_input(In), 213 | json_read(In, JsonRpcMessage). 214 | 215 | flush_message(Stream) :- 216 | % Terminate the line (assuming single-line output). 217 | nl(Stream), 218 | flush_output(Stream). 219 | 220 | :- endif. 221 | 222 | % parse_message(+RPC, -Message) 223 | parse_message(RPC, Message) :- 224 | json_member(RPC, 'method', Method), 225 | json_member(RPC, 'id', _NoId, Id), 226 | json_member(RPC, 'params', [], Params), 227 | !, 228 | Message = request(Method,Id,Params,RPC). 229 | parse_message(RPC, Message) :- 230 | % RPC is not valid JSON-RPC 2.0 231 | Message = invalid(RPC). 232 | 233 | 234 | % write_message(+JSON) 235 | write_message(JSON) :- 236 | log(JSON), 237 | % If sending the JSON message to the client directly fails (because the term JSON might not be parsable to JSON), 238 | % the client would receive an incomplete message. 239 | % Instead, try writing JSON to a file and send an error reply if this fails. 240 | % Otherwise, send the JSON message to the client. 241 | null_device_path(NullPath), 242 | open(NullPath, write, NullStream), 243 | catch(json_write(NullStream, JSON), Exception, true), 244 | close(NullStream), 245 | ( nonvar(Exception) -> 246 | write_term_to_atom(Exception, ExceptionAtom, [quoted(true)]), 247 | send_error_reply(@(null), invalid_json_response, ExceptionAtom) 248 | ; current_output(Out), 249 | json_write(Out, JSON), 250 | flush_message(Out) 251 | ). 252 | 253 | 254 | % Parse json messages 255 | 256 | % parse_json_terms_request(+Params, -TermsAndVariables, -ParsingErrorMessageData) 257 | % 258 | % Reads terms from the given 'code' string in Params. 259 | % In general, the code needs to be valid Logtalk syntax. 260 | % However, if a missing terminating full-stop causes the only syntax error (in case of SICStus Prolog), the terms can be parsed anyway. 261 | % Does not bind TermsAndVariables if the code parameter in Params is malformed or if there is an error when reading the terms. 262 | % If an error occurred while reading Prolog terms from the 'code' parameter, ParsingErrorMessageData is bound. 263 | parse_json_terms_request(Params, TermsAndVariables, ParsingErrorMessageData) :- 264 | Params = json(_), 265 | json_member(Params, code, GoalSpec), 266 | atom(GoalSpec), 267 | !, 268 | terms_from_atom(GoalSpec, TermsAndVariables, ParsingErrorMessageData). 269 | parse_json_terms_request(_Params, _TermsAndVariables, _ParsingErrorMessageData). 270 | 271 | 272 | % terms_from_atom(+TermsAtom, -TermsAndVariables, -ParsingErrorMessageData) 273 | % 274 | % The atom TermsAtom should form valid Logtalk term syntax (the last term does not need to be terminated by a full-stop). 275 | % Reads all terms from TermsAtom. 276 | % TermsAndVariables is a list with elements of the form Term-Variables. 277 | % Variables is a list of variable name and variable mappings (of the form [Name-Var, ...]) which occur in the corresponding term Term. 278 | % ParsingErrorMessageData is instantiated to a term of the form message_data(Kind, Term) if a syntax error was encountered when reading the terms. 279 | % ParsingErrorMessageData can be used to print the actual error message with print_message(Kind, jupyter, Term). 280 | % In case of a syntax error, TermsAndVariables is left unbound. 281 | % 282 | % Examples: 283 | % - terms_from_atom("hello(world).", [hello(world)-[]], _ParsingError). 284 | % - terms_from_atom("member(E, [1,2,3]).", [member(_A,[1,2,3])-['E'-_A]], _ParsingError). 285 | % - terms_from_atom("hello(world)", _TermsAndVariables, parsing_error(error(syntax_error('operator expected after expression'),syntax_error(read_term('$stream'(140555796879536),_A,[variable_names(_B)]),1,'operator expected after expression',[atom(hello)-1,'('-1,atom(world)-1,')'-1],0)),'! Syntax error in read_term/3\n! operator expected after expression\n! in line 1\n! hello ( world ) \n! <>')). 286 | 287 | terms_from_atom(TermsAtom, TermsAndVariables, ParsingErrorMessageData) :- 288 | atom_codes(TermsAtom, GoalCodes), 289 | % Try reading the terms from the codes 290 | terms_from_codes(GoalCodes, TermsAndVariables, ParsingErrorMessageData), 291 | ( nonvar(ParsingErrorMessageData) 292 | -> % No valid Logtalk syntax 293 | % The error might have been caused by a missing terminating full-stop 294 | ( append(_, [46], GoalCodes) % NOTE: the dot could be on a comment line. 295 | ; % If the last code of the GoalCodes list does not represent a full-stop, add one and try reading the term(s) again 296 | append(GoalCodes, [10, 46], GoalCodesWithFullStop), % The last line might be a comment -> add a new line code as well 297 | terms_from_codes(GoalCodesWithFullStop, TermsAndVariables, _NewParsingErrorMessageData) 298 | ) 299 | ; true 300 | ). 301 | 302 | % terms_from_codes(+Codes, -TermsAndVariables, -ParsingErrorMessageData) 303 | terms_from_codes(Codes, TermsAndVariables, ParsingErrorMessageData) :- 304 | catch( 305 | read_terms_and_vars(Codes, TermsAndVariables), 306 | Exception, 307 | ParsingErrorMessageData = message_data(error, Exception) 308 | ). 309 | 310 | % read_terms_and_vars(+Codes, -TermsAndVariables) 311 | read_terms_and_vars(Codes, NewTermsAndVariables) :- 312 | read_term_from_codes(Codes, Term, Tail, [variable_names(Variables)]), 313 | ( Term == end_of_file -> 314 | NewTermsAndVariables = [] 315 | ; NewTermsAndVariables = [Term-Variables|TermsAndVariables], 316 | read_terms_and_vars(Tail, TermsAndVariables) 317 | ). 318 | 319 | % json_member(+Object, +Name, -Value) 320 | % 321 | % If Object is a JSON object, with a member named Name, then bind Value to the corresponding value. 322 | % Otherwise, fail. 323 | json_member(Object, Name, Value) :- 324 | nonvar(Object), 325 | Object = json(Members), 326 | member(Name-V, Members), 327 | !, 328 | Value = V. 329 | 330 | % json_member(+Object, +Name, +Default, -Value) 331 | % 332 | % If Object is a JSON object, with a member named Name, then bind Value to the corresponding value. 333 | % Otherwise, e.g. if there is no such member or Object is not an object, bind Value to Default. 334 | json_member(Object, Name, _Default, Value) :- 335 | nonvar(Object), 336 | Object = json(Members), 337 | member(Name-V, Members), 338 | !, 339 | Value = V. 340 | json_member(_Object, _Name, Default, Value) :- 341 | Value = Default. 342 | 343 | :- end_object. 344 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_logging.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2023 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | :- object(jupyter_logging). 29 | 30 | :- info([ 31 | version is 0:2:0, 32 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 33 | date is 2025-03-05, 34 | comment is 'Logging support.' 35 | ]). 36 | 37 | :- public(create_log_file/1). 38 | :- mode(create_log_file(-boolean), zero_or_one). 39 | :- info(create_log_file/1, [ 40 | comment is 'Creates a log file if possible. Each running backend uses its own log file.', 41 | argnames is ['IsSuccess'] 42 | ]). 43 | 44 | :- public(log/1). 45 | :- mode(log(@term), one). 46 | :- info(log/1, [ 47 | comment is 'Logs a term.', 48 | argnames is ['Term'] 49 | ]). 50 | 51 | :- public(log/2). 52 | :- mode(log(+atom, @term), one). 53 | :- info(log/2, [ 54 | comment is 'Logs arguments after the given format.', 55 | argnames is ['Format', 'Arguments'] 56 | ]). 57 | 58 | :- uses(format, [format/3]). 59 | 60 | % create_log_file(-IsSuccess) 61 | create_log_file(true) :- 62 | % Open a log file (jupyter_logging to stdout would send the messages to the client) 63 | % On Windows platforms, opening a file with SICStus which is alread opened by another process (i.e. another Prolog server) fails 64 | % Therefore separate log files are created for each Prolog backend 65 | current_logtalk_flag(prolog_dialect, Dialect), 66 | atom_concat('.logtalk_server_log_', Dialect, LogFileName), 67 | catch(open(LogFileName, write, _Stream, [alias(log_stream)]), _Exception, fail), 68 | !. 69 | create_log_file(false). 70 | % No new log file could be opened 71 | 72 | log(Term) :- 73 | log('~w~n', [Term]). 74 | 75 | log(Format, Arguments) :- 76 | % Write to the log file 77 | stream_property(_, alias(log_stream)), 78 | !, 79 | format(log_stream, Format, Arguments), 80 | flush_output(log_stream). 81 | log(_Control, _Arguments). 82 | % No new log file could be opened 83 | 84 | :- end_object. 85 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_preferences.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2025 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | :- object(jupyter_preferences). 29 | 30 | :- info([ 31 | version is 0:12:0, 32 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 33 | date is 2025-03-05, 34 | comment is 'Preferences management.' 35 | ]). 36 | 37 | :- initialization(init_preferences). 38 | 39 | :- public(set_preference/2). 40 | :- mode(set_preference(+atom, +nonvar), one). 41 | :- info(set_preference/2, [ 42 | comment is 'Sets a preference value.', 43 | argnames is ['Preference', 'Value'] 44 | ]). 45 | 46 | :- public(set_preference/3). 47 | :- mode(set_preference(+atom, -nonvar, +nonvar), one). 48 | :- info(set_preference/3, [ 49 | comment is 'Sets a preference value.', 50 | argnames is ['Preference', 'OldValue', 'NewValue'] 51 | ]). 52 | 53 | :- public(get_preference/2). 54 | :- mode(get_preference(+atom, -nonvar), one). 55 | :- info(get_preference/2, [ 56 | comment is 'Returns a preference value.', 57 | argnames is ['Preference', 'Value'] 58 | ]). 59 | 60 | :- public(get_preferences/1). 61 | :- mode(get_preferences(-list(pair(atom,nonvar))), one). 62 | :- info(get_preferences/1, [ 63 | comment is 'Returns a list of all preferences.', 64 | argnames is ['Preferences'] 65 | ]). 66 | 67 | :- public(reset_preferences/0). 68 | :- mode(reset_preferences, one). 69 | :- info(reset_preferences/0, [ 70 | comment is 'Reset preferences.' 71 | ]). 72 | 73 | :- private(preference_value_/2). 74 | :- dynamic(preference_value_/2). 75 | :- mode(preference_value_(?atom, ?nonvar), zero_or_more). 76 | :- info(preference_value_/2, [ 77 | comment is 'Table of preference values.', 78 | argnames is ['Preference', 'Value'] 79 | ]). 80 | 81 | :- uses(logtalk, [ 82 | print_message(debug, jupyter, Message) as dbg(Message) 83 | ]). 84 | 85 | preference_definition(verbosity, 1, natural, 'Verbosity level, 0=off, 10=maximal'). 86 | 87 | set_preference(Name, Value) :- 88 | set_preference(Name, _Old, Value). 89 | 90 | set_preference(Name, OldValue, NewValue) :- 91 | preference_definition(Name, _, Type, _Desc), 92 | check_type(Type, NewValue), 93 | retract(preference_value_(Name, OldValue)), !, 94 | dbg('Changing preference ~w from ~w to ~w~n'+[Name, OldValue, NewValue]), 95 | assertz(preference_value_(Name, NewValue)). 96 | 97 | check_type(natural,Val) :- integer(Val), Val >= 0. 98 | check_type(integer,Val) :- integer(Val). 99 | check_type(boolean,true). 100 | check_type(boolean,false). 101 | 102 | get_preference(Name, Value) :- 103 | preference_value_(Name, Value). 104 | 105 | get_preferences(List) :- 106 | findall(P-V,get_preference(P,V),L), 107 | sort(L,List). 108 | 109 | init_preferences :- 110 | preference_definition(Name, Default, _Type, _Desc), 111 | \+ preference_value_(Name, _), % not already defined 112 | dbg('Initialising preference ~w to ~w~n'+[Name,Default]), 113 | assertz(preference_value_(Name,Default)), 114 | fail. 115 | init_preferences. 116 | 117 | reset_preferences :- 118 | retractall(preference_value_(_,_)), 119 | preference_definition(Name,Default,_Type,_Desc), 120 | dbg('Resetting preference ~w to ~w~n'+[Name,Default]), 121 | assertz(preference_value_(Name,Default)), 122 | fail. 123 | reset_preferences. 124 | 125 | :- end_object. 126 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_query_handling.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2023 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | % The main predicates are 29 | % - call_with_output_to_file/3: call a goal and read all its output 30 | % - call_query_with_output_to_file/7: call a goal, read all its output, and assert its runtime and query data 31 | % - retrieve_message/2: for a term of the form message_data(Kind, Term), print the message with print_message(Kind, jupyter, Term) and read it 32 | 33 | % Additionally, it provides the dynamic predicate query_data(CallRequestId, Runtime, TermData, OriginalTermData) where TermData and OriginalTermData are terms of the form term_data(TermAtom, Bindings). 34 | % It is used to remember all queries' IDs, goal and runtime so that the data can be accessed by jupyter::print_query_time/0 and jupyter::print_queries/1. 35 | % If there was a replacement of $Var terms in the original term, OriginalTermData contains the original term and its bindings. 36 | % Otherwise, OriginalTermData=same 37 | 38 | 39 | :- object(jupyter_query_handling). 40 | 41 | :- info([ 42 | version is 0:6:0, 43 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 44 | date is 2025-05-01, 45 | comment is 'This object provides predicates to redirect the output of a query execution to a file and read it from the file.' 46 | ]). 47 | 48 | :- public(call_query_with_output_to_file/7). 49 | :- meta_predicate(call_query_with_output_to_file(*, *, *, *, *, *, *)). 50 | :- mode(call_query_with_output_to_file(+callable, +integer, +list, +compound, -atom, -compound, -boolean), one). 51 | :- info(call_query_with_output_to_file/7, [ 52 | comment is 'Calls a goal returning its output and any error message data in case of an exception.', 53 | argnames is ['Goal', 'CallRequestId', 'Bindings', 'OriginalTermData', 'Output', 'ErrorMessageData', 'IsFailure'] 54 | ]). 55 | 56 | :- public(call_with_output_to_file/3). 57 | :- meta_predicate(call_with_output_to_file(*, *, *)). 58 | :- mode(call_with_output_to_file(+callable, -atom, -compound), zero_or_one). 59 | :- info(call_with_output_to_file/3, [ 60 | comment is 'Calls a goal returning its output and any error message data in case of an exception.', 61 | argnames is ['Goal', 'Output', 'ErrorMessageData'] 62 | ]). 63 | 64 | :- public(delete_output_file/1). 65 | :- mode(delete_output_file(+boolean), one). 66 | :- info(delete_output_file/1, [ 67 | comment is 'Deletes the output file when the argument is the atom ``true``.', 68 | argnames is ['Boolean'] 69 | ]). 70 | 71 | :- public(query_data/4). 72 | :- dynamic(query_data/4). 73 | :- mode(query_data(-integer, -float, -compound, -compound), zero_or_more). 74 | :- info(query_data/4, [ 75 | comment is 'Table of query data.', 76 | argnames is ['CallRequestId', 'Runtime', 'TermData', 'OriginalTermData'] 77 | ]). 78 | 79 | :- public(redirect_output_to_file/0). 80 | :- mode(redirect_output_to_file, one). 81 | :- info(redirect_output_to_file/0, [ 82 | comment is 'Redirects output to file.' 83 | ]). 84 | 85 | :- public(retrieve_message/2). 86 | :- mode(retrieve_message(+atom, -atom), one). 87 | :- mode(retrieve_message(+compound, -atom), one). 88 | :- info(retrieve_message/2, [ 89 | comment is 'Retrieves the message text from priting the message given its data. Returns the empty atom when the data is athe atom ``null``.', 90 | argnames is ['ErrorMessageData', 'Message'] 91 | ]). 92 | 93 | :- public(send_reply_on_error/0). 94 | :- dynamic(send_reply_on_error/0). 95 | 96 | % :- public(safe_call_without_sending_error_replies/1). 97 | % :- meta_predicate(safe_call_without_sending_error_replies(0)). 98 | % 99 | % :- private(remove_output_lines_for/1). 100 | % :- dynamic(remove_output_lines_for/1). 101 | % remove_output_lines_for(Type) 102 | 103 | :- uses(debugger, [notrace/0]). 104 | :- uses(list, [append/2, append/3]). 105 | :- uses(logtalk, [print_message/3]). 106 | :- uses(os, [delete_file/1, wall_time/1]). 107 | :- uses(reader, [line_to_codes/2 as read_line_to_codes/2]). 108 | :- uses(term_io, [write_term_to_codes/3]). 109 | 110 | % TermData and OriginalTermData are terms of the form term_data(TermAtom, Bindings) 111 | 112 | % If send_reply_on_error exists, an error reply is sent to the client if an unhandled error occurs and is printed with print_message/2. 113 | % This predicate is retracted when an error message is to be produced from an error term and therefore printed. 114 | % TODO: this is very ugly, we need to get rid of this. 115 | send_reply_on_error. 116 | 117 | file_name(stdout, '.server_stdout'). 118 | file_name(message_output, '.message_output'). 119 | file_name(output, '.server_output'). 120 | file_name(test, 'test_definition.pl'). 121 | 122 | % safe_call_without_sending_error_replies(Goal) :- 123 | % retractall(send_reply_on_error), 124 | % % call_cleanup(Goal, assert(send_reply_on_error)). 125 | % ( catch(Goal, Error, (assertz(send_reply_on_error), throw(Error))) -> 126 | % assertz(send_reply_on_error) 127 | % ; assertz(send_reply_on_error) 128 | % ). 129 | 130 | % Call a goal and read all output 131 | 132 | % call_with_output_to_file(+Goal, -Output, -ErrorMessageData) 133 | % 134 | % Redirects the output of the goal Goal and debugging messages to a file. 135 | % This is done by creating a file which is set as the current output and error stream. 136 | % Calls the goal Goal and reads its output Output (and debugging messages) from the file. 137 | % If an exception is thrown when calling the goal, ErrorMessageData is a term of the form message_data(Kind, Term) so that the acutal error message can be retrieved with print_message(Kind, jupyter, Term). 138 | % If Goal=jupyter::trace(TraceGoal), debug mode has to be switched off afterwards. 139 | call_with_output_to_file(Goal, Output, ErrorMessageData) :- 140 | prepare_call_with_output_to_file, 141 | % Call the goal Goal and compute the runtime 142 | ( call_with_exception_handling(Goal, ErrorMessageData) 143 | ; % Goal failed 144 | reset_output_streams(true), 145 | fail 146 | ), 147 | cleanup_and_read_output_from_file(Goal, Output). 148 | 149 | % call_query_with_output_to_file(+Goal, +CallRequestId, +Bindings, +OriginalTermData, -Output, -ErrorMessageData -IsFailure) 150 | % 151 | % Like call_with_output_to_file/3. 152 | % Additionally, the runtime of the goal Goal is elapsed and query data is asserted. 153 | call_query_with_output_to_file(Goal, CallRequestId, Bindings, OriginalTermData, Output, ErrorMessageData, IsFailure) :- 154 | % Compute the atom of the goal Goal before calling it causes variables to be bound 155 | % The atom is needed for the term data which is asserted 156 | write_term_to_codes(Goal, GoalCodes, [quoted(true), variable_names(Bindings)]), 157 | atom_codes(GoalAtom, GoalCodes), 158 | prepare_call_with_output_to_file, 159 | % Call the goal Goal and compute the runtime 160 | wall_time(WallTime0), 161 | ( call_with_exception_handling(Goal, ErrorMessageData) 162 | ; % Goal failed 163 | IsFailure = true 164 | ), 165 | wall_time(WallTime1), 166 | WallTime is WallTime1 - WallTime0, 167 | assert_query_data(CallRequestId, WallTime, term_data(GoalAtom, Bindings), OriginalTermData), 168 | cleanup_and_read_output_from_file(Goal, Output). 169 | 170 | prepare_call_with_output_to_file :- 171 | redirect_output_to_file, 172 | retractall(send_reply_on_error). 173 | 174 | % Redirects the output of a goal and debugging messages to a file 175 | redirect_output_to_file :- 176 | file_name(output, OutputFileName), 177 | open(OutputFileName, write, OutputStream, [alias(output_to_file_stream)]), 178 | % Set the streams to which the goal's output and debugging messages are written by default 179 | redirect_output_to_stream(OutputStream). 180 | 181 | % call_with_exception_handling(+Goal, -ErrorMessageData) 182 | :- meta_predicate(call_with_exception_handling(*, *)). 183 | call_with_exception_handling(Goal, ErrorMessageData) :- 184 | catch( 185 | {Goal}, 186 | Exception, 187 | % In case of an exception, switch debug mode off so that no more debugging messages are printed 188 | (notrace, ErrorMessageData = message_data(error, Exception)) 189 | ). 190 | 191 | % assert_query_data(+CallRequestId, +Runtime, +TermData, +OriginalTermData) 192 | assert_query_data(0, _Runtime, _TermData, _OriginalTermData) :- 193 | !. 194 | % Do not assert query data for requests with ID 0 195 | % With requests with this ID, the kernel can request additional data (e.g. for inspection in the case of SWI-Prolog) 196 | assert_query_data(CallRequestId, Runtime, TermData, OriginalTermData) :- 197 | nonvar(OriginalTermData), 198 | !, 199 | % Remember all queries' IDs, goal and runtime so that it can be accessed by jupyter:print_query_time/0 and jupyter:print_queries/1 200 | ( TermData = OriginalTermData -> 201 | StoreOriginalTermData = same 202 | ; % there was a replacement of $Var terms in the original term -> store both terms data 203 | StoreOriginalTermData = OriginalTermData 204 | ), 205 | % Assert the data with assertz/1 so that they can be accessed in the correct order with jupyter:print_queries/1 206 | % use a catch/3 to succeed in case of assert error due to cyclic terms in TermData 207 | catch(assertz(query_data(CallRequestId, Runtime, TermData, StoreOriginalTermData)), _, true). 208 | assert_query_data(_CallRequestId, _Runtime, _TermData, _OriginalTermData). 209 | 210 | % cleanup_and_read_output_from_file(+Goal, -Output) 211 | % 212 | % Output is the output and debugging messages of the goal Goal which was written to the output file. 213 | cleanup_and_read_output_from_file(Goal, Output) :- 214 | reset_output_streams(false), 215 | assertz(send_reply_on_error), 216 | file_name(output, OutputFileName), 217 | read_output_from_file(OutputFileName, Goal, Output), 218 | delete_output_file(true). 219 | 220 | % reset_output_streams(+Boolean) 221 | reset_output_streams(Boolean) :- 222 | terminate_redirect_output_to_stream(output_to_file_stream), 223 | delete_output_file(Boolean). 224 | 225 | % delete_output_file(+Boolean) 226 | delete_output_file(true) :- 227 | file_name(output, OutputFileName), 228 | catch(delete_file(OutputFileName), _Exception, true). 229 | delete_output_file(false). 230 | 231 | % Print and read (error) messages 232 | 233 | % retrieve_message(+ErrorMessageData, -Message) 234 | % 235 | % ErrorMessageData either null or a term of the form message_data(Kind, Term). 236 | % In the first case, Message=''. 237 | % Otherwise, Message is the message as printed by print_message(Kind, jupyter, Term). 238 | % For this, the error stream is redirected to a file, the message is printed and read from the file. 239 | retrieve_message(null, '') :- !. 240 | retrieve_message(message_data(Kind, Term), Message) :- 241 | % Open a file to print the message to it 242 | file_name(message_output, FileName), 243 | open(FileName, write, Stream), 244 | redirect_output_to_stream(Stream), 245 | % Do not send an error reply when printing the error message 246 | % Use catch/3, because send_reply_on_error might have been retracted by call_with_output_to_file/3 247 | catch(retractall(send_reply_on_error), _Exception, true), 248 | print_message(Kind, jupyter, Term), 249 | assertz(send_reply_on_error), 250 | terminate_redirect_output_to_stream(Stream), 251 | % Read the error message from the file 252 | read_atom_from_file(FileName, Message), 253 | delete_file(FileName), 254 | !. 255 | 256 | % redirect_output_to_stream(+Stream) 257 | :- if(current_logtalk_flag(prolog_dialect, eclipse)). 258 | 259 | redirect_output_to_stream(Stream) :- 260 | set_stream(output, Stream), 261 | set_stream(log_output, Stream), 262 | set_stream(warning_output, Stream), 263 | set_stream(user_output, Stream), 264 | set_stream(error, Stream), 265 | set_stream(user_error, Stream). 266 | 267 | terminate_redirect_output_to_stream(_) :- 268 | close(user_output), 269 | close(output), 270 | close(log_output), 271 | close(warning_output), 272 | close(user_error), 273 | close(error). 274 | 275 | :- elif(current_logtalk_flag(prolog_dialect, gnu)). 276 | 277 | redirect_output_to_stream(Stream) :- 278 | set_stream_alias(Stream, current_output), 279 | set_stream_alias(Stream, user_output), 280 | set_stream_alias(Stream, user_error). 281 | 282 | terminate_redirect_output_to_stream(Stream) :- 283 | close(Stream). 284 | 285 | :- elif(predicate_property(set_stream(_,_), built_in)). 286 | 287 | redirect_output_to_stream(Stream) :- 288 | set_stream(Stream, alias(current_output)), 289 | set_stream(Stream, alias(user_output)), 290 | set_stream(Stream, alias(user_error)). 291 | 292 | terminate_redirect_output_to_stream(Stream) :- 293 | close(Stream). 294 | 295 | :- elif(current_logtalk_flag(prolog_dialect, sicstus)). 296 | 297 | redirect_output_to_stream(Stream) :- 298 | set_output(Stream), 299 | set_prolog_flag(user_output, Stream), 300 | set_prolog_flag(user_error, Stream). 301 | 302 | terminate_redirect_output_to_stream(Stream) :- 303 | close(Stream). 304 | 305 | :- endif. 306 | 307 | % Read from a file 308 | 309 | % read_output_from_file(+OutputFileName, +Goal, -Output) 310 | read_output_from_file(OutputFileName, _, Output) :- 311 | read_atom_from_file(OutputFileName, Output). 312 | 313 | % read_atom_from_file(+FileName, -FileContent) 314 | % 315 | % FileContent is an atom containing the content of the file with name FileName. 316 | read_atom_from_file(FileName, FileContent) :- 317 | open(FileName, read, Stream), 318 | read_lines(Stream, AllLinesCodes), 319 | close(Stream), 320 | AllLinesCodes \= [], 321 | !, 322 | remove_output_lines(AllLinesCodes, LineCodes), 323 | % Create an atom from the line lists 324 | ( LineCodes == [] -> 325 | FileContent = '' 326 | ; append(LineCodes, [_|ContentCodes]), % Cut off the first new line code 327 | atom_codes(FileContent, ContentCodes) 328 | ). 329 | read_atom_from_file(_FileName, ''). 330 | 331 | % read_lines(+Stream, -Lines) 332 | read_lines(Stream, NewLines) :- 333 | read_line_to_codes(Stream, Line), 334 | ( Line == end_of_file -> 335 | NewLines = [] 336 | ; % Add a new line code to the beginning of each line 337 | NewLines = [[10|Line]|Lines], 338 | read_lines(Stream, Lines) 339 | ). 340 | 341 | % % remove_output_lines(++Lines, -NewLines) 342 | % % 343 | % % Lines is a list of codes corresponding to lines read from a file to which output of a goal was written. 344 | % % In some cases such as for a jupyter::trace/1 or juypter::print_sld_tree/1 call, not all lines should be included in the output sent to the client. 345 | % remove_output_lines(Lines, NewLines) :- 346 | % remove_output_lines_for(sld_tree_breakpoint_messages), 347 | % !, 348 | % retractall(remove_output_lines_for(sld_tree_breakpoint_messages)), 349 | % % The output was produced by a call of jupyter::print_sld_tree 350 | % % The first two lines are of the following form: 351 | % % "% The debugger will first leap -- showing spypoints (debug)" 352 | % % "% Generic spypoint added, BID=1" 353 | % % The last line is like the following: 354 | % % "% Generic spypoint, BID=1, removed (last)" 355 | % % The lines corresponding to those messages are removed 356 | % append(LinesWithoutLast, [_LastLine], Lines), 357 | % LinesWithoutLast = [_First, _Second|NewLines]. 358 | remove_output_lines(Lines, Lines). 359 | 360 | :- end_object. 361 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_request_handling.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2025 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | % This is done by starting a loop which: 29 | % - Reads a message from the standard input stream with jupyter_jsonrpc::next_jsonrpc_message/1. 30 | % - Checks if the message is a valid request with dispatch_message/3. 31 | % - Checks the method of the request with dispatch_request/4, handles it accordingly and sends a response to the client. 32 | % There are five methods: 33 | % - call: execute any terms (handled by the object jupyter_term_handling) 34 | % - version: retrieve the Prolog backend version 35 | % - jupyter_predicate_docs: retrieve the docs of the predicates in the object jupyter 36 | % - enable_logging: create a log file to which log messages can be written 37 | 38 | % In case of a call request, the request might contain multiple terms. 39 | % They are handled one by one and the remaining ones are asserted with request_data/2. 40 | % They need to be asserted so that "retry." terms can fail into the previous call. 41 | % If the term produces any result, it is asserted with jupyter_term_handling::term_response/1. 42 | % Once all terms of a request are handled, their results are sent to the client. 43 | 44 | 45 | :- object(jupyter_request_handling). 46 | 47 | :- info([ 48 | version is 0:11:0, 49 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 50 | date is 2025-03-10, 51 | comment is 'This object provides predicates to start a loop reading and handling JSON RPC requests.' 52 | ]). 53 | 54 | :- public(loop/3). 55 | :- mode(loop(+callable, +atom, +atom), zero_or_one). 56 | :- info(loop/3, [ 57 | comment is 'Reads and processes requests from the client. Fails if it receives a request to retry an active goal - this causes the call to compute the next solution.', 58 | argnames is ['ContIn', 'Stack', 'ContOut'] 59 | ]). 60 | 61 | :- uses(term_io, [format_to_atom/3, write_term_to_atom/3]). 62 | :- uses(user, [atomic_list_concat/2]). 63 | 64 | :- uses(jupyter_logging, [create_log_file/1, log/1]). 65 | :- uses(jupyter_jsonrpc, [send_success_reply/2, send_error_reply/3, next_jsonrpc_message/1, parse_json_terms_request/3]). 66 | :- uses(jupyter_term_handling, [handle_term/5, term_response/1]). 67 | :- uses(jupyter_query_handling, [send_reply_on_error/0, retrieve_message/2]). 68 | :- uses(jupyter, [predicate_docs/1]). 69 | 70 | % Assert the terms which were read from the current request so that "retry." terms can fail into the previous call 71 | :- private(request_data/2). 72 | :- dynamic(request_data/2). % request_data(CallRequestId, TermsAndVariables) 73 | % TermsAndVariables is a list with elements of the form Term-Bindings. 74 | % Each of the terms Term can be a directive, clause definition, or query. 75 | % Bindings is a list of variable name and variable mappings (of the form Name=Var) which occur in the corresponding term Term. 76 | 77 | :- multifile(logtalk::message_hook/4). 78 | :- dynamic(logtalk::message_hook/4). 79 | 80 | logtalk::message_hook(MessageTerm, error, _, _Lines) :- 81 | handle_unexpected_exception(MessageTerm). 82 | 83 | 84 | % handle_unexpected_exception(+MessageTerm) 85 | % 86 | % Handle an unexpected exception. 87 | % Send an error reply to let the client know that the server is in a state from which it cannot recover and therefore needs to be killed and restarted. 88 | handle_unexpected_exception(MessageTerm) :- 89 | send_reply_on_error, 90 | log(MessageTerm), 91 | % Retract all data of the current request 92 | retract(request_data(_CallRequestId, _TermsAndVariables)), 93 | % Send an error response 94 | retrieve_message(message_data(error, MessageTerm), ExceptionMessage), 95 | send_error_reply(@(null), unhandled_exception, ExceptionMessage), 96 | fail. 97 | 98 | 99 | % loop(+ContIn, +Stack, -ContOut) 100 | % 101 | % Read and process requests from the client. 102 | % Called to start processing requests and after calling a goal to provide the ability to compute another solution for a goal on the stack Stack. 103 | % Succeeds with ContOut = cut if it receives a request to cut an active goal. 104 | % Succeeds with ContOut = done if it receives a request to quit. 105 | % Fails if it receives a request to retry an active goal - this causes the call to compute the next solution. 106 | loop(Cont, _Stack, _ContOut) :- 107 | var(Cont), 108 | !, 109 | fail. 110 | loop(done, _Stack, done) :- 111 | !, 112 | send_responses. 113 | loop(cut, _Stack, cut) :- !. 114 | loop(continue, Stack, ContOut) :- 115 | handle_next_term_or_request(Stack, Cont), 116 | loop(Cont, Stack, ContOut). 117 | 118 | 119 | % handle_next_term_or_request(+Stack, -Cont) 120 | % 121 | % Handles the next term or request. 122 | % One call request can contain more than one term. 123 | % Terms of the current request which have not been processed yet are asserted as request_data(CallRequestId, TermsAndVariables). 124 | handle_next_term_or_request(Stack, Cont) :- 125 | request_data(CallRequestId, TermsAndVariables), 126 | TermsAndVariables = [Term-Variables|RemainingTermsAndVariables], 127 | !, 128 | % Continue processing terms of the current request 129 | retract(request_data(CallRequestId, TermsAndVariables)), 130 | assertz(request_data(CallRequestId, RemainingTermsAndVariables)), 131 | handle_term(Term, CallRequestId, Stack, Variables, Cont). 132 | handle_next_term_or_request(Stack, Cont) :- 133 | % All terms of the current request have been processed -> send their results to the client 134 | request_data(_CallRequestId, []), 135 | !, 136 | send_responses, 137 | % Read the next request 138 | next_jsonrpc_message(Message), 139 | dispatch_message(Message, Stack, Cont). 140 | handle_next_term_or_request(Stack, Cont) :- 141 | % First request 142 | % Read and handle the next request from the client 143 | next_jsonrpc_message(Message), 144 | dispatch_message(Message, Stack, Cont). 145 | 146 | 147 | % Get all term responses which were asserted as term_response(JsonResponse). 148 | % Send a json response object where 149 | % - the keys are the indices of the Prolog terms from the request starting from 1 150 | % - the values are json objects representing the result of the corresponding Prolog term 151 | send_responses :- 152 | % Retract all data of the current request 153 | retract(request_data(CallRequestId, _)), 154 | % Collect the responses and send them to the client 155 | term_responses(1, TermResponses), 156 | send_success_reply(CallRequestId, json(TermResponses)). 157 | 158 | 159 | % term_responses(+CurrentNum, -TermResponses) 160 | term_responses(Num, [NumAtom-Response|TermResponses]) :- 161 | retract(term_response(Response)), 162 | !, 163 | number_codes(Num, NumCodes), 164 | atom_codes(NumAtom, NumCodes), 165 | NextNum is Num + 1, 166 | term_responses(NextNum, TermResponses). 167 | term_responses(_Num, []). 168 | 169 | % Request handling 170 | 171 | % dispatch_message(+Message, +Stack, -Cont) 172 | % 173 | % Checks if the message is a valid request message. 174 | % If so, handles the request. 175 | % Otherwise, an error response is sent. 176 | dispatch_message(Message, Stack, Cont) :- 177 | Message = request(Method,_Id,_Params,_RPC), !, 178 | dispatch_request(Method, Message, Stack, Cont). 179 | dispatch_message(invalid(_RPC), _Stack, continue) :- 180 | % Malformed -> the Id must be null 181 | send_error_reply(@(null), invalid_request, ''). 182 | 183 | % dispatch_request(+Method, +Message, +Stack, -Cont) 184 | % 185 | % Checks the request method and handles the request accordingly. 186 | dispatch_request(call, Message, Stack, Cont) :- 187 | Message = request(_Method,CallRequestId,Params,_RPC), 188 | Params = json([code-Code]), 189 | file_cell_magic(Code, File, Mode, Terms, Action), 190 | !, 191 | assertz(request_data(CallRequestId, [])), 192 | ( Action == none -> 193 | Cont = continue 194 | ; open(File, Mode, Stream), 195 | write(Stream, Terms), 196 | close(Stream), 197 | ( Action == load -> 198 | handle_term(logtalk_load(File, [reload(always)]), CallRequestId, Stack, [], Cont) 199 | ; Cont = continue 200 | ) 201 | ). 202 | dispatch_request(call, Message, Stack, Cont) :- 203 | Message = request(Method,CallRequestId,Params,RPC), 204 | Params = json([code-Code]), 205 | goal_cell_magic(Code, Rest), 206 | !, 207 | RestMessage = request(Method,CallRequestId,json([code-Rest]),RPC), 208 | dispatch_request(call, RestMessage, Stack, Cont). 209 | dispatch_request(call, Message, Stack, Cont) :- 210 | Message = request(Method,CallRequestId,Params,RPC), 211 | Params = json([code-Code]), 212 | line_magic(Code, Rest), 213 | !, 214 | RestMessage = request(Method,CallRequestId,json([code-Rest]),RPC), 215 | dispatch_request(call, RestMessage, Stack, Cont). 216 | dispatch_request(call, Message, Stack, Cont) :- 217 | !, 218 | Message = request(_Method,CallRequestId,Params,_RPC), 219 | parse_json_terms_request(Params, TermsAndVariables, ParsingErrorMessageData), 220 | ( var(TermsAndVariables) -> 221 | !, 222 | % An error occurred when parsing the json request 223 | handle_parsing_error(ParsingErrorMessageData, CallRequestId), 224 | Cont = continue 225 | ; TermsAndVariables == [] -> 226 | !, 227 | % The request does not contain any term 228 | send_success_reply(CallRequestId, ''), 229 | Cont = continue 230 | ; TermsAndVariables = [Term-Variables] -> 231 | !, 232 | % The request contains one term 233 | % Normally this is a goal which is to be evaluated 234 | assertz(request_data(CallRequestId, [])), 235 | handle_term(Term, CallRequestId, Stack, Variables, Cont) 236 | ; % The request contains multiple terms 237 | % Process the first term and assert the remaining ones 238 | % This is needed so that "retry." terms can fail into the previous call 239 | TermsAndVariables = [Term-Variables|RemainingTermsAndVariables], 240 | assertz(request_data(CallRequestId, RemainingTermsAndVariables)), 241 | handle_term(Term, CallRequestId, Stack, Variables, Cont) 242 | ). 243 | dispatch_request(backend, Message, _Stack, continue) :- 244 | !, 245 | Message = request(_Method,CallRequestId,_Params,_RPC), 246 | current_logtalk_flag(prolog_dialect, Dialect), 247 | send_success_reply(CallRequestId, Dialect). 248 | dispatch_request(enable_logging, Message, _Stack, continue) :- 249 | !, 250 | % Create a log file 251 | Message = request(_Method,CallRequestId,_Params,_RPC), 252 | create_log_file(IsSuccess), 253 | send_success_reply(CallRequestId, IsSuccess). 254 | dispatch_request(version, Message, _Stack, continue) :- 255 | !, 256 | % Send the backend version to the client 257 | Message = request(_Method,CallRequestId,_Params,_RPC), 258 | current_logtalk_flag(prolog_version, v(Major,Minor,Patch)), 259 | format_to_atom('~d.~d.~d', [Major, Minor, Patch], VersionAtom), 260 | send_success_reply(CallRequestId, VersionAtom). 261 | dispatch_request(jupyter_predicate_docs, Message, _Stack, continue) :- 262 | % Retrieve the docs of the predicates in the object jupyter and send them to the client 263 | Message = request(_Method,CallRequestId,_Params,_RPC), 264 | !, 265 | predicate_docs(PredDocs), 266 | send_success_reply(CallRequestId, json(PredDocs)). 267 | dispatch_request(Method, Message, _Stack, continue) :- 268 | % Make sure that a 'retry' call can fail 269 | Method \= call, 270 | Message = request(_,Id,_Params,_RPC), !, 271 | write_term_to_atom(Method, MethodAtom, [quoted(true)]), 272 | send_error_reply(Id, method_not_found, MethodAtom). 273 | 274 | 275 | % handle_parsing_error(+ParsingErrorMessageData, +CallRequestId) 276 | handle_parsing_error(ParsingErrorMessageData, CallRequestId) :- 277 | nonvar(ParsingErrorMessageData), 278 | !, 279 | % Parsing error when reading the terms from the request 280 | retrieve_message(ParsingErrorMessageData, ErrorMessage), 281 | send_error_reply(CallRequestId, exception, ErrorMessage). 282 | handle_parsing_error(_ParsingErrorMessageData, CallRequestId) :- 283 | % Malformed request 284 | send_error_reply(CallRequestId, invalid_params, ''). 285 | 286 | % cell magic 287 | 288 | file_cell_magic(Code, 'user.lgt', append, Terms, load) :- 289 | sub_atom(Code, 0, _, _, '%%user+\n'), 290 | sub_atom(Code, 8, _, 0, Terms0), 291 | atom_concat('\n\n', Terms0, Terms), 292 | !. 293 | file_cell_magic(Code, 'user.lgt', write, Terms, load) :- 294 | sub_atom(Code, 0, _, _, '%%user\n'), 295 | sub_atom(Code, 7, _, 0, Terms), 296 | !. 297 | file_cell_magic(Code, File, append, Terms, load) :- 298 | sub_atom(Code, 0, _, _, '%%file+ '), 299 | sub_atom(Code, Before, _, _, '\n'), 300 | Length is Before - 8, 301 | sub_atom(Code, 8, Length, _, File), 302 | Rest is 8 + Length + 1, 303 | sub_atom(Code, Rest, _, 0, Terms0), 304 | atom_concat('\n\n', Terms0, Terms), 305 | !. 306 | file_cell_magic(Code, File, write, Terms, load) :- 307 | sub_atom(Code, 0, _, _, '%%file '), 308 | sub_atom(Code, Before, _, _, '\n'), 309 | Length is Before - 7, 310 | sub_atom(Code, 7, Length, _, File), 311 | Rest is 7 + Length + 1, 312 | sub_atom(Code, Rest, _, 0, Terms), 313 | !. 314 | file_cell_magic(Code, File, write, Terms, save) :- 315 | sub_atom(Code, 0, _, _, '%%save '), 316 | sub_atom(Code, Before, _, _, '\n'), 317 | Length is Before - 7, 318 | sub_atom(Code, 7, Length, _, File), 319 | Rest is 7 + Length + 1, 320 | sub_atom(Code, Rest, _, 0, Terms), 321 | !. 322 | file_cell_magic(Code, File, write, Terms, load) :- 323 | sub_atom(Code, 0, _, _, '%%load '), 324 | sub_atom(Code, Before, _, _, '\n'), 325 | Length is Before - 7, 326 | sub_atom(Code, 7, Length, _, File), 327 | Rest is 7 + Length + 1, 328 | sub_atom(Code, Rest, _, 0, Terms), 329 | !. 330 | file_cell_magic(Code, none, none, [], none) :- 331 | sub_atom(Code, 0, _, _, '%%highlight\n'), 332 | !. 333 | 334 | goal_cell_magic(Code, Rest) :- 335 | atom_concat('%%table\n', Goal0, Code), 336 | ( sub_atom(Goal0, _, 1, 0, '.') -> 337 | sub_atom(Goal0, 0, _, 1, Goal) 338 | ; Goal = Goal0 339 | ), 340 | !, 341 | atomic_list_concat(['print_table((', Goal, ')).'], Rest). 342 | goal_cell_magic(Code, Rest) :- 343 | sub_atom(Code, 0, _, _, '%%csv '), 344 | sub_atom(Code, Before, _, _, '\n'), 345 | Length is Before - 6, 346 | sub_atom(Code, 6, Length, _, File), 347 | Start is 6 + Length + 1, 348 | sub_atom(Code, Start, _, 0, Goal0), 349 | ( sub_atom(Goal0, _, 1, 0, '.') -> 350 | sub_atom(Goal0, 0, _, 1, Goal) 351 | ; Goal = Goal0 352 | ), 353 | !, 354 | atomic_list_concat(['print_and_save_table((', Goal, '),csv,\'', File, '\').'], Rest). 355 | goal_cell_magic(Code, Rest) :- 356 | sub_atom(Code, 0, _, _, '%%tsv '), 357 | sub_atom(Code, Before, _, _, '\n'), 358 | Length is Before - 6, 359 | sub_atom(Code, 6, Length, _, File), 360 | Start is 6 + Length + 1, 361 | sub_atom(Code, Start, _, 0, Goal0), 362 | ( sub_atom(Goal0, _, 1, 0, '.') -> 363 | sub_atom(Goal0, 0, _, 1, Goal) 364 | ; Goal = Goal0 365 | ), 366 | !, 367 | atomic_list_concat(['print_and_save_table((', Goal, '),tsv,\'', File, '\').'], Rest). 368 | goal_cell_magic(Code, Rest) :- 369 | atom_concat('%%tree\n', Term0, Code), 370 | ( sub_atom(Term0, _, 1, 0, '.') -> 371 | sub_atom(Term0, 0, _, 1, Term) 372 | ; Term = Term0 373 | ), 374 | !, 375 | atomic_list_concat(['show_term(', Term, ').'], Rest). 376 | goal_cell_magic(Code, Rest) :- 377 | atom_concat('%%data\n', Goal0, Code), 378 | ( sub_atom(Goal0, _, 1, 0, '.') -> 379 | sub_atom(Goal0, 0, _, 1, Goal) 380 | ; Goal = Goal0 381 | ), 382 | !, 383 | atomic_list_concat(['show_data((', Goal, ')).'], Rest). 384 | 385 | line_magic('%bindings', 'jupyter::print_variable_bindings.'). 386 | line_magic('%queries', 'jupyter::print_queries.'). 387 | line_magic('%help', 'jupyter::help.'). 388 | line_magic('%pwd', 'jupyter::pwd.'). 389 | line_magic('%magic', 'jupyter::magic.'). 390 | line_magic('%versions', 'jupyter::versions.'). 391 | line_magic('%flags', 'jupyter::print_table(current_logtalk_flag(Name,Value)).'). 392 | 393 | :- end_object. 394 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_server.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2023 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | % This is the main object of the backend server. 29 | % The predicate start/0 can be called to start the server which enters a loop handling requests from a client. 30 | % The requests and corresponding replies are JSON-RPC 2.0 (https://www.jsonrpc.org/specification) messages sent over the standard streams. 31 | % The handling of those is based on code from 'jsonrpc_server.pl' from SICStus 4.5.1 32 | 33 | 34 | :- object(jupyter_server). 35 | 36 | :- info([ 37 | version is 0:5:0, 38 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 39 | date is 2025-03-10, 40 | comment is 'Main object of the server.' 41 | ]). 42 | 43 | :- public(start/0). 44 | :- mode(start, one). 45 | :- info(start/0, [ 46 | comment is 'Starts the server at the default verbosity level (1).' 47 | ]). 48 | 49 | :- public(start/1). 50 | :- mode(start(+integer), one). 51 | :- info(start/1, [ 52 | comment is 'Starts the server at the given verbosity level (0..10).', 53 | argnames is ['VerbosityLevel'] 54 | ]). 55 | 56 | :- uses(jupyter_request_handling, [loop/3]). 57 | % :- uses(jupyter_term_handling, [assert_sld_data/4]). 58 | :- uses(jupyter_preferences, [set_preference/2]). 59 | 60 | start :- 61 | start(1). 62 | 63 | start(JupyterKernelVerbosityLevel) :- 64 | set_preference(verbosity, JupyterKernelVerbosityLevel), % useful for testing purposes 65 | % Start the loop handling requests from the client 66 | loop(continue, [], _ContOut). 67 | 68 | % :- multifile(logtalk::trace_event/2). 69 | % :- dynamic(logtalk::trace_event/2). 70 | % 71 | % % the Logtalk runtime calls all defined logtalk::trace_event/2 hooks using 72 | % % a failure-driven loop; thus we don't have to worry about handling all 73 | % % events or failing after handling an event to give other hooks a chance 74 | % logtalk::trace_event(top_goal(Goal, _), _) :- 75 | % assert_sld_data(call, Goal, _Frame, _ParentFrame). 76 | % logtalk::trace_event(goal(Goal, _), _) :- 77 | % assert_sld_data(call, Goal, _Frame, _ParentFrame). 78 | 79 | :- multifile(logtalk::message_prefix_stream/4). 80 | :- dynamic(logtalk::message_prefix_stream/4). 81 | 82 | logtalk::message_prefix_stream(Kind, jupyter, Prefix, Stream) :- 83 | message_prefix_stream(Kind, Prefix, Stream). 84 | 85 | message_prefix_stream(information, '% ', user_output). 86 | message_prefix_stream(comment, '% ', user_output). 87 | message_prefix_stream(warning, '* ', user_output). 88 | message_prefix_stream(error, '! ', user_output). 89 | 90 | :- multifile(logtalk::message_tokens//2). 91 | :- dynamic(logtalk::message_tokens//2). 92 | 93 | logtalk::message_tokens(jupyter(JupyterMessageTerm), jupyter) --> 94 | message_tokens(JupyterMessageTerm). 95 | logtalk::message_tokens(MessageTerm, jupyter) --> 96 | message_tokens(MessageTerm). 97 | 98 | message_tokens(goal_failed(Goal)) --> 99 | ['~w - goal failed'-[Goal]], [nl]. 100 | 101 | message_tokens(set_preference(Preference,Value)) --> 102 | ['Invalid preference or preference value: ~q - ~q'-[Preference, Value]], [nl]. 103 | message_tokens(invalid_table_values_lists_length) --> 104 | ['The values lists need to be of the same length'-[]], [nl]. 105 | message_tokens(invalid_table_variable_names) --> 106 | ['The list of names needs to be empty or of the same length as the values lists and contain ground terms only'-[]], [nl]. 107 | message_tokens(leash_pred) --> 108 | ['The leash mode cannot be changed in a Jupyter application as no user interaction can be provided at a breakpoint'-[]], [nl]. 109 | message_tokens(print_transition_graph_indices(Arity)) --> 110 | ['All indices need to be less or equal to the provided predicate arity ~w'-[Arity]], [nl]. 111 | message_tokens(print_transition_graph_pred_spec(PredSpec)) --> 112 | ['Incorrect predicate specification: ~w'-[PredSpec]], [nl], 113 | ['It needs to be of the form PredName/PredArity or Object::PredName/PredArity'-[]], [nl]. 114 | message_tokens(prolog_backend_no_atom) --> 115 | ['The Prolog backend ID must be an atom'-[]], [nl]. 116 | message_tokens(trace_pred(TracePredSpec)) --> 117 | ['~w cannot be used in a Jupyter application'-[TracePredSpec]], [nl], 118 | ['However, there is juypter:trace(Goal)'-[]], [nl]. 119 | message_tokens(no_answer_given) --> 120 | % Used for the code stub for manually graded tasks of nbgrader assignments 121 | ['No answer given'-[]], [nl]. 122 | 123 | message_tokens(error(Error, Context)) --> 124 | ['Error: ~q'-[Error], nl], 125 | ['Context: ~q'-[Context], nl]. 126 | 127 | :- end_object. 128 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/jupyter_variable_bindings.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2023 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | % It is based on the module toplevel_variables from SWI-Prolog (version 8.4.2). 29 | 30 | % Define $ to be an operator. 31 | % This is needed so that terms containing terms of the form $Var can be read without any exceptions. 32 | :- op(1, fx, '$'). 33 | 34 | 35 | :- object(jupyter_variable_bindings). 36 | 37 | :- info([ 38 | version is 0:5:0, 39 | author is 'Anne Brecklinghaus, Michael Leuschel, and Paulo Moura', 40 | date is 2025-05-01, 41 | comment is 'This object provides predicates to reuse previous values of variables in a query.' 42 | ]). 43 | 44 | :- public(store_var_bindings/1). 45 | :- mode(store_var_bindings(+list), one). 46 | :- info(store_var_bindings/1, [ 47 | comment is 'Stores variable bindings.', 48 | argnames is ['Bindings'] 49 | ]). 50 | 51 | :- public(term_with_stored_var_bindings/4). 52 | :- mode(term_with_stored_var_bindings(+term, +list, -term, -list), one). 53 | :- info(term_with_stored_var_bindings/4, [ 54 | comment is 'Expands ``Term`` by replacing all ``$Var`` terms with the latest value of the ``Var`` variables from a previous execution.', 55 | argnames is ['Term', 'Bindings', 'ExpandedTerm', 'UpdatedBindings'] 56 | ]). 57 | 58 | :- public(var_bindings/1). 59 | :- dynamic(var_bindings/1). 60 | :- mode(var_bindings(-list), one). 61 | :- info(var_bindings/1, [ 62 | comment is 'Variable bindings (``Name=Var`` pairs where ``Name`` is the name of the variable ``Var`` of the latest query in which a variable of this name was assigned a value).', 63 | argnames is ['Bindings'] 64 | ]). 65 | 66 | var_bindings([]). 67 | 68 | :- uses(list, [append/3, delete/3, member/2]). 69 | 70 | :- multifile(logtalk::message_tokens//2). 71 | :- dynamic(logtalk::message_tokens//2). 72 | 73 | logtalk::message_tokens(jupyter(invalid_var_reference(VarName)), jupyter) --> 74 | ['$~w is not a valid variable reference'-[VarName]], [nl]. 75 | logtalk::message_tokens(jupyter(no_var_binding(VarName)), jupyter) --> 76 | ['$~w was not bound by a previous query~n'-[VarName]], [nl]. 77 | 78 | % Store variable var_bindings 79 | 80 | % store_var_bindings(+Bindings) 81 | % 82 | % Bindings is a list of Name=Var pairs, where Name is the name of a variable Var occurring in the term Term. 83 | % Updates the previously stored variable bindings with the new values if the variables are instantiated. 84 | store_var_bindings(Bindings) :- 85 | retract(var_bindings(StoredBindings)), 86 | !, 87 | updated_variables(StoredBindings, Bindings, UpdatedBindings), 88 | assertz(var_bindings(UpdatedBindings)). 89 | store_var_bindings(Bindings) :- 90 | % There are no previously stored variables 91 | % Call updated_variables/3 anyway to make sure that only instantiated values are stored 92 | updated_variables([], Bindings, BoundBindings), 93 | assertz(var_bindings(BoundBindings)). 94 | 95 | % Reuse stored variable bindings 96 | 97 | % term_with_stored_var_bindings(+Term, +Bindings, -ExpandedTerm, -UpdatedBindings) 98 | % 99 | % ExpandedTerm results from expanding the term Term by replacing all terms of the form $Var 100 | % with the latest value of the variable Var from a previous execution. 101 | % Bindings is a list of Name=Var pairs, where Name is the name of a variable Var occurring in the term Term. 102 | % If any term $Var was replaced, UpdatedBindings contains the corresponding value. 103 | % If there is no previous value for one of the variables, an exception is thrown. 104 | term_with_stored_var_bindings(Term, Bindings, ExpandedTerm, UpdatedBindings) :- 105 | expand_term(Term, Bindings, ExpandedTerm, StoredBindings), 106 | updated_variables(Bindings, StoredBindings, UpdatedBindings). 107 | 108 | % expand_term(+Term, +Bindings, -ExpandedTerm, -StoredBindings) 109 | expand_term(Var, _Bindings, Var, []) :- 110 | var(Var), 111 | !. 112 | expand_term(Atomic, _Bindings, Atomic, []) :- 113 | atomic(Atomic), 114 | !. 115 | expand_term($(Var), _Bindings, _Value, _) :- 116 | nonvar(Var), 117 | throw(jupyter(invalid_var_reference(Var))). 118 | expand_term($(Var), Bindings, Value, [Name=Value]) :- 119 | !, 120 | % Get the name of the variable to get the previous value 121 | var_name(Bindings, Var, Name), 122 | stored_variable_binding(Name, Value). 123 | expand_term(Term, Bindings, ExpandedTerm, StoredBindings) :- 124 | functor(Term, Name, Arity), 125 | !, 126 | functor(ExpandedTerm, Name, Arity), 127 | expand_args(1, Bindings, Term, ExpandedTerm, StoredBindings). 128 | 129 | % expand_args(+ArgNum, +Bindings, +Term, +ExpandedTerm, -StoredBindings) 130 | expand_args(ArgNum, Bindings, Term, ExpandedTerm, StoredBindings) :- 131 | arg(ArgNum, Term, Arg), 132 | arg(ArgNum, ExpandedTerm, ExpandedArg), 133 | !, 134 | NextArgNum is ArgNum + 1, 135 | expand_term(Arg, Bindings, ExpandedArg, TermBindings), 136 | append(TermBindings, ArgsBindings, StoredBindings), 137 | expand_args(NextArgNum, Bindings, Term, ExpandedTerm, ArgsBindings). 138 | expand_args(_ArgNum, _Bindings, _Term, _ExpandedTerm, []). 139 | 140 | % var_name(+Bindings, +Var, -Name) 141 | % 142 | % Bindings is a list of Name=Var pairs, where Name is the name of a variable Var. 143 | var_name([Name=SameVar|_Bindings], Var, Name) :- 144 | Var == SameVar, 145 | !. 146 | var_name([_Binding|Bindings], Var, Name) :- 147 | var_name(Bindings, Var, Name). 148 | 149 | % stored_variable_binding(+VarName, -VarValue) 150 | % 151 | % VarValue is the latest value of the variable with name VarName. 152 | % If there is no previous value, an exception is thrown. 153 | stored_variable_binding(VarName, VarValue) :- 154 | var_bindings(Bindings), 155 | member(VarName=VarValue, Bindings), 156 | !. 157 | stored_variable_binding(VarName, _VarValue) :- 158 | throw(jupyter(no_var_binding(VarName))). 159 | 160 | % Update variable list 161 | 162 | % updated_variables(+BindingsToUpdate, +BindingsToUpdateWith, -UpdatedBindings) 163 | % 164 | % The arguments are lists of Name=Var pairs, where Name is the name of a variable Var occurring in the term Term. 165 | % UpdatedBindings contains all elements of BindingsToUpdateWith if the corresponding variables are instantiated. 166 | % It also contains those elements of BindingsToUpdate for which there is no instantiated element with the same variable name in BindingsToUpdateWith. 167 | updated_variables([], [], []) :- !. 168 | updated_variables([], [Name=Value|BindingsToUpdateWith], [Name=Value|UpdatedBindings]) :- 169 | nonvar(Value), 170 | acyclic_term(Value), 171 | !, 172 | updated_variables([], BindingsToUpdateWith, UpdatedBindings). 173 | updated_variables([], [_Name=_Value|BindingsToUpdateWith], UpdatedBindings) :- 174 | updated_variables([], BindingsToUpdateWith, UpdatedBindings). 175 | updated_variables([Name=_Var|BindingsToUpdate], BindingsToUpdateWith, [Name=Value|UpdatedBindings]) :- 176 | member(Name=Value, BindingsToUpdateWith), 177 | nonvar(Value), 178 | acyclic_term(Value), 179 | % There is a value Value for the variable with name Name in BindingsToUpdateWith -> use that value 180 | !, 181 | % Delete the entry from the list BindingsToUpdateWith as it has been processed 182 | delete(BindingsToUpdateWith, Name=Value, RemainingBindings), 183 | updated_variables(BindingsToUpdate, RemainingBindings, UpdatedBindings). 184 | updated_variables([Name=Var|BindingsToUpdate], BindingsToUpdateWith, [Name=Var|UpdatedBindings]) :- 185 | % There is no new value for the variable with name Name 186 | updated_variables(BindingsToUpdate, BindingsToUpdateWith, UpdatedBindings). 187 | 188 | :- end_object. 189 | -------------------------------------------------------------------------------- /logtalk_kernel/logtalk_server/loader.lgt: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % Copyright (c) 2022-2025 Paulo Moura 4 | % Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | % SPDX-License-Identifier: MIT 6 | % 7 | % Permission is hereby granted, free of charge, to any person obtaining a copy 8 | % of this software and associated documentation files (the "Software"), to deal 9 | % in the Software without restriction, including without limitation the rights 10 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | % copies of the Software, and to permit persons to whom the Software is 12 | % furnished to do so, subject to the following conditions: 13 | % 14 | % The above copyright notice and this permission notice shall be included in all 15 | % copies or substantial portions of the Software. 16 | % 17 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | % SOFTWARE. 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | 28 | :- if(current_logtalk_flag(prolog_dialect, xvm)). 29 | :- set_prolog_flag(encoding, 'UTF-8'). 30 | :- elif(current_logtalk_flag(prolog_dialect, swi)). 31 | :- set_prolog_flag(encoding, utf8). 32 | :- endif. 33 | 34 | :- initialization(( 35 | logtalk_load(basic_types(loader)), 36 | logtalk_load(format(loader)), 37 | logtalk_load(json(loader)), 38 | logtalk_load(meta(loader)), 39 | logtalk_load(os(loader)), 40 | logtalk_load(reader(loader)), 41 | logtalk_load(term_io(loader)), 42 | logtalk_load(debugger(loader)), 43 | logtalk_load([ 44 | jupyter_logging, 45 | jupyter_preferences, 46 | jupyter_variable_bindings, 47 | jupyter_query_handling, 48 | jupyter, 49 | jupyter_jsonrpc, 50 | jupyter_request_handling, 51 | jupyter_term_handling, 52 | jupyter_server 53 | ], [ 54 | optimize(on) 55 | % debug(on), 56 | % portability(warning) 57 | ]) 58 | )). 59 | -------------------------------------------------------------------------------- /logtalk_kernel/sicstus_kernel_implementation.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | """ 29 | Default SICStus Prolog kernel implementation. 30 | 31 | Defines the inspection for other predicates than the ones defined in the module jupyter. 32 | """ 33 | 34 | 35 | import requests 36 | 37 | from bs4 import BeautifulSoup, Tag 38 | 39 | from logtalk_kernel.logtalk_kernel_base_implementation import LogtalkKernelBaseImplementation 40 | 41 | # When overriding this at a different location, add the location of the logtalk_kernel_base_implementation.py file defining the LogtalkKernelBaseImplementation class to the search path for modules 42 | #sys.path.append('/path/to/kernel/logtalk_kernel/logtalk_kernel') 43 | #from logtalk_kernel_base_implementation import LogtalkKernelBaseImplementation 44 | 45 | 46 | class LogtalkKernelImplementation(LogtalkKernelBaseImplementation): 47 | 48 | def retrieve_predicate_information(self): 49 | """When inspecting a built-in predicate, the SICStus Prolog version is needed for retrieving the documentation URLs""" 50 | try: 51 | version_response_dict = self.server_request(0, 'version', log_response=False) 52 | self.sicstus_version = version_response_dict["result"] 53 | 54 | self.predicate_doc_links = self.get_predicate_doc_links() 55 | except Exception as exception: 56 | self.logger.error(exception, exc_info=True) 57 | 58 | super().retrieve_predicate_information() 59 | 60 | 61 | def get_predicate_doc_links(self): 62 | """ 63 | Retrieves the links to the documentation of all predicates for the current Prolog version from the Predicate Index page (e.g. https://sicstus.sics.se/sicstus/docs/latest/html/sicstus.html/Predicate-Index.html for the latest version). 64 | The link texts look like the following: 'zip/0 (built-in):', 'assert/[1,2] (built-in, ref page):' 65 | 66 | Returns a dictionary where the keys are the names and arities of the built-in predicates (the part of the link text before the bracket). 67 | The values are lists containing dictionaries with elements 'link_text' and 'link'. 68 | 69 | Additionally, the dictionary contains elements where the key is the predicate name and arity of a predicate exported by the Prolog module 'jupyter'. 70 | For such a predicate the corresponding value is the documentation of the predicate as a string. 71 | 72 | Example 73 | ------ 74 | The dictionary containing the first built-in element only might look like the following: 75 | {'!/0': [{'link_text': '!/0 (built-in, ref page)', 'link': 'https://sicstus.sics.se/sicstus/docs/4.5.1/html/sicstus.html/mpg_002dref_002dcut.html#index-_0021_002f0-_0028built_002din_002c-ref-page_0029-1'}]} 76 | """ 77 | try: 78 | # Get the html content and parse it 79 | base_url = 'https://sicstus.sics.se/sicstus/docs/' + self.sicstus_version + '/html/sicstus.html/' 80 | 81 | predicate_index_link = base_url + '/Predicate-Index.html' 82 | response = requests.get(predicate_index_link, timeout=1) 83 | soup = BeautifulSoup(response.text, 'html.parser') 84 | 85 | predicate_data = {} 86 | 87 | # The table "index-pl" contains all the predicate links 88 | # Find the hyperlink objects 89 | tables = soup.find_all('table', class_='index-pl') 90 | if len(tables) > 0: 91 | for link in tables[0].find_all('a'): 92 | if len(link.contents) > 0: 93 | child = link.contents[0] 94 | if isinstance(child, Tag): 95 | # Remove the part in brackets 96 | predicate_string = child.text.split('(')[0].strip() 97 | predicate_link_dict = {'link_text': child.text, 'link': base_url + link.get('href')} 98 | if predicate_string in predicate_data: 99 | link_dicts = predicate_data[predicate_string] 100 | link_dicts.append(predicate_link_dict) 101 | predicate_data[predicate_string] = link_dicts 102 | else: 103 | predicate_data[predicate_string] = [predicate_link_dict] 104 | 105 | return predicate_data 106 | except Exception as exception: 107 | self.logger.error(exception, exc_info=True) 108 | return None 109 | 110 | 111 | def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): 112 | """ 113 | For SICStus Prolog, the website https://sicstus.sics.se/sicstus/docs/latest/html/sicstus.html/Predicate-Index.html lists links to the documentation of predicates. 114 | When inspecting a token, links for matching predicates are shown preceding the docs for predicates from module jupyter. 115 | """ 116 | # Get the matching predicates from module jupyter 117 | token, jupyter_data = self.get_token_and_jupyter_predicate_inspection_data(code, cursor_pos) 118 | 119 | if not token: 120 | # There is no token which can be inspected 121 | return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} 122 | 123 | # If the links could not be retrieved when starting the kernel, try reading them again 124 | if self.predicate_doc_links is None: 125 | self.predicate_doc_links = self.get_predicate_doc_links() 126 | 127 | if self.predicate_doc_links is None and jupyter_data == {}: 128 | # There is no matching predicate 129 | return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} 130 | 131 | # Find the matching predicate links 132 | # If a key of the dictionary contains the current token, the element is assumed to match 133 | matching_predicate_data = {pred:self.predicate_doc_links[pred] for pred in self.predicate_doc_links if (token in pred)} 134 | 135 | found = True 136 | 137 | if len(matching_predicate_data) == 0: 138 | # There is no matching predicate from the Predicate Index website 139 | if jupyter_data == {}: 140 | data = {} 141 | found = False 142 | else: 143 | data = jupyter_data 144 | else: 145 | # There is a matching predicate from the Predicate Index website 146 | # Compute the link texts 147 | jupyter_docs_plain = '' 148 | jupyter_docs_md = '' 149 | 150 | for pred, data in matching_predicate_data.items(): 151 | if isinstance(data, list): 152 | for link_dict in data: 153 | jupyter_docs_plain += '\x1b[0m' + link_dict['link_text'] + ':\n\x1b[0;34m' + link_dict['link'] + '\n\n' 154 | jupyter_docs_md += '
' + link_dict['link_text'] + '

' 155 | 156 | if jupyter_data != {}: 157 | # Append the jupyter docs 158 | jupyter_docs_plain += '\x1b[0m' + '_'*80 + '\n\n' + jupyter_data['text/plain'] 159 | jupyter_docs_md += '' + '_'*80 + '

' + jupyter_data['text/markdown'] 160 | 161 | data = {'text/plain': jupyter_docs_plain, 'text/markdown': jupyter_docs_md} 162 | 163 | return {'status': 'ok', 'data': data, 'metadata': {}, 'found': found} 164 | -------------------------------------------------------------------------------- /logtalk_kernel/swi_kernel_implementation.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # 3 | # Copyright (c) 2022-2023 Paulo Moura 4 | # Copyright (c) 2022 Anne Brecklinghaus, Michael Leuschel, dgelessus 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | ############################################################################# 26 | 27 | 28 | """ 29 | Default SWI-Prolog kernel implementation. 30 | 31 | Defines the inspection for other predicates than the ones defined in the module jupyter. 32 | """ 33 | 34 | 35 | from logtalk_kernel.logtalk_kernel_base_implementation import LogtalkKernelBaseImplementation 36 | 37 | # When overriding this at a different location, add the location of the logtalk_kernel_base_implementation.py file defining the LogtalkKernelBaseImplementation class to the search path for modules 38 | #sys.path.append('/path/to/kernel/logtalk_kernel/logtalk_kernel') 39 | #from logtalk_kernel_base_implementation import LogtalkKernelBaseImplementation 40 | 41 | 42 | class LogtalkKernelImplementation(LogtalkKernelBaseImplementation): 43 | 44 | def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): 45 | """ 46 | For SWI-Prolog, help for a predicate can be accessed with help/1. 47 | When inspecting a token, the output of this predicate precedes the docs for predicates from module jupyter. 48 | """ 49 | # Get the matching predicates from module jupyter 50 | token, jupyter_data = self.get_token_and_jupyter_predicate_inspection_data(code, cursor_pos) 51 | 52 | if not token: 53 | # There is no token which can be inspected 54 | return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} 55 | 56 | try: 57 | # Request predicate help with help/1 58 | response_dict = self.server_request(0, 'call', {'code':'help(' + token + ')'}) 59 | help_output = response_dict["result"]["1"]["output"] 60 | 61 | except Exception as exception: 62 | self.logger.error(exception, exc_info=True) 63 | help_output = '' 64 | 65 | found = True 66 | 67 | if help_output == '': 68 | # There is no help/1 ouput 69 | if jupyter_data == {}: 70 | data = {} 71 | found = False 72 | else: 73 | data = jupyter_data 74 | else: 75 | # There is help/1 ouput 76 | jupyter_docs_plain = help_output 77 | jupyter_docs_md = '
' + help_output.replace('\n', '
').replace('$', '$') + '
' 78 | 79 | if jupyter_data != {}: 80 | # Append the jupyter docs 81 | jupyter_docs_plain += '\n\n' + '_'*80 + '\n\n' + jupyter_data['text/plain'] 82 | jupyter_docs_md += '
' + '_'*80 + '

' + jupyter_data['text/markdown'] 83 | 84 | data = {'text/plain': jupyter_docs_plain, 'text/markdown': jupyter_docs_md} 85 | 86 | return {'status': 'ok', 'data': data, 'metadata': {}, 'found': found} 87 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = [ 4 | "setuptools>=61.0" 5 | ] 6 | 7 | [project] 8 | name = "logtalk-jupyter-kernel" 9 | version = "0.31.0" 10 | authors = [ 11 | { name = "Paulo Moura", email = "pmoura@logtalk.org" }, 12 | { name = "Anne Brecklinghaus" }, 13 | { name = "Michael Leuschel" }, 14 | { name = "dgelessus" } 15 | ] 16 | maintainers = [ 17 | { name = "Paulo Moura", email = "pmoura@logtalk.org" } 18 | ] 19 | description = "Hercutalk - A Jupyter Kernel for Logtalk" 20 | readme = "README.md" 21 | license = { file="LICENSE" } 22 | requires-python = ">=3.7" 23 | classifiers = [ 24 | "Development Status :: 4 - Beta", 25 | "Framework :: Jupyter", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: Education", 30 | "Topic :: Scientific/Engineering", 31 | "Topic :: System :: Shells", 32 | "Natural Language :: English", 33 | "Programming Language :: Prolog", 34 | ] 35 | keywords = [ 36 | "logtalk", 37 | "prolog", 38 | "logic-programming", 39 | ] 40 | dependencies = [ 41 | "jupyter_client", 42 | "IPython", 43 | "ipykernel", 44 | "jupytext", 45 | "graphviz", 46 | "beautifulsoup4", 47 | "matplotlib" 48 | ] 49 | 50 | [project.urls] 51 | "Source" = "https://github.com/LogtalkDotOrg/logtalk-jupyter-kernel" 52 | "Issues" = "https://github.com/LogtalkDotOrg/logtalk-jupyter-kernel/issues" 53 | 54 | [project.optional-dependencies] 55 | dev = [ 56 | "jupyter_core", 57 | "jupyterlab", 58 | "notebook", 59 | ] 60 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | packages = find_namespace: 3 | include_package_data = True 4 | 5 | [options.packages.find] 6 | exclude = 7 | notebooks* 8 | 9 | [options.package_data] 10 | * = *.js, *.json, *.lgt 11 | --------------------------------------------------------------------------------