diff options
author | Timotej Lazar <timotej.lazar@araneo.org> | 2014-12-31 16:44:46 +0100 |
---|---|---|
committer | Aleš Smodiš <aless@guru.si> | 2015-08-11 14:26:01 +0200 |
commit | 79527522a5c53e57fd15589d7cc12946a8372afb (patch) | |
tree | 77cdd0a118740c6c84ebd0a9212a5d09158f85be /prolog | |
parent | 164cf022747b06d1f9058f4ff3729098e4412310 (diff) |
Delegate testing to a Prolog server
... which obsoletes *a lot* of effort in making the testing procedure
more robust in the past two years. Oh well. It seems to be the sanest
way of coping with more than one simultaneous user (who could have
predicted this use case?). The new way involves a PEngine server, and it
seems to work quite well. Remember Knuth: premature optimization (as in
ignoring possible solutions because they _might_ be to slow) is stupid.
TODO:
- library loading (again)
- use of previous solution (again)
- fix issues when converting non-ground terms to json
Side note, constructivism works: in the past few days I have reached a
much better but fundamentally ineffable intuition about Prolog, more so
than in the past two years teaching it. So, fuck ITS and rather fix the
schools by giving students something meaningful to do. Sigh.
Diffstat (limited to 'prolog')
-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). |