├── .gitignore ├── LICENSE ├── README.md ├── jswipl ├── __init__.py ├── jupyter.py ├── swipl.py └── test.py ├── kernel.json ├── release.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | 107 | temp.pl 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Luca Corbatto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jswip 2 | A Jupyter Kernel for SWI-Prolog. 3 | 4 | Inspired by [madmax2012/SWI-Prolog-Kernel](https://github.com/madmax2012/SWI-Prolog-Kernel) and now using [PySwip](https://github.com/yuce/pyswip). 5 | 6 | **USE WITH CARE!** 7 | 8 | This kernel is only barely tested using jupyter lab on debian linux. If anyone tests it e. g. on plain jupyter and/or windows feel free to open a ticket to report success and/or failure. 9 | 10 | I have only just started out learning prolog and have not tested this kernel with all language constructs of prolog yet. There might still be problems with more advanced prolog stuff. 11 | 12 | ## Usage Notes and Limitations 13 | 14 | You should **split your knowledgebase and your queries into different cells**. KB entries (facts and so on) will be retained as long as the kernel is running. This means that if you run a cell containing facts twice the facts will be sent to swipl twice. This will typically not result in errors but in odd outputs when querying the KB. 15 | 16 | Should you receive bogus output when running queries restart the kernel and make sure you run any cell containing something other than queries only once. 17 | 18 | Every query must start with `?-`. 19 | 20 | Working example: 21 | 22 | ``` 23 | man(socrates). 24 | mortal(X) :- man(X). 25 | 26 | ?- mortal(socrates). 27 | ?- mortal(X). 28 | ?- mortal(bob). 29 | ``` 30 | 31 | Will output. 32 | 33 | ``` 34 | true. 35 | X = socrates. 36 | false. 37 | ``` 38 | 39 | Keep in mind that for some queries there are lots of answers. For the kernel to always succeed in a reasonable amount of time the default output limit to any one query is 10 answers. You can influence this limit by the following syntax. 40 | 41 | ``` 42 | ?- someQuery(...) {LIMIT}. 43 | ``` 44 | 45 | Where `LIMIT` is replaced by the maximum number of answers that will be displayed for this query. It is not very smartly parsed (sorry), so there must be no spaces inside the curly braces or in between the closing curly brace and the period. *Not like this:* ~`{1} .`~ or this ~`{ 1 }.`~ 46 | 47 | A limit value of `-1` means no limit. Be careful with this! 48 | 49 | ## Supported environments 50 | 51 | Only **pyhton3** is supported (anybody still using python2 should really have upgraded by now...) and it was only tested on **linux** as I have no jupyter installation on windows. In theory it should work on windows though. 52 | 53 | ## Installation 54 | 55 | 1. Install [SWI-Prolog](http://www.swi-prolog.org). 56 | 2. Install jswipl `python3 -m pip install --upgrade --user jswipl` 57 | 3. Change directory to your jupyters kernel directory. Typically `~/.local/share/jupyter/kernels`. 58 | 4. `mkdir jswipl && cd jswipl` 59 | 5. Install kernel spec: `wget https://raw.githubusercontent.com/targodan/jupyter-swi-prolog/master/kernel.json` 60 | 6. Restart jupyter 61 | 7. Profit 62 | 63 | ## Upgrading 64 | 65 | Keeping up to date is as simple as running `python3 -m pip install --upgrade --user jswipl` from time to time. 66 | 67 | ## Contributing 68 | 69 | Feel free to open tickets if something goes wrong or open pull requests. If you open a pull request do open it onto the `develop` branch. This repository loosely adheres to the [git flow workflow](https://datasift.github.io/gitflow/IntroducingGitFlow.html). The `master` branch is the branch on which only "released" versions are. Anything in development goes into the `develop` branch. 70 | 71 | Please do keep in mind that this is just a very small side project and I am unlikely to sink tons of development time into this. However I will try to answer on any issues even if it's just a quick "Yup, would be nice. Please open a pull request someone." and I'll try to handly pull requests quickly. 72 | 73 | Anyone who's interested in becoming a co-maintainer: open an issue and let me know. :) 74 | -------------------------------------------------------------------------------- /jswipl/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["jupyter"] 2 | -------------------------------------------------------------------------------- /jswipl/jupyter.py: -------------------------------------------------------------------------------- 1 | from . import swipl 2 | 3 | def main(): 4 | from ipykernel.kernelapp import IPKernelApp 5 | IPKernelApp.launch_instance(kernel_class=SwiplKernel) 6 | 7 | """SWI-Prolog kernel wrapper""" 8 | from ipykernel.kernelbase import Kernel 9 | 10 | class SwiplKernel(Kernel): 11 | implementation = 'SWI-Prolog' 12 | implementation_version = '1.0.2' 13 | language = 'Prolog' 14 | language_version = '1.0' 15 | language_info = {'name': 'swipl', 16 | 'mimetype': 'text/plain'} 17 | banner = "SWI-Prolog Kernel" 18 | 19 | def do_execute(self, code, silent, 20 | store_history=True, 21 | user_expressions=None, 22 | allow_stdin=False): 23 | """This function is called when a code cell is 24 | executed.""" 25 | if not silent: 26 | # We run the Prolog code and get the output. 27 | output, ok = swipl.run(code) 28 | 29 | # We send back the result to the frontend. 30 | stream_content = {'name': 'stdout', 31 | 'text': "\n".join(output)} 32 | self.send_response(self.iopub_socket, 33 | 'stream', stream_content) 34 | return {'status': 'ok', 35 | # The base class increments the execution 36 | # count 37 | 'execution_count': self.execution_count, 38 | 'payload': [], 39 | 'user_expressions': {}, 40 | } 41 | -------------------------------------------------------------------------------- /jswipl/swipl.py: -------------------------------------------------------------------------------- 1 | from pyswip import Prolog 2 | from pyswip import Functor 3 | from pyswip.prolog import PrologError 4 | 5 | DEFAULT_LIMIT = 10 6 | 7 | def format_value(value): 8 | output = "" 9 | if isinstance(value, list): 10 | output = "[ " + ", ".join([format_value(val) for val in value]) + " ]" 11 | elif isinstance(value, Functor) and value.arity == 2: 12 | output = "{0}{1}{2}".format(value.args[0], value.name, value.args[1]) 13 | else: 14 | output = "{}".format(value) 15 | 16 | return output 17 | 18 | def format_result(result): 19 | result = list(result) 20 | 21 | if len(result) == 0: 22 | return "false." 23 | 24 | if len(result) == 1 and len(result[0]) == 0: 25 | return "true." 26 | 27 | output = "" 28 | for res in result: 29 | tmpOutput = [] 30 | for var in res: 31 | tmpOutput.append(var + " = " + format_value(res[var])) 32 | output += ", ".join(tmpOutput) + " ;\n" 33 | output = output[:-3] + " ." 34 | 35 | return output 36 | 37 | def run(code): 38 | prolog = Prolog() 39 | 40 | output = [] 41 | ok = True 42 | 43 | tmp = "" 44 | isQuery = False 45 | for line in code.split("\n"): 46 | line = line.strip() 47 | if line == "" or line[0] == "%": 48 | continue 49 | 50 | if line[:2] == "?-": 51 | isQuery = True 52 | line = line[2:] 53 | 54 | tmp += " " + line 55 | 56 | if tmp[-1] == ".": 57 | # End of statement 58 | tmp = tmp[:-1] # Removes "." 59 | maxresults = DEFAULT_LIMIT 60 | # Checks for maxresults 61 | if tmp[-1] == "}": 62 | tmp = tmp[:-1] # Removes "." 63 | limitStart = tmp.rfind('{') 64 | if limitStart == -1: 65 | ok = False 66 | output.append("ERROR: Found '}' before '.' but opening '{' is missing!") 67 | else: 68 | limit = tmp[limitStart+1:] 69 | try: 70 | maxresults = int(limit) 71 | except: 72 | ok = False 73 | output.append("ERROR: Invalid limit {" + limit + "}!") 74 | tmp = tmp[:limitStart] 75 | 76 | try: 77 | if isQuery: 78 | result = prolog.query(tmp, maxresult=maxresults) 79 | output.append(format_result(result)) 80 | result.close() 81 | else: 82 | prolog.assertz('(' + tmp + ')') 83 | except PrologError as error: 84 | ok = False 85 | output.append("ERROR: {}".format(error)) 86 | 87 | tmp = "" 88 | isQuery = False 89 | 90 | return output, ok 91 | -------------------------------------------------------------------------------- /jswipl/test.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | 3 | import tempfile 4 | import swipl 5 | 6 | europe_pl = """ 7 | neighbours(austria , [czech_republic, germany, hungary, italy, 8 | slovenia, slovakia]). 9 | neighbours(belgium , [france, netherlands, luxemburg, germany, 10 | united_kingdom]). 11 | neighbours(bulgaria , [romania, greece]). 12 | neighbours(croatia , [slovenia, hungary]). 13 | neighbours(cyprus , [greece]). 14 | neighbours(czech_republic , [germany, poland, slovakia, austria]). 15 | neighbours(denmark , [germany, sweden]). 16 | neighbours(estonia , [finland, latvia, lithuania]). 17 | neighbours(finland , [estonia, sweden]). 18 | neighbours(france , [spain, belgium, luxemburg, germany, italy, 19 | united_kingdom]). 20 | neighbours(germany , [netherlands, belgium, luxemburg, denmark, 21 | france, austria, poland]). 22 | neighbours(greece , [bulgaria, cyprus]). 23 | neighbours(hungary , [austria, slovakia, romania, croatia, 24 | slovenia]). 25 | neighbours(ireland , [united_kingdom]). 26 | neighbours(italy , [france, austria, slovenia]). 27 | neighbours(latvia , [estonia, lithuania]). 28 | neighbours(luxemburg , [belgium, france, germany]). 29 | neighbours(malta , []). 30 | neighbours(netherlands , [belgium, germany , united_kingdom]). 31 | neighbours(poland , [germany, czech_republic, slovakia, 32 | lithuania]). 33 | neighbours(portugal , [spain]). 34 | neighbours(romania , [hungary, bulgaria]). 35 | neighbours(slovakia , [czech_republic, poland, hungary, austria]). 36 | neighbours(slovenia , [austria, italy, hungary, croatia]). 37 | neighbours(spain , [france, portugal]). 38 | neighbours(sweden , [finland, denmark]). 39 | neighbours(united_kingdom , [ireland, netherlands, belgium, france]). 40 | 41 | colour_countries(Colours) :- 42 | setof(Country/_, X^neighbours(Country,X), Colours), 43 | colours(Colours). 44 | 45 | colours([]). 46 | colours([Country/Colour | Rest]):- 47 | colours(Rest), 48 | member(Colour, [green, yellow, red, purple]), 49 | \+ (member(CountryA/Colour, Rest), neighbour(Country, CountryA)). 50 | 51 | neighbour(Country, CountryA):- 52 | neighbours(Country, Neighbours), 53 | member(CountryA, Neighbours). 54 | 55 | member(X, [X|_]). 56 | member(X, [_|Tail]) :- member(X, Tail). 57 | 58 | ?- colour_countries(Map){1}. 59 | """ 60 | 61 | socrates_pl = """ 62 | man(socrates). 63 | man(bob). 64 | mortal(X) :- man(X). 65 | 66 | ?- mortal(socrates). 67 | ?- mortal(X). 68 | ?- mortal(socrates2). 69 | """ 70 | 71 | def main(): 72 | output, ok = swipl.run(socrates_pl) 73 | output, ok = swipl.run(socrates_pl) 74 | 75 | if ok: 76 | print("OK") 77 | else: 78 | print("NOT OK") 79 | 80 | print("\n".join(output)) 81 | 82 | if __name__ == "__main__": 83 | main() 84 | -------------------------------------------------------------------------------- /kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": [ 3 | "jswiplkernel", 4 | "-f", 5 | "{connection_file}" 6 | ], 7 | "display_name": "SWI-Prolog" 8 | } 9 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf dist &>/dev/null 4 | python -m pip install --user --upgrade setuptools wheel twine 5 | python setup.py sdist bdist_wheel 6 | 7 | echo -n "Deploy [N/y]? " 8 | read -n1 yesNo 9 | 10 | if [[ "$yesNo" == $(printf "\n\n") ]]; then 11 | yesNo=n 12 | else 13 | echo "" 14 | fi 15 | 16 | if [[ "$yesNo" == "y" || "$yesNo" == "Y" ]]; then 17 | twine upload dist/* 18 | fi 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="jswipl", 8 | version="1.0.2", 9 | author="Luca Corbatto", 10 | author_email="luca-pip@corbatto.de", 11 | description="A Jupyter Kernel for SWI-Prolog.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/targodan/jupyter-swi-prolog", 15 | packages=setuptools.find_packages(), 16 | install_requires=[ 17 | "pyswip", 18 | "ipykernel" 19 | ], 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ], 25 | entry_points={ 26 | 'console_scripts': ['jswiplkernel=jswipl.jupyter:main'], 27 | } 28 | ) 29 | --------------------------------------------------------------------------------