CLIPS Python bindings¶
- Release:
1.0.4
- Date:
May 26, 2024
Python CFFI bindings for the ‘C’ Language Integrated Production System (CLIPS) 6.41
Design principles¶
The clipspy bindings aim to be a “pythonic” thin layer built on top of the CLIPS native C APIs. Most of the functions and the methods directly resolve to the CLIPS functions documented in the Advanced Programming Guide.
Python standard paradigms are preferred such as property getters and setters, generators and magic methods.
Data types¶
The mapping between CLIPS and Python types is as follows.
CLIPS |
Python |
---|---|
INTEGER |
int |
FLOAT |
float |
STRING |
str |
SYMBOL |
Symbol * |
MULTIFIELD |
list |
FACT_ADDRESS |
Fact ** |
INSTANCE_NAME |
InstanceName * |
INSTANCE_ADDRESS |
Instance |
EXTERNAL_ADDRESS |
ffi.CData |
Python native types returned by functions defined within an Environment are mapped to the following CLIPS symbols.
CLIPS |
Python |
---|---|
nil |
None |
TRUE |
True |
FALSE |
False |
* The Python Symbol and InstanceName objects are interned string.
** ImpliedFact and TemplateFact are Fact subclasses.
Basic Data Abstractions¶
Facts¶
A fact is a list of atomic values that are either referenced positionally (ordered or implied facts) or by name (unordered or template facts).
Ordered Facts¶
Ordered or implied facts represent information as a list of elements. As the order of the data is what matters, implied facts do not have explicit templates. Ordered facts are pretty limited in terms of supported features.
import clips
env = clips.Environment()
# Ordered facts can only be asserted as strings
fact = env.assert_string('(ordered-fact 1 2 3)')
# Ordered facts data can be accessed as list elements
assert fact[0] == 1
assert list(fact) == [1, 2, 3]
Template Facts¶
Template or unordered facts represent data similarly to Python dictionaries. Unordered facts require a template to be defined. Templates are formal descriptions of the data represented by the fact.
Template facts are more flexible as they support features such as constraints for the data types, default values and more. Template facts can also be modified once asserted.
import clips
template_string = """
(deftemplate person
(slot name (type STRING))
(slot surname (type STRING))
(slot birthdate (type SYMBOL)))
"""
env = clips.Environment()
env.build(template_string)
template = env.find_template('person')
fact = template.assert_fact(name='John',
surname='Doe',
birthdate=clips.Symbol('01/01/1970'))
assert dict(fact) == {'name': 'John',
'surname': 'Doe',
'birthdate': clips.Symbol('01/01/1970')}
fact.modify_slots(name='Leeroy',
surname='Jenkins',
birthdate=clips.Symbol('11/05/2005'))
for fact in env.facts():
print(fact)
Instances¶
Objects are instantiations of specific classes. They support more features such as class inheritance and message sending.
import clips
env = clips.Environment()
class_string = """
(defclass MyClass (is-a USER)
(slot One)
(slot Two))
"""
handler_string = """
(defmessage-handler MyClass handler ()
(+ ?self:One ?self:Two))
"""
env.build(class_string)
env.build(handler_string)
defclass = env.find_class('MyClass')
instance = defclass.make_instance('instance-name', One=1, Two=2)
retval = instance.send('handler')
assert retval == 3
for instance in env.instances():
print(instance)
Evaluating CLIPS code¶
It is possible to quickly evaluate CLIPS statements retrieving their results in Python.
Create a multifield value.
import clips
env = clips.Environment()
env.eval("(create$ hammer drill saw screw pliers wrench)")
CLIPS functions can also be called directly without the need of building language specific strings.
import clips
env = clips.Environment()
env.call('create$', clips.Symbol('hammer'), 'drill', 1, 2.0)
Note
None of the above can be used to define CLIPS constructs. Use the build or load functions instead.
Defining CLIPS constructs¶
CLIPS constructs must be defined in CLIPS language. Use the load or the build functions to define the constructs within the engine.
Rule definition example.
import clips
env = clips.Environment()
rule = """
(defrule my-rule
(my-fact first-slot)
=>
(printout t "My Rule fired!" crlf))
"""
env.build(rule)
for rule in env.rules():
print(rule)
Embedding Python¶
Through the define_function method, it is possible to embed Python code within the CLIPS environment.
The Python function will be accessible within CLIPS via its name as if it was defined via the deffunction construct.
In this example, Python regular expression support is added within the CLIPS engine.
import re
import clips
def regex_match(pattern, string):
"""Match pattern against string returning a multifield
with the first element containing the full match
followed by all captured groups.
"""
match = re.match(pattern, string)
if match is not None:
return (match.group(),) + match.groups()
else:
return []
env = clips.Environment()
env.define_function(regex_match)
env.eval('(regex_match "(www.)(.*)(.com)" "www.example.com")')
I/O Routers¶
CLIPS provides a system to manage I/O via a Router interface documented in the Section 9 of the Advanced Programming Guide. CLIPS routers mechanics are used, for example, to capture error messages and expose them through the CLIPSError exception.
The following example shows how CLIPS routers can be used to integrate CLIPS output with Python logging facilities.
import logging
import clips
log_format = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(level=logging.INFO, format=log_format)
env = clips.Environment()
router = clips.LoggingRouter()
env.add_router(router)
fact = env.assert_string('(foo bar baz)')
multifield = env.call('create$', 1, 2.0, clips.Symbol('three'), 'four')
env.write_router('stdout', 'New fact asserted: ', fact, '. ', 'A multifield: ', multifield, '\n')
Example output.
2019-02-10 20:36:26,669 - INFO - New fact asserted: <Fact-1>. A multifield: (1 2.0 three "four")
Memory management and objects lifecycle¶
All clipspy objects are wrappers of CLIPS data structures.
Facts and Instances implement a simple reference counting mechanism. Therefore, they can be accessed even when retracted from the engine working memory. Nevertheless, retaining these objects beyond their normal lifecycle will add pressure to the engine internal memory.
All other constructs become unusable when deleted or undefined.
Example:
template = env.find_template('some-fact')
# remove the Template from the CLIPS Environment
template.undefine() # from here on, the template object is unusable
# this will cause an error
print(template)
If the previous example is pretty straightforward, there are more subtle scenarios.
templates = tuple(env.templates())
# remove all CLIPS constructs from the environment
env.clear() # from here on, all the previously created objects are unusable
# this will cause an error
for template in templates:
print(template)
Building from sources¶
The provided Makefile takes care of retrieving the CLIPS source code and compiling the Python bindings together with it.
$ make
$ sudo make install
The following tools are required to build the sources.
gcc
make
curl
unzip
python
python-cffi
The following conditional variables are accepted by the Makefile.
PYTHON: Python interpreter to use, default python
CLIPS_SOURCE_URL: Location from where to retrieve CLIPS source code archive.
SHARED_LIBRARY_DIR: Path where to install CLIPS shared library, default /usr/lib
Manylinux Wheels¶
It is possible to build x86_64 wheels for Linux based on PEP-599 standards. Only requirement is Docker.
To build the container, issue the following command from the project root folder.
$ docker build -t clipspy-build-wheels:latest -f manylinux/Dockerfile .
The wheels can then be built within the container placing the resulting packages in the manylinux/wheelhouse folder as follows.
$ docker run --rm -v `pwd`/manylinux/wheelhouse:/io/wheelhouse clipspy-build-wheels:latest
The container takes care of building the wheel packages and running the tests.