diff options
-rw-r--r-- | prolog/core.py | 1022 | ||||
-rw-r--r-- | prolog/engine.py | 384 | ||||
-rw-r--r-- | prolog/lib.pl | 462 |
3 files changed, 54 insertions, 1814 deletions
diff --git a/prolog/core.py b/prolog/core.py deleted file mode 100644 index afcfdd0..0000000 --- a/prolog/core.py +++ /dev/null @@ -1,1022 +0,0 @@ -# -*- coding: utf-8 -*- - - -# pyswip -- Python SWI-Prolog bridge -# Copyright (c) 2007-2012 YĆ¼ce Tekol -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -import os -import re -import sys -import locale -import glob -import warnings -from subprocess import Popen, PIPE -from ctypes import * -from ctypes.util import find_library - - -# To initialize the SWI-Prolog environment, two things need to be done: the -# first is to find where the SO/DLL is located and the second is to find the -# SWI-Prolog home, to get the saved state. -# -# The goal of the (entangled) process below is to make the library installation -# independent. - - -def _findSwiplPathFromFindLib(): - """ - This function resorts to ctype's find_library to find the path to the - DLL. The biggest problem is that find_library does not give the path to the - resource file. - - :returns: - A path to the swipl SO/DLL or None if it is not found. - - :returns type: - {str, None} - """ - - path = (find_library('swipl') or - find_library('pl') or - find_library('libswipl')) # This last one is for Windows - return path - - -def _findSwiplFromExec(): - """ - This function tries to use an executable on the path to find SWI-Prolog - SO/DLL and the resource file. - - :returns: - A tuple of (path to the swipl DLL, path to the resource file) - - :returns type: - ({str, None}, {str, None}) - """ - - platform = sys.platform[:3] - - fullName = None - swiHome = None - - try: # try to get library path from swipl executable. - # We may have pl or swipl as the executable - try: - cmd = Popen(['swipl', '-dump-runtime-variables'], - stdout=PIPE, universal_newlines=True) - except OSError: - cmd = Popen(['pl', '-dump-runtime-variables'], - stdout=PIPE, universal_newlines=True) - ret = cmd.communicate() - - # Parse the output into a dictionary - ret = ret[0].replace(';', '').splitlines() - ret = [line.split('=', 1) for line in ret] - rtvars = dict((name, value[1:-1]) for name, value in ret) # [1:-1] gets - # rid of the - # quotes - - if rtvars['PLSHARED'] == 'no': - raise ImportError('SWI-Prolog is not installed as a shared ' - 'library.') - else: # PLSHARED == 'yes' - swiHome = rtvars['PLBASE'] # The environment is in PLBASE - if not os.path.exists(swiHome): - swiHome = None - - # determine platform specific path - if platform == "win": - dllName = rtvars['PLLIB'][:-4] + '.' + rtvars['PLSOEXT'] - path = os.path.join(rtvars['PLBASE'], 'bin') - fullName = os.path.join(path, dllName) - - if not os.path.exists(fullName): - fullName = None - - elif platform == "cyg": - # e.g. /usr/lib/pl-5.6.36/bin/i686-cygwin/cygpl.dll - - dllName = 'cygpl.dll' - path = os.path.join(rtvars['PLBASE'], 'bin', rtvars['PLARCH']) - fullName = os.path.join(path, dllName) - - if not os.path.exists(fullName): - fullName = None - - elif platform == "dar": - dllName = 'lib' + rtvars['PLLIB'][2:] + '.' + rtvars['PLSOEXT'] - path = os.path.join(rtvars['PLBASE'], 'lib', rtvars['PLARCH']) - baseName = os.path.join(path, dllName) - - if os.path.exists(baseName): - fullName = baseName - else: # We will search for versions - fullName = None - - else: # assume UNIX-like - # The SO name in some linuxes is of the form libswipl.so.5.10.2, - # so we have to use glob to find the correct one - dllName = 'lib' + rtvars['PLLIB'][2:] + '.' + rtvars['PLSOEXT'] - path = os.path.join(rtvars['PLBASE'], 'lib', rtvars['PLARCH']) - baseName = os.path.join(path, dllName) - - if os.path.exists(baseName): - fullName = baseName - else: # We will search for versions - pattern = baseName + '.*' - files = glob.glob(pattern) - if len(files) == 0: - fullName = None - elif len(files) == 1: - fullName = files[0] - else: # Will this ever happen? - fullName = None - - except (OSError, KeyError): # KeyError from accessing rtvars - pass - - return (fullName, swiHome) - - -def _findSwiplWin(): - """ - This function uses several heuristics to gues where SWI-Prolog is installed - in Windows. It always returns None as the path of the resource file because, - in Windows, the way to find it is more robust so the SWI-Prolog DLL is - always able to find it. - - :returns: - A tuple of (path to the swipl DLL, path to the resource file) - - :returns type: - ({str, None}, {str, None}) - """ - - dllNames = ('swipl.dll', 'libswipl.dll') - - # First try: check the usual installation path (this is faster but - # hardcoded) - programFiles = os.getenv('ProgramFiles') - paths = [os.path.join(programFiles, r'pl\bin', dllName) - for dllName in dllNames] - for path in paths: - if os.path.exists(path): - return (path, None) - - # Second try: use the find_library - path = _findSwiplPathFromFindLib() - if path is not None and os.path.exists(path): - return (path, None) - - # Third try: use reg.exe to find the installation path in the registry - # (reg should be installed in all Windows XPs) - try: - cmd = Popen(['reg', 'query', - r'HKEY_LOCAL_MACHINE\Software\SWI\Prolog', - '/v', 'home'], stdout=PIPE) - ret = cmd.communicate() - - # Result is like: - # ! REG.EXE VERSION 3.0 - # - # HKEY_LOCAL_MACHINE\Software\SWI\Prolog - # home REG_SZ C:\Program Files\pl - # (Note: spaces may be \t or spaces in the output) - ret = ret[0].splitlines() - ret = [line for line in ret if len(line) > 0] - pattern = re.compile('[^h]*home[^R]*REG_SZ( |\t)*(.*)$') - match = pattern.match(ret[-1]) - if match is not None: - path = match.group(2) - - paths = [os.path.join(path, 'bin', dllName) - for dllName in dllNames] - for path in paths: - if os.path.exists(path): - return (path, None) - - except OSError: - # reg.exe not found? Weird... - pass - - # May the exec is on path? - (path, swiHome) = _findSwiplFromExec() - if path is not None: - return (path, swiHome) - - # Last try: maybe it is in the current dir - for dllName in dllNames: - if os.path.exists(dllName): - return (dllName, None) - - return (None, None) - -def _findSwiplLin(): - """ - This function uses several heuristics to guess where SWI-Prolog is - installed in Linuxes. - - :returns: - A tuple of (path to the swipl so, path to the resource file) - - :returns type: - ({str, None}, {str, None}) - """ - - # Maybe the exec is on path? - (path, swiHome) = _findSwiplFromExec() - if path is not None: - return (path, swiHome) - - # If it is not, use find_library - path = _findSwiplPathFromFindLib() - if path is not None: - return (path, swiHome) - - # Our last try: some hardcoded paths. - paths = ['/lib', '/usr/lib', '/usr/local/lib', '.', './lib'] - names = ['libswipl.so', 'libpl.so'] - - path = None - for name in names: - for try_ in paths: - try_ = os.path.join(try_, name) - if os.path.exists(try_): - path = try_ - break - - if path is not None: - return (path, swiHome) - - return (None, None) - - -def _findSwiplDar(): - """ - This function uses several heuristics to guess where SWI-Prolog is - installed in MacOS. - - :returns: - A tuple of (path to the swipl so, path to the resource file) - - :returns type: - ({str, None}, {str, None}) - """ - - # If the exec is in path - (path, swiHome) = _findSwiplFromExec() - if path is not None: - return (path, swiHome) - - # If it is not, use find_library - path = _findSwiplPathFromFindLib() - if path is not None: - return (path, swiHome) - - # Last guess, searching for the file - paths = ['.', './lib', '/usr/lib/', '/usr/local/lib', '/opt/local/lib'] - names = ['libswipl.dylib', 'libpl.dylib'] - - for name in names: - for path in paths: - path = os.path.join(path, name) - if os.path.exists(path): - return (path, None) - - return (None, None) - - -def _findSwipl(): - """ - This function makes a big effort to find the path to the SWI-Prolog shared - library. Since this is both OS dependent and installation dependent, we may - not aways succeed. If we do, we return a name/path that can be used by - CDLL(). Otherwise we raise an exception. - - :return: Tuple. Fist element is the name or path to the library that can be - used by CDLL. Second element is the path were SWI-Prolog resource - file may be found (this is needed in some Linuxes) - :rtype: Tuple of strings - :raises ImportError: If we cannot guess the name of the library - """ - - # Now begins the guesswork - platform = sys.platform[:3] - if platform == "win": # In Windows, we have the default installer - # path and the registry to look - (path, swiHome) = _findSwiplWin() - - elif platform in ("lin", "cyg"): - (path, swiHome) = _findSwiplLin() - - elif platform == "dar": # Help with MacOS is welcome!! - (path, swiHome) = _findSwiplDar() - - else: - raise EnvironmentError('The platform %s is not supported by this ' - 'library. If you want it to be supported, ' - 'please open an issue.' % platform) - - # This is a catch all raise - if path is None: - raise ImportError('Could not find the SWI-Prolog library in this ' - 'platform. If you are sure it is installed, please ' - 'open an issue.') - else: - return (path, swiHome) - - -def _fixWindowsPath(dll): - """ - When the path to the DLL is not in Windows search path, Windows will not be - able to find other DLLs on the same directory, so we have to add it to the - path. This function takes care of it. - - :parameters: - - `dll` (str) - File name of the DLL - """ - - if sys.platform[:3] != 'win': - return # Nothing to do here - - pathToDll = os.path.dirname(dll) - currentWindowsPath = os.getenv('PATH') - - if pathToDll not in currentWindowsPath: - # We will prepend the path, to avoid conflicts between DLLs - newPath = pathToDll + ';' + currentWindowsPath - os.putenv('PATH', newPath) - - -# Find the path and resource file. SWI_HOME_DIR shall be treated as a constant -# by users of this module -(_path, SWI_HOME_DIR) = _findSwipl() -_fixWindowsPath(_path) - - -# Load the library -_lib = CDLL(_path) - -# PySWIP constants -PYSWIP_MAXSTR = 1024 -c_int_p = c_void_p -c_long_p = c_void_p -c_double_p = c_void_p -c_uint_p = c_void_p - -# constants (from SWI-Prolog.h) -# PL_unify_term() arguments -PL_VARIABLE = 1 # nothing -PL_ATOM = 2 # const char -PL_INTEGER = 3 # int -PL_FLOAT = 4 # double -PL_STRING = 5 # const char * -PL_TERM = 6 # -# PL_unify_term() -PL_FUNCTOR = 10 # functor_t, arg ... -PL_LIST = 11 # length, arg ... -PL_CHARS = 12 # const char * -PL_POINTER = 13 # void * -# /* PlArg::PlArg(text, type) */ -#define PL_CODE_LIST (14) /* [ascii...] */ -#define PL_CHAR_LIST (15) /* [h,e,l,l,o] */ -#define PL_BOOL (16) /* PL_set_feature() */ -#define PL_FUNCTOR_CHARS (17) /* PL_unify_term() */ -#define _PL_PREDICATE_INDICATOR (18) /* predicate_t (Procedure) */ -#define PL_SHORT (19) /* short */ -#define PL_INT (20) /* int */ -#define PL_LONG (21) /* long */ -#define PL_DOUBLE (22) /* double */ -#define PL_NCHARS (23) /* unsigned, const char * */ -#define PL_UTF8_CHARS (24) /* const char * */ -#define PL_UTF8_STRING (25) /* const char * */ -#define PL_INT64 (26) /* int64_t */ -#define PL_NUTF8_CHARS (27) /* unsigned, const char * */ -#define PL_NUTF8_CODES (29) /* unsigned, const char * */ -#define PL_NUTF8_STRING (30) /* unsigned, const char * */ -#define PL_NWCHARS (31) /* unsigned, const wchar_t * */ -#define PL_NWCODES (32) /* unsigned, const wchar_t * */ -#define PL_NWSTRING (33) /* unsigned, const wchar_t * */ -#define PL_MBCHARS (34) /* const char * */ -#define PL_MBCODES (35) /* const char * */ -#define PL_MBSTRING (36) /* const char * */ - -# /******************************** -# * NON-DETERMINISTIC CALL/RETURN * -# *********************************/ -# -# Note 1: Non-deterministic foreign functions may also use the deterministic -# return methods PL_succeed and PL_fail. -# -# Note 2: The argument to PL_retry is a 30 bits signed integer (long). - -PL_FIRST_CALL = 0 -PL_CUTTED = 1 -PL_REDO = 2 - -PL_FA_NOTRACE = 0x01 # foreign cannot be traced -PL_FA_TRANSPARENT = 0x02 # foreign is module transparent -PL_FA_NONDETERMINISTIC = 0x04 # foreign is non-deterministic -PL_FA_VARARGS = 0x08 # call using t0, ac, ctx -PL_FA_CREF = 0x10 # Internal: has clause-reference */ - -# /******************************* -# * CALL-BACK * -# *******************************/ - -PL_Q_DEBUG = 0x01 # = TRUE for backward compatibility -PL_Q_NORMAL = 0x02 # normal usage -PL_Q_NODEBUG = 0x04 # use this one -PL_Q_CATCH_EXCEPTION = 0x08 # handle exceptions in C -PL_Q_PASS_EXCEPTION = 0x10 # pass to parent environment -PL_Q_DETERMINISTIC = 0x20 # call was deterministic - -# /******************************* -# * BLOBS * -# *******************************/ - -#define PL_BLOB_MAGIC_B 0x75293a00 /* Magic to validate a blob-type */ -#define PL_BLOB_VERSION 1 /* Current version */ -#define PL_BLOB_MAGIC (PL_BLOB_MAGIC_B|PL_BLOB_VERSION) - -#define PL_BLOB_UNIQUE 0x01 /* Blob content is unique */ -#define PL_BLOB_TEXT 0x02 /* blob contains text */ -#define PL_BLOB_NOCOPY 0x04 /* do not copy the data */ -#define PL_BLOB_WCHAR 0x08 /* wide character string */ - -# /******************************* -# * CHAR BUFFERS * -# *******************************/ - -CVT_ATOM = 0x0001 -CVT_STRING = 0x0002 -CVT_LIST = 0x0004 -CVT_INTEGER = 0x0008 -CVT_FLOAT = 0x0010 -CVT_VARIABLE = 0x0020 -CVT_NUMBER = CVT_INTEGER | CVT_FLOAT -CVT_ATOMIC = CVT_NUMBER | CVT_ATOM | CVT_STRING -CVT_WRITE = 0x0040 # as of version 3.2.10 -CVT_ALL = CVT_ATOMIC | CVT_LIST -CVT_MASK = 0x00ff - -BUF_DISCARDABLE = 0x0000 -BUF_RING = 0x0100 -BUF_MALLOC = 0x0200 - -CVT_EXCEPTION = 0x10000 # throw exception on error - -# used to convert python strings to bytes (and back) when calling C functions -encoding = locale.getpreferredencoding() - -argv = (c_char_p*(len(sys.argv) + 1))() -for i, arg in enumerate(sys.argv): - argv[i] = bytes(arg, encoding=encoding) - -argv[-1] = None -argc = len(sys.argv) - -# /******************************* -# * TYPES * -# *******************************/ -# -# typedef uintptr_t atom_t; /* Prolog atom */ -# typedef uintptr_t functor_t; /* Name/arity pair */ -# typedef void * module_t; /* Prolog module */ -# typedef void * predicate_t; /* Prolog procedure */ -# typedef void * record_t; /* Prolog recorded term */ -# typedef uintptr_t term_t; /* opaque term handle */ -# typedef uintptr_t qid_t; /* opaque query handle */ -# typedef uintptr_t PL_fid_t; /* opaque foreign context handle */ -# typedef void * control_t; /* non-deterministic control arg */ -# typedef void * PL_engine_t; /* opaque engine handle */ -# typedef uintptr_t PL_atomic_t; /* same a word */ -# typedef uintptr_t foreign_t; /* return type of foreign functions */ -# typedef wchar_t pl_wchar_t; /* Prolog wide character */ -# typedef foreign_t (*pl_function_t)(); /* foreign language functions */ - -atom_t = c_uint_p -functor_t = c_uint_p -module_t = c_void_p -predicate_t = c_void_p -record_t = c_void_p -term_t = c_uint_p -qid_t = c_uint_p -PL_fid_t = c_uint_p -fid_t = c_uint_p -control_t = c_void_p -PL_engine_t = c_void_p -PL_atomic_t = c_uint_p -foreign_t = c_uint_p -pl_wchar_t = c_wchar - -PL_initialise = _lib.PL_initialise -#PL_initialise.argtypes = [c_int, c_c?? - -PL_open_foreign_frame = _lib.PL_open_foreign_frame -PL_open_foreign_frame.restype = fid_t - -PL_new_term_ref = _lib.PL_new_term_ref -PL_new_term_ref.restype = term_t - -PL_new_term_refs = _lib.PL_new_term_refs -PL_new_term_refs.argtypes = [c_int] -PL_new_term_refs.restype = term_t - -PL_chars_to_term = _lib.PL_chars_to_term -PL_chars_to_term.argtypes = [c_char_p, term_t] -PL_chars_to_term.restype = c_int - -PL_call = _lib.PL_call -PL_call.argtypes = [term_t, module_t] -PL_call.restype = c_int - -PL_call_predicate = _lib.PL_call_predicate -PL_call_predicate.argtypes = [module_t, c_int, predicate_t, term_t] -PL_call_predicate.restype = fid_t - -PL_close_foreign_frame = _lib.PL_close_foreign_frame -PL_close_foreign_frame.argtypes = [fid_t] -PL_close_foreign_frame.restype = None - -PL_discard_foreign_frame = _lib.PL_discard_foreign_frame -PL_discard_foreign_frame.argtypes = [fid_t] -PL_discard_foreign_frame.restype = None - -PL_rewind_foreign_frame = _lib.PL_rewind_foreign_frame -PL_rewind_foreign_frame.argtypes = [fid_t] -PL_rewind_foreign_frame.restype = None - -PL_put_list_chars = _lib.PL_put_list_chars -PL_put_list_chars.argtypes = [term_t, c_char_p] -PL_put_list_chars.restype = c_int - -#PL_EXPORT(void) PL_register_atom(atom_t a); -PL_register_atom = _lib.PL_register_atom -PL_register_atom.argtypes = [atom_t] -PL_register_atom.restype = None - -#PL_EXPORT(void) PL_unregister_atom(atom_t a); -PL_unregister_atom = _lib.PL_unregister_atom -PL_unregister_atom.argtypes = [atom_t] -PL_unregister_atom.restype = None - -#PL_EXPORT(atom_t) PL_functor_name(functor_t f); -PL_functor_name = _lib.PL_functor_name -PL_functor_name.argtypes = [functor_t] -PL_functor_name.restype = atom_t - -#PL_EXPORT(int) PL_functor_arity(functor_t f); -PL_functor_arity = _lib.PL_functor_arity -PL_functor_arity.argtypes = [functor_t] -PL_functor_arity.restype = c_int - -# /* Get C-values from Prolog terms */ -#PL_EXPORT(int) PL_get_atom(term_t t, atom_t *a); -PL_get_atom = _lib.PL_get_atom -PL_get_atom.argtypes = [term_t, POINTER(atom_t)] -PL_get_atom.restype = c_int - -#PL_EXPORT(int) PL_get_bool(term_t t, int *value); -PL_get_bool = _lib.PL_get_bool -PL_get_bool.argtypes = [term_t, POINTER(c_int)] -PL_get_bool.restype = c_int - -#PL_EXPORT(int) PL_get_atom_chars(term_t t, char **a); -PL_get_atom_chars = _lib.PL_get_atom_chars # FIXME -PL_get_atom_chars.argtypes = [term_t, POINTER(c_char_p)] -PL_get_atom_chars.restype = c_int - -##define PL_get_string_chars(t, s, l) PL_get_string(t,s,l) -# /* PL_get_string() is depricated */ -#PL_EXPORT(int) PL_get_string(term_t t, char **s, size_t *len); -PL_get_string = _lib.PL_get_string -PL_get_string_chars = PL_get_string -#PL_get_string_chars.argtypes = [term_t, POINTER(c_char_p), c_int_p] - -#PL_EXPORT(int) PL_get_chars(term_t t, char **s, unsigned int flags); -PL_get_chars = _lib.PL_get_chars # FIXME: - -#PL_EXPORT(int) PL_get_list_chars(term_t l, char **s, -# unsigned int flags); -#PL_EXPORT(int) PL_get_atom_nchars(term_t t, size_t *len, char **a); -#PL_EXPORT(int) PL_get_list_nchars(term_t l, -# size_t *len, char **s, -# unsigned int flags); -#PL_EXPORT(int) PL_get_nchars(term_t t, -# size_t *len, char **s, -# unsigned int flags); -#PL_EXPORT(int) PL_get_integer(term_t t, int *i); -PL_get_integer = _lib.PL_get_integer -PL_get_integer.argtypes = [term_t, POINTER(c_int)] -PL_get_integer.restype = c_int - -#PL_EXPORT(int) PL_get_long(term_t t, long *i); -PL_get_long = _lib.PL_get_long -PL_get_long.argtypes = [term_t, POINTER(c_long)] -PL_get_long.restype = c_int - -#PL_EXPORT(int) PL_get_pointer(term_t t, void **ptr); -#PL_EXPORT(int) PL_get_float(term_t t, double *f); -PL_get_float = _lib.PL_get_float -PL_get_float.argtypes = [term_t, c_double_p] -PL_get_float.restype = c_int - -#PL_EXPORT(int) PL_get_functor(term_t t, functor_t *f); -PL_get_functor = _lib.PL_get_functor -PL_get_functor.argtypes = [term_t, POINTER(functor_t)] -PL_get_functor.restype = c_int - -#PL_EXPORT(int) PL_get_name_arity(term_t t, atom_t *name, int *arity); -PL_get_name_arity = _lib.PL_get_name_arity -PL_get_name_arity.argtypes = [term_t, POINTER(atom_t), POINTER(c_int)] -PL_get_name_arity.restype = c_int - -#PL_EXPORT(int) PL_get_module(term_t t, module_t *module); -#PL_EXPORT(int) PL_get_arg(int index, term_t t, term_t a); -PL_get_arg = _lib.PL_get_arg -PL_get_arg.argtypes = [c_int, term_t, term_t] -PL_get_arg.restype = c_int - -#PL_EXPORT(int) PL_get_list(term_t l, term_t h, term_t t); -#PL_EXPORT(int) PL_get_head(term_t l, term_t h); -PL_get_head = _lib.PL_get_head -PL_get_head.argtypes = [term_t, term_t] -PL_get_head.restype = c_int - -#PL_EXPORT(int) PL_get_tail(term_t l, term_t t); -PL_get_tail = _lib.PL_get_tail -PL_get_tail.argtypes = [term_t, term_t] -PL_get_tail.restype = c_int - -#PL_EXPORT(int) PL_get_nil(term_t l); -PL_get_nil = _lib.PL_get_nil -PL_get_nil.argtypes = [term_t] -PL_get_nil.restype = c_int - -#PL_EXPORT(int) PL_get_term_value(term_t t, term_value_t *v); -#PL_EXPORT(char *) PL_quote(int chr, const char *data); - -PL_put_atom_chars = _lib.PL_put_atom_chars -PL_put_atom_chars.argtypes = [term_t, c_char_p] -PL_put_atom_chars.restype = c_int - -PL_atom_chars = _lib.PL_atom_chars -PL_atom_chars.argtypes = [atom_t] -PL_atom_chars.restype = c_char_p - -PL_predicate = _lib.PL_predicate -PL_predicate.argtypes = [c_char_p, c_int, c_char_p] -PL_predicate.restype = predicate_t - -PL_pred = _lib.PL_pred -PL_pred.argtypes = [functor_t, module_t] -PL_pred.restype = predicate_t - -PL_open_query = _lib.PL_open_query -PL_open_query.argtypes = [module_t, c_int, predicate_t, term_t] -PL_open_query.restype = qid_t - -PL_next_solution = _lib.PL_next_solution -PL_next_solution.argtypes = [qid_t] -PL_next_solution.restype = c_int - -PL_copy_term_ref = _lib.PL_copy_term_ref -PL_copy_term_ref.argtypes = [term_t] -PL_copy_term_ref.restype = term_t - -PL_get_list = _lib.PL_get_list -PL_get_list.argtypes = [term_t, term_t, term_t] -PL_get_list.restype = c_int - -PL_get_chars = _lib.PL_get_chars # FIXME - -PL_close_query = _lib.PL_close_query -PL_close_query.argtypes = [qid_t] -PL_close_query.restype = None - -#void PL_cut_query(qid) -PL_cut_query = _lib.PL_cut_query -PL_cut_query.argtypes = [qid_t] -PL_cut_query.restype = None - -PL_halt = _lib.PL_halt -PL_halt.argtypes = [c_int] -PL_halt.restype = None - -# PL_EXPORT(int) PL_cleanup(int status); -PL_cleanup = _lib.PL_cleanup -PL_cleanup.restype = c_int - -PL_unify_integer = _lib.PL_unify_integer -PL_unify = _lib.PL_unify -PL_unify.restype = c_int - -#PL_EXPORT(int) PL_unify_arg(int index, term_t t, term_t a) WUNUSED; -PL_unify_arg = _lib.PL_unify_arg -PL_unify_arg.argtypes = [c_int, term_t, term_t] -PL_unify_arg.restype = c_int - -# Verify types - -PL_term_type = _lib.PL_term_type -PL_term_type.argtypes = [term_t] -PL_term_type.restype = c_int - -PL_is_variable = _lib.PL_is_variable -PL_is_variable.argtypes = [term_t] -PL_is_variable.restype = c_int - -PL_is_ground = _lib.PL_is_ground -PL_is_ground.argtypes = [term_t] -PL_is_ground.restype = c_int - -PL_is_atom = _lib.PL_is_atom -PL_is_atom.argtypes = [term_t] -PL_is_atom.restype = c_int - -PL_is_integer = _lib.PL_is_integer -PL_is_integer.argtypes = [term_t] -PL_is_integer.restype = c_int - -PL_is_string = _lib.PL_is_string -PL_is_string.argtypes = [term_t] -PL_is_string.restype = c_int - -PL_is_float = _lib.PL_is_float -PL_is_float.argtypes = [term_t] -PL_is_float.restype = c_int - -#PL_is_rational = _lib.PL_is_rational -#PL_is_rational.argtypes = [term_t] -#PL_is_rational.restype = c_int - -PL_is_compound = _lib.PL_is_compound -PL_is_compound.argtypes = [term_t] -PL_is_compound.restype = c_int - -PL_is_functor = _lib.PL_is_functor -PL_is_functor.argtypes = [term_t, functor_t] -PL_is_functor.restype = c_int - -PL_is_list = _lib.PL_is_list -PL_is_list.argtypes = [term_t] -PL_is_list.restype = c_int - -PL_is_atomic = _lib.PL_is_atomic -PL_is_atomic.argtypes = [term_t] -PL_is_atomic.restype = c_int - -PL_is_number = _lib.PL_is_number -PL_is_number.argtypes = [term_t] -PL_is_number.restype = c_int - -# /* Assign to term-references */ -#PL_EXPORT(void) PL_put_variable(term_t t); -PL_put_variable = _lib.PL_put_variable -PL_put_variable.argtypes = [term_t] -PL_put_variable.restype = None - -#PL_EXPORT(void) PL_put_atom(term_t t, atom_t a); -PL_put_atom = _lib.PL_put_atom -PL_put_atom.argtypes = [term_t, atom_t] -PL_put_atom.restype = None - -#PL_EXPORT(void) PL_put_atom_chars(term_t t, const char *chars); -#PL_EXPORT(void) PL_put_string_chars(term_t t, const char *chars); -#PL_EXPORT(void) PL_put_list_chars(term_t t, const char *chars); -#PL_EXPORT(void) PL_put_list_codes(term_t t, const char *chars); -#PL_EXPORT(void) PL_put_atom_nchars(term_t t, size_t l, const char *chars); -#PL_EXPORT(void) PL_put_string_nchars(term_t t, size_t len, const char *chars); -#PL_EXPORT(void) PL_put_list_nchars(term_t t, size_t l, const char *chars); -#PL_EXPORT(void) PL_put_list_ncodes(term_t t, size_t l, const char *chars); -#PL_EXPORT(void) PL_put_integer(term_t t, long i); -PL_put_integer = _lib.PL_put_integer -PL_put_integer.argtypes = [term_t, c_long] -PL_put_integer.restype = None - -#PL_EXPORT(void) PL_put_pointer(term_t t, void *ptr); -#PL_EXPORT(void) PL_put_float(term_t t, double f); -PL_put_float = _lib.PL_put_float -PL_put_float.argtypes = [term_t, c_double] -PL_put_float.restype = None - -#PL_EXPORT(void) PL_put_functor(term_t t, functor_t functor); -PL_put_functor = _lib.PL_put_functor -PL_put_functor.argtypes = [term_t, functor_t] -PL_put_functor.restype = None - -#PL_EXPORT(void) PL_put_list(term_t l); -PL_put_list = _lib.PL_put_list -PL_put_list.argtypes = [term_t] -PL_put_list.restype = None - -#PL_EXPORT(void) PL_put_nil(term_t l); -PL_put_nil = _lib.PL_put_nil -PL_put_nil.argtypes = [term_t] -PL_put_nil.restype = None - -#PL_EXPORT(void) PL_put_term(term_t t1, term_t t2); -PL_put_term = _lib.PL_put_term -PL_put_term.argtypes = [term_t, term_t] -PL_put_term.restype = None - -# /* construct a functor or list-cell */ -#PL_EXPORT(void) PL_cons_functor(term_t h, functor_t f, ...); -#class _PL_cons_functor(object): -PL_cons_functor = _lib.PL_cons_functor # FIXME: - -#PL_EXPORT(void) PL_cons_functor_v(term_t h, functor_t fd, term_t a0); -PL_cons_functor_v = _lib.PL_cons_functor_v -PL_cons_functor_v.argtypes = [term_t, functor_t, term_t] -PL_cons_functor_v.restype = None - -#PL_EXPORT(void) PL_cons_list(term_t l, term_t h, term_t t); -PL_cons_list = _lib.PL_cons_list -PL_cons_list.argtypes = [term_t, term_t, term_t] -PL_cons_list.restype = None - -# -# term_t PL_exception(qid_t qid) -PL_exception = _lib.PL_exception -PL_exception.argtypes = [qid_t] -PL_exception.restype = term_t - -PL_clear_exception = _lib.PL_clear_exception -PL_clear_exception.restype = None - -# -PL_register_foreign = _lib.PL_register_foreign - -# -#PL_EXPORT(atom_t) PL_new_atom(const char *s); -PL_new_atom = _lib.PL_new_atom -PL_new_atom.argtypes = [c_char_p] -PL_new_atom.restype = atom_t - -#PL_EXPORT(functor_t) PL_new_functor(atom_t f, int a); -PL_new_functor = _lib.PL_new_functor -PL_new_functor.argtypes = [atom_t, c_int] -PL_new_functor.restype = functor_t - - -# /******************************* -# * COMPARE * -# *******************************/ -# -#PL_EXPORT(int) PL_compare(term_t t1, term_t t2); -#PL_EXPORT(int) PL_same_compound(term_t t1, term_t t2); -PL_compare = _lib.PL_compare -PL_compare.argtypes = [term_t, term_t] -PL_compare.restype = c_int - -PL_same_compound = _lib.PL_same_compound -PL_same_compound.argtypes = [term_t, term_t] -PL_same_compound.restype = c_int - - -# /******************************* -# * RECORDED DATABASE * -# *******************************/ -# -#PL_EXPORT(record_t) PL_record(term_t term); -PL_record = _lib.PL_record -PL_record.argtypes = [term_t] -PL_record.restype = record_t - -#PL_EXPORT(void) PL_recorded(record_t record, term_t term); -PL_recorded = _lib.PL_recorded -PL_recorded.argtypes = [record_t, term_t] -PL_recorded.restype = None - -#PL_EXPORT(void) PL_erase(record_t record); -PL_erase = _lib.PL_erase -PL_erase.argtypes = [record_t] -PL_erase.restype = None - -# -#PL_EXPORT(char *) PL_record_external(term_t t, size_t *size); -#PL_EXPORT(int) PL_recorded_external(const char *rec, term_t term); -#PL_EXPORT(int) PL_erase_external(char *rec); - -PL_new_module = _lib.PL_new_module -PL_new_module.argtypes = [atom_t] -PL_new_module.restype = module_t - -intptr_t = c_long -ssize_t = intptr_t -wint_t = c_uint - -#typedef struct -#{ -# int __count; -# union -# { -# wint_t __wch; -# char __wchb[4]; -# } __value; /* Value so far. */ -#} __mbstate_t; - -class _mbstate_t_value(Union): - _fields_ = [("__wch",wint_t), - ("__wchb",c_char*4)] - -class mbstate_t(Structure): - _fields_ = [("__count",c_int), - ("__value",_mbstate_t_value)] - -# stream related funcs -Sread_function = CFUNCTYPE(ssize_t, c_void_p, c_char_p, c_size_t) -Swrite_function = CFUNCTYPE(ssize_t, c_void_p, c_char_p, c_size_t) -Sseek_function = CFUNCTYPE(c_long, c_void_p, c_long, c_int) -Sseek64_function = CFUNCTYPE(c_int64, c_void_p, c_int64, c_int) -Sclose_function = CFUNCTYPE(c_int, c_void_p) -Scontrol_function = CFUNCTYPE(c_int, c_void_p, c_int, c_void_p) - -# IOLOCK -IOLOCK = c_void_p - -# IOFUNCTIONS -class IOFUNCTIONS(Structure): - _fields_ = [("read",Sread_function), - ("write",Swrite_function), - ("seek",Sseek_function), - ("close",Sclose_function), - ("seek64",Sseek64_function), - ("reserved",intptr_t*2)] - -# IOENC -ENC_UNKNOWN,ENC_OCTET,ENC_ASCII,ENC_ISO_LATIN_1,ENC_ANSI,ENC_UTF8,ENC_UNICODE_BE,ENC_UNICODE_LE,ENC_WCHAR = list(range(9)) -IOENC = c_int - -# IOPOS -class IOPOS(Structure): - _fields_ = [("byteno",c_int64), - ("charno",c_int64), - ("lineno",c_int), - ("linepos",c_int), - ("reserved", intptr_t*2)] - -# IOSTREAM -class IOSTREAM(Structure): - _fields_ = [("bufp",c_char_p), - ("limitp",c_char_p), - ("buffer",c_char_p), - ("unbuffer",c_char_p), - ("lastc",c_int), - ("magic",c_int), - ("bufsize",c_int), - ("flags",c_int), - ("posbuf",IOPOS), - ("position",POINTER(IOPOS)), - ("handle",c_void_p), - ("functions",IOFUNCTIONS), - ("locks",c_int), - ("mutex",IOLOCK), - ("closure_hook",CFUNCTYPE(None, c_void_p)), - ("closure",c_void_p), - ("timeout",c_int), - ("message",c_char_p), - ("encoding",IOENC)] -IOSTREAM._fields_.extend([("tee",IOSTREAM), - ("mbstate",POINTER(mbstate_t)), - ("reserved",intptr_t*6)]) - - - -#PL_EXPORT(IOSTREAM *) Sopen_string(IOSTREAM *s, char *buf, size_t sz, const char *m); -Sopen_string = _lib.Sopen_string -Sopen_string.argtypes = [POINTER(IOSTREAM), c_char_p, c_size_t, c_char_p] -Sopen_string.restype = POINTER(IOSTREAM) - -#PL_EXPORT(int) Sclose(IOSTREAM *s); -Sclose = _lib.Sclose -Sclose.argtypes = [POINTER(IOSTREAM)] - - -#PL_EXPORT(int) PL_unify_stream(term_t t, IOSTREAM *s); -PL_unify_stream = _lib.PL_unify_stream -PL_unify_stream.argtypes = [term_t, POINTER(IOSTREAM)] - -PL_unify_atom_chars = _lib.PL_unify_atom_chars -PL_unify_atom_chars.argtypes = [term_t, c_char_p] -PL_unify_atom_chars.restype = c_int diff --git a/prolog/engine.py b/prolog/engine.py index 597ebc4..6627b9a 100644 --- a/prolog/engine.py +++ b/prolog/engine.py @@ -1,339 +1,63 @@ #!/usr/bin/python3 -from prolog.core import * -import prolog.util +import http.client +import json +import urllib -class Atom(object): - __slots__ = 'ref' - - def __init__(self, val=None, ref=None): - if ref is not None: - self.ref = ref - return - self.ref = PL_new_atom(bytes(val, encoding=encoding)) - -class Term(object): - __slots__ = 'ref' - - # Initialize from term reference [ref] if given, otherwise construct a new - # term from [val] and possibly [args]. - def __init__(self, val=None, args=None, ref=None): - if ref is not None: - self.ref = ref - return - self.ref = PL_new_term_ref() - if isinstance(val, str): - if args is not None: - # Explicitly constructed compound term with name [val] and arguments [args]. - name = PL_new_atom(bytes(val, encoding=encoding)) - PL_cons_functor_v(self.ref, PL_new_functor(name, len(args)), Termv(args).ref) - else: - # Parse term from [val]. - if not PL_chars_to_term(bytes(val, encoding=encoding), self.ref): - raise ValueError('invalid compound term') - elif isinstance(val, int): - PL_put_integer(self.ref, val) - elif isinstance(val, float): - PL_put_float(self.ref, val) - elif isinstance(val, list): - PL_put_nil(self.ref) - for t in val: - PL_cons_list(self.ref, t.ref, self.ref) - elif isinstance(val, Atom): - PL_put_atom(self.ref, val.ref) - - def __iter__(self): - if not PL_is_list(self.ref): - raise TypeError('term is not a list') - ref = self.ref - while True: - head, tail = Term(), Term() - if not PL_get_list(ref, head.ref, tail.ref): - break - yield head - ref = tail.ref - - def __str__(self): - ptr = c_char_p() - if PL_get_chars(self.ref, byref(ptr), CVT_WRITE|BUF_RING): - return str(ptr.value, encoding=encoding) - -class Termv(object): - __slots__ = 'ref' - - def __init__(self, terms): - self.ref = PL_new_term_refs(len(terms)) - for i, term in enumerate(terms): - PL_put_term(self.ref+i, term.ref) - -class Problem(object): - def __init__(self, name, solution, facts, tests): - self.name = name - self.solution = solution - self.facts = facts - self.tests = {t: None for t in tests} - self.answers = {} +address, port = 'localhost', 3030 class PrologEngine(object): - def __init__(self): - # Initialize the swipl library. - args = ['./', '-q', '--nosignals'] - if SWI_HOME_DIR is not None: - args.append('--home={0}'.format(SWI_HOME_DIR)) - s_plargs = len(args) - plargs = (c_char_p*s_plargs)() - for i in range(s_plargs): - plargs[i] = bytes(args[i], encoding) - if not PL_initialise(s_plargs, plargs): - raise EnvironmentError('Could not initialize Prolog environment.' - 'PL_initialise returned {0}'.format(result)) - - # Construct some predicates. - self.p = { - 'abolish/1': PL_predicate(b'abolish', 1, None), - 'add_import_module/3': PL_predicate(b'add_import_module', 3, None), - 'arg/3': PL_predicate(b'arg', 3, None), - 'assertz/1': PL_predicate(b'assertz', 1, None), - 'call_with_time_limit/2': PL_predicate(b'call_with_time_limit', 2, None), - 'compile_predicates/1': PL_predicate(b'compile_predicates', 1, None), - 'consult/1': PL_predicate(b'consult', 1, None), - 'functor/3': PL_predicate(b'functor', 3, None), - 'message_to_string/2': PL_predicate(b'message_to_string', 2, None), - 'read_term_from_atom/3': PL_predicate(b'read_term_from_atom', 3, None), - 'safe_goal/1': PL_predicate(b'safe_goal', 1, None), - 'set_prolog_flag/2': PL_predicate(b'set_prolog_flag', 2, None), - 'set_prolog_stack/2': PL_predicate(b'set_prolog_stack', 2, None), - 'use_module/1': PL_predicate(b'use_module', 1, None) - } - self.err_flags = PL_Q_NODEBUG|PL_Q_CATCH_EXCEPTION - - # Load the sandbox and compatibility library. - self.call('consult/1', [Term(Atom('prolog/lib.pl'))]) - - # Load the time module (for call_with_time_limit) then disable autoload. - self.call('use_module/1', [Term('library(random)')]) - self.call('use_module/1', [Term('library(time)')]) - self.call('set_prolog_flag/2', [Term('autoload'), Term('false')]) - - # Increase memory limits. - self.call('set_prolog_stack/2', [Term('global'), Term('limit(2*10**9)')]) - self.call('set_prolog_stack/2', [Term('local'), Term('limit(2*10**9)')]) - - # Discard messages from the swipl library. - self.call('assertz/1', [Term('message_hook(_, _, _)')]) - - # Problem data loaded with load_problem. - self.problems = {} - - # The set of already loaded facts. - self.facts = set() - - # Load the [solution] for problem [pid] called [name] and find answers to - # [tests]. Also load [facts] in the main module, and import modules for - # problems in [depends] into this problem's module. - def load_problem(self, pid, name, solution, depends, facts, tests): - self.problems[pid] = Problem(name, solution, facts, tests) - - # Load the solution in 'solution<pid>' module. - mod_problem = 'problem{}'.format(pid) - - fid = PL_open_foreign_frame() - predicates = self.load(solution, mod_problem) - if facts and facts not in self.facts: - predicates |= self.load(facts) - self.facts.add(facts) - self.call('compile_predicates/1', [Term([Term(p) for p in predicates])]) - - # Import solutions for dependency predicates. - for i in depends: - mod_dependency = 'problem{}'.format(i) - self.call('add_import_module/3', [Term(mod_problem), Term(mod_dependency), Term('end')]) - - # Find the correct test answers. - for query in tests: - result = self.query(query, mod_problem) - if result is None or len(result) < 1 or 'X' not in result[0]: - raise Exception('Error finding correct answer to query "{}"'.format(query)) - self.problems[pid].tests[query] = result[0]['X'] - PL_discard_foreign_frame(fid) - - # Import the correct solution for problem [pid] into module for user [uid]. - def mark_solved(self, uid, pid): - mod_user = 'user{}'.format(uid) - mod_problem = 'problem{}'.format(pid) - - fid = PL_open_foreign_frame() - result = self.call('add_import_module/3', [Term(mod_user), Term(mod_problem), Term('end')]) - PL_discard_foreign_frame(fid) - return result - - # Get up to [n] solutions to query [q]. If there are no solutions, return - # an empty list. Raise an exception on error (either from self.call, or due - # to malformed/unsafe query or a timeout). - def query(self, q, module=None, n=1): - if module is not None: - q = '{}:({})'.format(module, q) - - fid = PL_open_foreign_frame() - qid = None - try: - # Parse the query and store variable names. - goal = Term() - var_names = Term() - options = Term([Term('variable_names', [var_names])]) - if not self.call('read_term_from_atom/3', [Term(Atom(q)), goal, options]): - raise Exception('Warning: Could not read term from {}\n'.format(q)) - - # Check if goal is safe with currently loaded rules. - if not self.call('safe_goal/1', [goal]): - raise Exception('Warning: Unsafe goal: {}\n'.format(goal)) - - solutions = Term() - goal_aux = Term('findnsols', [Term(n), goal, goal, solutions]) - qid = PL_open_query(None, self.err_flags, self.p['call_with_time_limit/2'], - Termv([Term(0.01), goal_aux]).ref) - - result = [] - if PL_next_solution(qid): - solutions = list(solutions) - fid_solution = PL_open_foreign_frame() - for solution in solutions: - PL_unify(goal.ref, solution.ref) - variables = {} - for var in var_names: - name, value = Term(), Term() - PL_get_arg(1, var.ref, name.ref) - PL_get_arg(2, var.ref, value.ref) - variables[str(name)] = str(value) - result.append(variables) - PL_rewind_foreign_frame(fid_solution) - PL_discard_foreign_frame(fid_solution) - else: - # Check for exceptions. - error_msg = self.error(qid) - if error_msg: - raise Exception(error_msg) - finally: - if qid: - PL_close_query(qid) - PL_discard_foreign_frame(fid) - - return result - - # Test whether [code] gives the same answer to [query] as the correct - # solution for problem [pid]. The solution should be loaded beforehand. - def test(self, uid, pid, code): - mod_user = 'user{}'.format(uid) - - fid = PL_open_foreign_frame() - correct = True - predicates = set() - try: - self.load(code, mod_user, predicates) - for query, answer in sorted(self.problems[pid].tests.items()): - result = self.query(query, mod_user, n=1) - if len(result) != 1 or result[0]['X'] != answer: - correct = False - break - - # If a correct solution was found, see if another (incorrect) - # solution is found in the first 10 answers. - try: - result = self.query(query, mod_user, n=10) - unique = set([r['X'] for r in result]) - if len(unique) != 1: - correct = False - break - except Exception as ex: - # Only a timeout exception can occur here; in this case, we - # consider [code] correct. - pass - except Exception as ex: - correct = False - - self.unload(predicates) - PL_discard_foreign_frame(fid) - - return correct - - # Call the Prolog predicate [name]. Raise an exception on error. Since this - # creates a Termv object, it should be called within an open foreign frame. - def call(self, name, args): - qid = PL_open_query(None, self.err_flags, self.p[name], Termv(args).ref) - try: - if not PL_next_solution(qid): - error_msg = self.error(qid) - if error_msg: - raise Exception(error_msg) - return False - finally: - PL_cut_query(qid) - return True - - # Load rules from [program] into [module] and return the corresponding - # predicate names. Since this function might not return due to exception, - # the [predicates] argument can be passed where the names will be stored. - def load(self, program, module=None, predicates=None): - if predicates is None: - predicates = set() - for rule in prolog.util.split(program): - name = self.predicate_indicator(rule) - if module: - rule = '{}:({})'.format(module, rule) - name = '{}:{}'.format(module, name) - self.call('assertz/1', [Term(rule)]) - predicates.add(name) - return predicates - - # Unload and remove the "dynamic" property for all rules implementing - # [predicates]. - def unload(self, predicates): - for predicate in predicates: - self.call('abolish/1', [Term(predicate)]) - - # Return a description of the last exception, or None if no error occurred. - def error(self, qid): - error_ref = PL_exception(qid) - if not error_ref: - return None - PL_clear_exception() - - # Get the Prolog error message. - fid = PL_open_foreign_frame() - msg = Term() - if PL_call_predicate(None, self.err_flags, self.p['message_to_string/2'], - Termv([Term(ref=error_ref), msg]).ref): - error_str = str(msg) + def __init__(self, address=address, port=port, code=''): + self.id = None + self.conn = http.client.HTTPConnection(address, port, timeout=10) + + hdrs = {'Content-Type': 'application/json; charset=utf-8'} + opts = json.dumps({'destroy': False, 'src_text': code, 'format': 'json'}) + self.conn.request('POST', '/pengine/create', body=opts, headers=hdrs) + + response = self.conn.getresponse() + data = response.read() + reply = json.loads(str(data, encoding='utf-8')) + if reply['event'] == 'create': + self.id = reply['id'] else: - error_str = 'Unknown error' - PL_discard_foreign_frame(fid) - - return error_str - - # Return the main functor defined by [clause], e.g. dup/2. - def predicate_indicator(self, clause): - # Return the main functor for [term]. - def main_functor(term): - name = Term() - arity = Term() - self.call('functor/3', [term, name, arity]) - return "'{}'/{}".format(name, arity) - - fid = PL_open_foreign_frame() - clause = Term(clause) - functor = main_functor(clause) - # Check whether [clause] is a rule or a fact. - if functor == "':-'/2": - # [clause] is a rule, return the main functor for the head. - head = Term() - self.call('arg/3', [Term(1), clause, head]) - functor = main_functor(head) - PL_discard_foreign_frame(fid) - return functor + raise Exception('could not create engine: ' + reply['code']) + + def send(self, event, fmt='json'): + params = urllib.parse.urlencode({ + 'id': self.id, + 'event': event, + 'format': fmt}) + self.conn.request('GET', '/pengine/send?' + params) + + response = self.conn.getresponse() + data = str(response.read(), encoding='utf-8') + if response.status == http.client.OK: + return json.loads(data) if fmt == 'json' else data + return None + + def ask(self, query, template='', fmt='json'): + event = 'ask(({}),[template({})])'.format(query, template) + reply = self.send(event, fmt=fmt) + return reply + + def next(self, n=1): + event = 'next({})'.format(n) + reply = self.send(event) + return reply + + def stop(self): + return self.send('stop') + + def destroy(self): + reply = self.send('destroy') + self.id = None + self.conn.close() + self.conn = None # Basic sanity check. if __name__ == '__main__': - engine = PrologEngine() - engine.load_solution(0, 'a(2). a(2). a(3). ') - result = engine.test(0, 0, 'a(2). ', ['a(X)', 'a(Y), Y=X']) - print('{}: {}'.format(i, result)) + engine = PrologEngine(code='dup([],[]). dup([H|T],[H,H|TT]) :- dup(T,TT).') + print('engine id is ' + engine.id) + print(engine.ask("run_tests({},'{}',Result)".format('dup/2', engine.id))) + engine.destroy() diff --git a/prolog/lib.pl b/prolog/lib.pl deleted file mode 100644 index 99dadf1..0000000 --- a/prolog/lib.pl +++ /dev/null @@ -1,462 +0,0 @@ -/* Part of SWI-Prolog - - Author: Jan Wielemaker - E-mail: J.Wielemaker@cs.vu.nl - WWW: http://www.swi-prolog.org - Copyright (C): 2009-2013, VU University Amsterdam - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - As a special exception, if you link this library with other files, - compiled with a Free Software compiler, to produce an executable, this - library does not by itself cause the resulting executable to be covered - by the GNU General Public License. This exception does not however - invalidate any other reasons why the executable file might be covered by - the GNU General Public License. -*/ - -:- module(sandbox, - [ safe_goal/1 % :Goal - ]). -:- use_module(library(assoc)). -:- use_module(library(lists)). -:- use_module(library(debug)). -:- use_module(library(apply_macros), [expand_phrase/2]). - -:- multifile - safe_primitive/1, % Goal - safe_meta/2. % Goal, Calls - -% :- debug(sandbox). - -/** <module> Sandboxed Prolog code - -Prolog is a full-featured Turing complete programming language in which -it is easy to write programs that can harm your computer. On the other -hand, Prolog is a logic based _query language_ which can be exploited to -query data interactively from, e.g., the web. This library provides -safe_goal/1, which determines whether it is safe to call its argument. - -@tbd Handling of ^ and // meta predicates -*/ - - -:- meta_predicate - safe_goal(0). - -%% safe_goal(:Goal) is det. -% -% True if calling Goal provides no security risc. This implies -% that: -% -% - The call-graph can be fully expanded. Full expansion *stops* -% if a meta-goal is found for which we cannot determine enough -% details to know which predicate will be called. -% -% - All predicates referenced from the fully expanded are -% whitelisted by the predicate safe_primitive/1 and safe_meta/2. -% -% @error instantiation_error if the analysis encounters a term in -% a callable position that is insufficiently instantiated -% to determine the predicate called. -% @error permission_error(call, sandboxed, Goal) if Goal is in -% the call-tree and not white-listed. - -safe_goal(M:Goal) :- - empty_assoc(Safe0), - safe(Goal, M, [], Safe0, _). - - -%% safe(+Goal, +Module, +Parents, +Safe0, -Safe) is semidet. -% -% Is true if Goal can only call safe code. - -safe(V, _, Parents, _, _) :- - var(V), !, - throw(error(instantiation_error, sandbox(V, Parents))). -safe(M:G, _, Parent, Safe0, Safe) :- !, - safe(G, M, Parent, Safe0, Safe). -safe(G, _, Parents, _, _) :- - debugging(sandbox(show)), - length(Parents, Level), - debug(sandbox(show), '[~D] SAFE ~q?', [Level, G]), - fail. -safe(G, _, _, Safe, Safe) :- - safe_primitive(G), - predicate_property(G, iso), !. -safe(G, M, _, Safe, Safe) :- - ( predicate_property(M:G, imported_from(M2)) - -> true - ; M2 = M - ), - safe_primitive(M2:G), !. -safe(G, M, Parents, Safe0, Safe) :- - safe_meta(G, Called), !, - safe_list(Called, M, Parents, Safe0, Safe). -safe(G, M, Parents, Safe0, Safe) :- - goal_id(M:G, Id, Gen), - ( get_assoc(Id, Safe0, _) - -> Safe = Safe0 - ; put_assoc(Id, Safe0, true, Safe1), - safe_clauses(Gen, M, [Id|Parents], Safe1, Safe) - ). - -safe_clauses(G, M, Parents, Safe0, Safe) :- - predicate_property(M:G, interpreted), !, -% \+ predicate_property(M:G, meta_predicate(_)), !, - def_module(M:G, MD:QG), - findall(Body, clause(MD:QG, Body), Bodies), - safe_list(Bodies, MD, Parents, Safe0, Safe). -safe_clauses(_, _M, [G|Parents], _, _) :- - throw(error(permission_error(call, sandboxed, G), - sandbox(G, Parents))). - -safe_list([], _, _, Safe, Safe). -safe_list([H|T], M, Parents, Safe0, Safe) :- - copy_term(H, H1), - safe(H1, M, Parents, Safe0, Safe1), - safe_list(T, M, Parents, Safe1, Safe). - - -def_module(M:G, MD:QG) :- - predicate_property(M:G, imported_from(MD)), !, - meta_qualify(MD:G, M, QG). -def_module(M:G, M:QG) :- - meta_qualify(M:G, M, QG). - -%% meta_qualify(:G, +M, -QG) is det. -% -% Perform meta-qualification of the goal-argument - -meta_qualify(MD:G, M, QG) :- - predicate_property(MD:G, meta_predicate(Head)), !, - G =.. [Name|Args], - Head =.. [_|Q], - qualify_args(Q, M, Args, QArgs), - QG =.. [Name|QArgs]. -meta_qualify(_:G, _, G). - -qualify_args([], _, [], []). -qualify_args([H|T], M, [A|AT], [Q|QT]) :- - qualify_arg(H, M, A, Q), - qualify_args(T, M, AT, QT). - -qualify_arg(S, M, A, Q) :- - q_arg(S), !, - qualify(A, M, Q). -qualify_arg(_, _, A, A). - -q_arg(I) :- integer(I), !. -q_arg(:). - -qualify(A, M, MZ:Q) :- - strip_module(M:A, MZ, Q). - -%% goal_id(:Goal, -Id, -Gen) is nondet. -% -% Generate an identifier for the goal proven to be safe. We -% first try to prove the most general form of the goal. If -% this fails, we try to prove more specific versions. -% -% @tbd Do step-by-step generalisation instead of the current -% two levels (most general and most specific). -% - -goal_id(M:Goal, M:Id, Gen) :- !, - goal_id(Goal, Id, Gen). -goal_id(Term, _, _) :- - \+ callable(Term), !, fail. -goal_id(Term, Name/Arity, Gen) :- % most general form - functor(Term, Name, Arity), - functor(Gen, Name, Arity). -goal_id(Term, Skolem, Term) :- % most specific form - copy_term(Term, Skolem), - numbervars(Skolem, 0, _). - -%% safe_primitive(?Goal) is nondet. -% -% True if Goal is safe to call (i.e., cannot access dangerous -% system-resources and cannot upset other parts of the Prolog -% process). There are two types of facts. ISO built-ins are -% declared without a module prefix. This is safe because it is not -% allowed to (re-)define these primitives (i.e., give them an -% unsafe implementation) and the way around -% (redefine_system_predicate/1) is unsafe. The other group are -% module-qualified and only match if the system infers that the -% predicate is (or will be) imported from the given module. - -% First, all ISO system predicates that are considered safe - -safe_primitive(true). -safe_primitive(fail). -safe_primitive(repeat). -safe_primitive(!). - % types -safe_primitive(var(_)). -safe_primitive(nonvar(_)). -safe_primitive(integer(_)). -safe_primitive(float(_)). -safe_primitive(number(_)). -safe_primitive(atom(_)). -safe_primitive(compound(_)). -safe_primitive(ground(_)). - % ordering -safe_primitive(@>(_,_)). -safe_primitive(@>=(_,_)). -safe_primitive(==(_,_)). -safe_primitive(@<(_,_)). -safe_primitive(@=<(_,_)). -safe_primitive(compare(_,_,_)). -safe_primitive(sort(_,_)). -safe_primitive(keysort(_,_)). - % unification and equivalence -safe_primitive(=(_,_)). -safe_primitive(\=(_,_)). -safe_primitive(\==(_,_)). - % arithmetic -safe_primitive(is(_,_)). -safe_primitive(>(_,_)). -safe_primitive(>=(_,_)). -safe_primitive(=:=(_,_)). -safe_primitive(=\=(_,_)). -safe_primitive(=<(_,_)). -safe_primitive(<(_,_)). - % term-handling -safe_primitive(arg(_,_,_)). -safe_primitive(system:setarg(_,_,_)). -safe_primitive(functor(_,_,_)). -safe_primitive(_ =.. _). -safe_primitive(copy_term(_,_)). -safe_primitive(numbervars(_,_,_)). - % atoms -safe_primitive(atom_concat(_,_,_)). -safe_primitive(atom_chars(_, _)). - % Lists -safe_primitive(length(_,_)). - % exceptions -safe_primitive(throw(_)). - % misc -safe_primitive(current_prolog_flag(_,_)). - -safe_primitive(clause(_,_)). -safe_primitive(asserta(X)) :- safe_assert(X). -safe_primitive(assertz(X)) :- safe_assert(X). -safe_primitive(retract(X)) :- safe_assert(X). -safe_primitive(retractall(X)) :- safe_assert(X). - -% The non-ISO system predicates. These can be redefined, so we must -% be careful to ensure the system ones are used. - -safe_primitive(system:false). -safe_primitive(system:cyclic_term(_)). -safe_primitive(system:msort(_,_)). -safe_primitive(system:between(_,_,_)). -safe_primitive(system:succ(_,_)). -safe_primitive(system:plus(_,_,_)). -safe_primitive(system:term_variables(_,_)). -safe_primitive(system:atom_to_term(_,_,_)). -safe_primitive(system:term_to_atom(_,_)). -safe_primitive(system:atomic_list_concat(_,_,_)). -safe_primitive(system:atomic_list_concat(_,_)). -safe_primitive(system:downcase_atom(_,_)). -safe_primitive(system:upcase_atom(_,_)). -safe_primitive(system:is_list(_)). -safe_primitive(system:memberchk(_,_)). -safe_primitive(system:'$skip_list'(_,_,_)). - % attributes -safe_primitive(system:get_attr(_,_,_)). -safe_primitive(system:del_attr(_,_)). - % globals -safe_primitive(system:b_getval(_,_)). -safe_primitive(system:b_setval(_,_)). -safe_primitive(system:nb_current(_,_)). -safe_primitive(system:assert(X)) :- - safe_assert(X). - -% use_module/1. We only allow for .pl files that are loaded from -% relative paths that do not contain /../ - -safe_primitive(system:use_module(Spec)) :- - ground(Spec), - ( atom(Spec) - -> Path = Spec - ; Spec =.. [_Alias, Segments], - phrase(segments_to_path(Segments), List), - atomic_list_concat(List, Path) - ), - \+ is_absolute_file_name(Path), - \+ sub_atom(Path, _, _, _, '/../'), - absolute_file_name(Spec, AbsFile, - [ access(read), - file_type(prolog), - file_errors(fail) - ]), - file_name_extension(_, Ext, AbsFile), - save_extension(Ext). - -% Other library predicates. - - % rdf -safe_primitive(rdf_db:rdf(_,_,_)). -safe_primitive(rdf_db:rdf(_,_,_,_)). - % http -safe_primitive(http_session:http_session_data(_)). -safe_primitive(http_session:http_session_id(_)). - % random -safe_primitive(random:random(_)). -safe_primitive(random:random(_,_,_)). -safe_primitive(random:random_between(_,_,_)). - % porter -safe_primitive(porter_stem:porter_stem(_,_)). -safe_primitive(porter_stem:unaccent_atom(_,_)). -safe_primitive(porter_stem:tokenize_atom(_,_)). -safe_primitive(porter_stem:atom_to_stem_list(_,_)). - -% support predicates for safe_primitive, validating the safety of -% arguments to certain goals. - -segments_to_path(A/B) --> !, - segments_to_path(A), - [/], - segments_to_path(B). -segments_to_path(X) --> - [X]. - -save_extension(pl). - -%% safe_assert(+Term) is semidet. -% -% True if assert(Term) is safe, which means it asserts in the -% current module. Cross-module asserts are considered unsafe. We -% only allow for adding facts. In theory, we could also allow for -% rules if we prove the safety of the body. - -safe_assert(C) :- cyclic_term(C), !, fail. -safe_assert(X) :- var(X), !, fail. -safe_assert(_Head:-_Body) :- !, fail. -safe_assert(_:_) :- !, fail. -safe_assert(_). - -%% safe_meta(+Goal, -Called) is semidet. -% -% True if Goal is a meta-predicate that is considered safe iff all -% elements in Called are safe. - -safe_meta(put_attr(_,M,A), [M:attr_unify_hook(A, _)]) :- - atom(M), !. -safe_meta(Phrase, [Goal]) :- - expand_phrase(Phrase, Goal), !. -safe_meta(Goal, Called) :- - generic_goal(Goal, Gen), - safe_meta(Gen), - findall(C, called(Gen, Goal, C), Called). - -called(Gen, Goal, Called) :- - arg(I, Gen, Spec), - integer(Spec), - arg(I, Goal, Called0), - extend(Spec, Called0, Called). - -generic_goal(G, Gen) :- - functor(G, Name, Arity), - functor(Gen, Name, Arity). - -extend(0, G, G) :- !. -extend(I, G0, G) :- - G0 =.. List, - length(Extra, I), - append(List, Extra, All), - G =.. All. - -safe_meta((0,0)). -safe_meta((0;0)). -safe_meta((0->0)). -safe_meta(forall(0,0)). -safe_meta(catch(0,_,0)). -safe_meta(findall(_,0,_)). -safe_meta(findall(_,0,_,_)). -safe_meta(setof(_,0,_)). % TBD -safe_meta(bagof(_,0,_)). -safe_meta(^(_,0)). -safe_meta(\+(0)). -safe_meta(maplist(1, _)). -safe_meta(maplist(2, _, _)). -safe_meta(maplist(3, _, _, _)). -safe_meta(call(0)). -safe_meta(call(1, _)). -safe_meta(call(2, _, _)). -safe_meta(call(3, _, _, _)). -safe_meta(call(4, _, _, _, _)). -safe_meta(call(5, _, _, _, _, _)). - - - /******************************* - * SAFE COMPILATION HOOKS * - *******************************/ - -:- multifile - prolog:sandbox_allowed_directive/1, - prolog:sandbox_allowed_expansion/1. - -%% prolog:sandbox_allowed_directive(:G) is det. -% -% Throws an exception if G is not considered a safe directive. - -prolog:sandbox_allowed_directive(M:PredAttr) :- - safe_directive(PredAttr), - ( prolog_load_context(module, M) - -> PredAttr =.. [Attr, Preds], - safe_pattr(Preds, Attr) - ; permission_error(directive, sandboxed, (:- M:PredAttr)) - ). -prolog:sandbox_allowed_directive(G) :- - safe_goal(G). - -safe_directive(dynamic(_)). -safe_directive(thread_local(_)). -safe_directive(discontiguous(_)). -safe_directive(public(_)). - -safe_pattr(Var, _) :- - var(Var), !, - instantiation_error(Var). -safe_pattr((A,B), Attr) :- !, - safe_pattr(A, Attr), - safe_pattr(B, Attr). -safe_pattr(M:G, Attr) :- !, - ( atom(M), - prolog_load_context(module, M) - -> true - ; Goal =.. [Attr,M:G], - permission_error(directive, sandboxed, (:- Goal)) - ). -safe_pattr(_, _). - - -%% prolog:sandbox_allowed_expansion(:G) is det. -% -% Throws an exception if G is not considered a safe expansion -% goal. This deals with call-backs from the compiler for -% -% - goal_expansion/2 -% - term_expansion/2 -% - Quasi quotations. - -prolog:sandbox_allowed_expansion(G) :- - safe_goal(G). - -% tuProlog compatibility -system:quicksort(L, <, S) :- msort(L, S). -system:quicksort(L, @<, S) :- msort(L, S). |