CLIPS Python bindings

Release:0.3.3
Date:Oct 14, 2019

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

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.

It is not possible to define a template of an ordered fact. Yet it is possible to retrieve the implied template of an existing one. In this way it is possible to programmatically assert ordered facts.

import clips

env = clips.Environment()

# Assert the first ordeded-fact as string so its template can be retrieved
fact_string = "(ordered-fact 1 2 3)"
fact = env.assert_string(fact_string)

template = fact.template

assert template.implied == True

new_fact = template.new_fact()
new_fact.extend((3, 4, 5))
new_fact.assertit()

for fact in env.facts():
    print(fact)

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.

import clips

env = clips.Environment()

template_string = """
(deftemplate template-fact
  (slot template-slot (type SYMBOL)))
"""
env.build(template_string)

template = env.find_template('template-fact')

new_fact = template.new_fact()
new_fact['template-slot'] = clips.Symbol('a-symbol')
new_fact.assertit()

for fact in env.facts():
    print(fact)

Objects

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)

instance = env.make_instance('(instance-name of MyClass (One 1) (Two 2))')
assert instance['One'] == 1
assert instance['Two'] == 2
instance.send('handler')

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()

expression = "(create$ hammer drill saw screw pliers wrench)"
env.eval(expression)

Note

The eval function cannot be used to define CLIPS constructs.

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")')

Python Objects lifecycle

All clipspy objects are simple wrappers of CLIPS data structures. This means every object lifecycle is bound to the CLIPS data structure it refers to.

In most of the cases, deleting or undefining an object makes it unusable.

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-513 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