CLIPS Python bindings

Release:1.0.0
Date:Aug 23, 2021

Python CFFI bindings for the ‘C’ Language Integrated Production System (CLIPS) 6.40

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
  • wget
  • 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.

Indices and tables