├── .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! <
' + 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 + '
' + help_output.replace('\n', '' 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 += '
').replace('$', '$') + '