clipspyx 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clipspyx-0.1.0/LICENSE.txt +12 -0
- clipspyx-0.1.0/PKG-INFO +103 -0
- clipspyx-0.1.0/README.md +84 -0
- clipspyx-0.1.0/clipspyx/__init__.py +68 -0
- clipspyx-0.1.0/clipspyx/_clipspyx/__init__.py +11 -0
- clipspyx-0.1.0/clipspyx/agenda.py +415 -0
- clipspyx-0.1.0/clipspyx/classes.py +893 -0
- clipspyx-0.1.0/clipspyx/common.py +213 -0
- clipspyx-0.1.0/clipspyx/dsl/__init__.py +7 -0
- clipspyx-0.1.0/clipspyx/dsl/codegen.py +83 -0
- clipspyx-0.1.0/clipspyx/dsl/define.py +124 -0
- clipspyx-0.1.0/clipspyx/dsl/ir.py +216 -0
- clipspyx-0.1.0/clipspyx/dsl/parser.py +526 -0
- clipspyx-0.1.0/clipspyx/dsl/rule.py +139 -0
- clipspyx-0.1.0/clipspyx/dsl/template.py +153 -0
- clipspyx-0.1.0/clipspyx/dsl/types.py +41 -0
- clipspyx-0.1.0/clipspyx/dsl/visualize.py +485 -0
- clipspyx-0.1.0/clipspyx/environment.py +228 -0
- clipspyx-0.1.0/clipspyx/facts.py +780 -0
- clipspyx-0.1.0/clipspyx/functions.py +484 -0
- clipspyx-0.1.0/clipspyx/modules.py +270 -0
- clipspyx-0.1.0/clipspyx/routers.py +306 -0
- clipspyx-0.1.0/clipspyx/tables.py +133 -0
- clipspyx-0.1.0/clipspyx/values.py +164 -0
- clipspyx-0.1.0/clipspyx.egg-info/PKG-INFO +103 -0
- clipspyx-0.1.0/clipspyx.egg-info/SOURCES.txt +38 -0
- clipspyx-0.1.0/clipspyx.egg-info/dependency_links.txt +1 -0
- clipspyx-0.1.0/clipspyx.egg-info/requires.txt +9 -0
- clipspyx-0.1.0/clipspyx.egg-info/top_level.txt +1 -0
- clipspyx-0.1.0/pyproject.toml +43 -0
- clipspyx-0.1.0/setup.cfg +4 -0
- clipspyx-0.1.0/tests/test_70x.py +616 -0
- clipspyx-0.1.0/tests/test_agenda.py +170 -0
- clipspyx-0.1.0/tests/test_classes.py +273 -0
- clipspyx-0.1.0/tests/test_dsl.py +1479 -0
- clipspyx-0.1.0/tests/test_environment.py +199 -0
- clipspyx-0.1.0/tests/test_facts.py +251 -0
- clipspyx-0.1.0/tests/test_functions.py +107 -0
- clipspyx-0.1.0/tests/test_modules.py +78 -0
- clipspyx-0.1.0/tests/test_visualize.py +715 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (c) 2016-2023, Matteo Cafasso
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
+
|
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
+
|
|
10
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
11
|
+
|
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
clipspyx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clipspyx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLIPS Python bindings via CFFI
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Project-URL: Homepage, https://github.com/inferal-oss/clipspyx
|
|
7
|
+
Project-URL: Repository, https://github.com/inferal-oss/clipspyx
|
|
8
|
+
Project-URL: Issues, https://github.com/inferal-oss/clipspyx/issues
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Provides-Extra: 64x
|
|
13
|
+
Requires-Dist: clipspyx-ffi-64x==0.1.0; extra == "64x"
|
|
14
|
+
Provides-Extra: 70x
|
|
15
|
+
Requires-Dist: clipspyx-ffi-70x==0.1.0; extra == "70x"
|
|
16
|
+
Provides-Extra: dsl
|
|
17
|
+
Requires-Dist: libcst>=1.0; extra == "dsl"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# clipspyx
|
|
21
|
+
|
|
22
|
+
CLIPS Python bindings via CFFI. Fork of [clipspy](https://github.com/noxdafox/clipspy).
|
|
23
|
+
|
|
24
|
+
## Building
|
|
25
|
+
|
|
26
|
+
clipspyx compiles CLIPS source directly into the CFFI extension. No separate `libclips` build step.
|
|
27
|
+
|
|
28
|
+
### Prerequisites
|
|
29
|
+
|
|
30
|
+
- Python 3.10+
|
|
31
|
+
- C compiler (gcc/clang on Linux/macOS, MSVC on Windows)
|
|
32
|
+
- CLIPS source (auto-checked out from orphan branch, or provide your own)
|
|
33
|
+
|
|
34
|
+
### Install for development
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install with 64x backend (CLIPS 6.4x)
|
|
38
|
+
uv sync --extra 64x
|
|
39
|
+
|
|
40
|
+
# Install with 70x backend (CLIPS 7.0x)
|
|
41
|
+
uv sync --extra 70x
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Switching backends
|
|
45
|
+
|
|
46
|
+
The 64x and 70x backends conflict with each other. Always use `uv sync` to switch:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Switch to 64x
|
|
50
|
+
uv sync --extra 64x
|
|
51
|
+
|
|
52
|
+
# Switch to 70x
|
|
53
|
+
uv sync --extra 70x
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Important:** `uv run --extra 64x` does *not* remove a previously installed 70x backend
|
|
57
|
+
(and vice versa). Only `uv sync --extra <variant>` correctly installs the requested
|
|
58
|
+
backend and removes the conflicting one.
|
|
59
|
+
|
|
60
|
+
### CLIPS source override
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Use an arbitrary CLIPS source directory
|
|
64
|
+
CLIPS_SOURCE_DIR=/path/to/clips/core uv sync --extra 64x
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Build distributable wheels
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run scripts/build-backend.py 64x # builds clipspyx-ffi + clipspyx-ffi-64x wheels
|
|
71
|
+
uv run scripts/build-backend.py 70x # builds clipspyx-ffi + clipspyx-ffi-70x wheels
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### CLIPS Source Management
|
|
75
|
+
|
|
76
|
+
CLIPS source is stored in git orphan branches (`clips-64x`, `clips-70x`).
|
|
77
|
+
Sync from SVN:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run scripts/sync-svn.py 64x # syncs core/ -> orphan branch clips-64x
|
|
81
|
+
uv run scripts/sync-svn.py 70x # syncs core/ -> orphan branch clips-70x
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import clipspyx
|
|
88
|
+
|
|
89
|
+
env = clipspyx.Environment()
|
|
90
|
+
env.build('(defrule hello (initial-fact) => (println "Hello CLIPS!"))')
|
|
91
|
+
env.reset()
|
|
92
|
+
env.run()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv run pytest tests/ -v
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
BSD-3-Clause. See [LICENSE.txt](LICENSE.txt).
|
clipspyx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# clipspyx
|
|
2
|
+
|
|
3
|
+
CLIPS Python bindings via CFFI. Fork of [clipspy](https://github.com/noxdafox/clipspy).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
clipspyx compiles CLIPS source directly into the CFFI extension. No separate `libclips` build step.
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
|
|
11
|
+
- Python 3.10+
|
|
12
|
+
- C compiler (gcc/clang on Linux/macOS, MSVC on Windows)
|
|
13
|
+
- CLIPS source (auto-checked out from orphan branch, or provide your own)
|
|
14
|
+
|
|
15
|
+
### Install for development
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install with 64x backend (CLIPS 6.4x)
|
|
19
|
+
uv sync --extra 64x
|
|
20
|
+
|
|
21
|
+
# Install with 70x backend (CLIPS 7.0x)
|
|
22
|
+
uv sync --extra 70x
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Switching backends
|
|
26
|
+
|
|
27
|
+
The 64x and 70x backends conflict with each other. Always use `uv sync` to switch:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Switch to 64x
|
|
31
|
+
uv sync --extra 64x
|
|
32
|
+
|
|
33
|
+
# Switch to 70x
|
|
34
|
+
uv sync --extra 70x
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Important:** `uv run --extra 64x` does *not* remove a previously installed 70x backend
|
|
38
|
+
(and vice versa). Only `uv sync --extra <variant>` correctly installs the requested
|
|
39
|
+
backend and removes the conflicting one.
|
|
40
|
+
|
|
41
|
+
### CLIPS source override
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Use an arbitrary CLIPS source directory
|
|
45
|
+
CLIPS_SOURCE_DIR=/path/to/clips/core uv sync --extra 64x
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Build distributable wheels
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv run scripts/build-backend.py 64x # builds clipspyx-ffi + clipspyx-ffi-64x wheels
|
|
52
|
+
uv run scripts/build-backend.py 70x # builds clipspyx-ffi + clipspyx-ffi-70x wheels
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### CLIPS Source Management
|
|
56
|
+
|
|
57
|
+
CLIPS source is stored in git orphan branches (`clips-64x`, `clips-70x`).
|
|
58
|
+
Sync from SVN:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv run scripts/sync-svn.py 64x # syncs core/ -> orphan branch clips-64x
|
|
62
|
+
uv run scripts/sync-svn.py 70x # syncs core/ -> orphan branch clips-70x
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import clipspyx
|
|
69
|
+
|
|
70
|
+
env = clipspyx.Environment()
|
|
71
|
+
env.build('(defrule hello (initial-fact) => (println "Hello CLIPS!"))')
|
|
72
|
+
env.reset()
|
|
73
|
+
env.run()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Testing
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv run pytest tests/ -v
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
BSD-3-Clause. See [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2016-2025, Matteo Cafasso
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
# this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
# and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
|
16
|
+
# specific prior written permission.
|
|
17
|
+
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
20
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
21
|
+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
|
22
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
23
|
+
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
24
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
25
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
26
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
27
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
28
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__author__ = 'Matteo Cafasso'
|
|
32
|
+
__version__ = '0.1.0'
|
|
33
|
+
__license__ = 'BSD-3'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ('CLIPSError',
|
|
37
|
+
'CLIPS_MAJOR',
|
|
38
|
+
'Environment',
|
|
39
|
+
'Router',
|
|
40
|
+
'LoggingRouter',
|
|
41
|
+
'ImpliedFact',
|
|
42
|
+
'TemplateFact',
|
|
43
|
+
'Template',
|
|
44
|
+
'Instance',
|
|
45
|
+
'InstanceName',
|
|
46
|
+
'Class',
|
|
47
|
+
'Strategy',
|
|
48
|
+
'SalienceEvaluation',
|
|
49
|
+
'Verbosity',
|
|
50
|
+
'ClassDefaultMode',
|
|
51
|
+
'TemplateSlotDefaultType',
|
|
52
|
+
'Symbol',
|
|
53
|
+
'InstanceName',
|
|
54
|
+
'SaveMode')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
from clipspyx.environment import Environment
|
|
58
|
+
from clipspyx.classes import Instance, Class
|
|
59
|
+
from clipspyx.values import Symbol, InstanceName
|
|
60
|
+
from clipspyx.routers import Router, LoggingRouter
|
|
61
|
+
from clipspyx.facts import ImpliedFact, TemplateFact, Template
|
|
62
|
+
from clipspyx.common import SaveMode, Strategy, SalienceEvaluation, Verbosity
|
|
63
|
+
from clipspyx.common import CLIPSError, ClassDefaultMode, TemplateSlotDefaultType
|
|
64
|
+
from clipspyx.common import CLIPS_MAJOR
|
|
65
|
+
|
|
66
|
+
if CLIPS_MAJOR >= 7:
|
|
67
|
+
from clipspyx.tables import Deftable
|
|
68
|
+
__all__ = __all__ + ('Deftable',)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Redirect imports to the appropriate CLIPS FFI backend."""
|
|
2
|
+
try:
|
|
3
|
+
from clipspyx_ffi_70x._clipspyx import lib, ffi # noqa: F401
|
|
4
|
+
except ImportError:
|
|
5
|
+
try:
|
|
6
|
+
from clipspyx_ffi_64x._clipspyx import lib, ffi # noqa: F401
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise ImportError(
|
|
9
|
+
"No CLIPS FFI backend found. "
|
|
10
|
+
"Install clipspyx[64x] or clipspyx[70x]."
|
|
11
|
+
) from None
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Copyright (c) 2016-2025, Matteo Cafasso
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
# this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
# and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
|
16
|
+
# specific prior written permission.
|
|
17
|
+
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
20
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
21
|
+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
|
22
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
23
|
+
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
24
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
25
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
26
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
27
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
28
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
"""This module contains the definition of:
|
|
31
|
+
|
|
32
|
+
* Agenda class
|
|
33
|
+
* Rule class
|
|
34
|
+
* Activation class
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import clipspyx
|
|
39
|
+
|
|
40
|
+
from clipspyx.modules import Module
|
|
41
|
+
from clipspyx.common import environment_builder
|
|
42
|
+
from clipspyx.common import CLIPSError, Strategy, SalienceEvaluation, Verbosity
|
|
43
|
+
|
|
44
|
+
from clipspyx._clipspyx import lib, ffi
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Rule:
|
|
48
|
+
"""A CLIPS rule.
|
|
49
|
+
|
|
50
|
+
In CLIPS, Rules are defined via the (defrule) statement.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
__slots__ = '_env', '_name'
|
|
55
|
+
|
|
56
|
+
def __init__(self, env: ffi.CData, name: str):
|
|
57
|
+
self._env = env
|
|
58
|
+
self._name = name.encode()
|
|
59
|
+
|
|
60
|
+
def __hash__(self):
|
|
61
|
+
return hash(self._ptr())
|
|
62
|
+
|
|
63
|
+
def __eq__(self, rule):
|
|
64
|
+
return self._ptr() == rule._ptr()
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
string = lib.DefrulePPForm(self._ptr())
|
|
68
|
+
string = ffi.string(string).decode() if string != ffi.NULL else ''
|
|
69
|
+
|
|
70
|
+
return ' '.join(string.split())
|
|
71
|
+
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
string = lib.DefrulePPForm(self._ptr())
|
|
74
|
+
string = ffi.string(string).decode() if string != ffi.NULL else ''
|
|
75
|
+
|
|
76
|
+
return "%s: %s" % (self.__class__.__name__, ' '.join(string.split()))
|
|
77
|
+
|
|
78
|
+
def _ptr(self) -> ffi.CData:
|
|
79
|
+
rule = lib.FindDefrule(self._env, self._name)
|
|
80
|
+
if rule == ffi.NULL:
|
|
81
|
+
raise CLIPSError(self._env, 'Rule <%s> not defined' % self.name)
|
|
82
|
+
|
|
83
|
+
return rule
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def name(self) -> str:
|
|
87
|
+
"""Rule name."""
|
|
88
|
+
return self._name.decode()
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def module(self) -> Module:
|
|
92
|
+
"""The module in which the Rule is defined.
|
|
93
|
+
|
|
94
|
+
Equivalent to the CLIPS (defrule-module) function.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
name = ffi.string(lib.DefruleModule(self._ptr())).decode()
|
|
98
|
+
|
|
99
|
+
return Module(self._env, name)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def deletable(self) -> bool:
|
|
103
|
+
"""True if the Rule can be deleted."""
|
|
104
|
+
return lib.DefruleIsDeletable(self._ptr())
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def watch_firings(self) -> bool:
|
|
108
|
+
"""Whether or not the Rule firings are being watched."""
|
|
109
|
+
return lib.DefruleGetWatchFirings(self._ptr())
|
|
110
|
+
|
|
111
|
+
@watch_firings.setter
|
|
112
|
+
def watch_firings(self, flag: bool):
|
|
113
|
+
"""Whether or not the Rule firings are being watched."""
|
|
114
|
+
lib.DefruleSetWatchFirings(self._ptr(), flag)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def watch_activations(self) -> bool:
|
|
118
|
+
"""Whether or not the Rule Activations are being watched."""
|
|
119
|
+
return lib.DefruleGetWatchActivations(self._ptr())
|
|
120
|
+
|
|
121
|
+
@watch_activations.setter
|
|
122
|
+
def watch_activations(self, flag: bool):
|
|
123
|
+
"""Whether or not the Rule Activations are being watched."""
|
|
124
|
+
lib.DefruleSetWatchActivations(self._ptr(), flag)
|
|
125
|
+
|
|
126
|
+
def matches(self, verbosity: Verbosity = Verbosity.TERSE):
|
|
127
|
+
"""Shows partial matches and activations.
|
|
128
|
+
|
|
129
|
+
Returns a tuple containing the combined sum of the matches
|
|
130
|
+
for each pattern, the combined sum of partial matches
|
|
131
|
+
and the number of activations.
|
|
132
|
+
|
|
133
|
+
The verbosity parameter controls how much to output:
|
|
134
|
+
|
|
135
|
+
* Verbosity.VERBOSE: detailed matches are printed to stdout
|
|
136
|
+
* Verbosity.SUCCINT: a brief description is printed to stdout
|
|
137
|
+
* Verbosity.TERSE: (default) nothing is printed to stdout
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
value = clipspyx.values.clips_value(self._env)
|
|
141
|
+
|
|
142
|
+
lib.Matches(self._ptr(), verbosity, value)
|
|
143
|
+
|
|
144
|
+
return clipspyx.values.python_value(self._env, value)
|
|
145
|
+
|
|
146
|
+
def refresh(self):
|
|
147
|
+
"""Refresh the Rule.
|
|
148
|
+
|
|
149
|
+
Equivalent to the CLIPS (refresh) function.
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
lib.Refresh(self._ptr())
|
|
153
|
+
|
|
154
|
+
def add_breakpoint(self):
|
|
155
|
+
"""Add a breakpoint for the Rule.
|
|
156
|
+
|
|
157
|
+
Equivalent to the CLIPS (add-break) function.
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
lib.SetBreak(self._ptr())
|
|
161
|
+
|
|
162
|
+
def remove_breakpoint(self):
|
|
163
|
+
"""Remove a breakpoint for the Rule.
|
|
164
|
+
|
|
165
|
+
Equivalent to the CLIPS (remove-break) function.
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
if not lib.RemoveBreak(self._env, self._ptr()):
|
|
169
|
+
raise CLIPSError("No breakpoint set")
|
|
170
|
+
|
|
171
|
+
def undefine(self):
|
|
172
|
+
"""Undefine the Rule.
|
|
173
|
+
|
|
174
|
+
Equivalent to the CLIPS (undefrule) function.
|
|
175
|
+
|
|
176
|
+
The object becomes unusable after this method has been called.
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
if not lib.Undefrule(self._ptr(), self._env):
|
|
180
|
+
raise CLIPSError(self._env)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Activation:
|
|
184
|
+
"""When all the constraints of a Rule are satisfied,
|
|
185
|
+
the Rule becomes active.
|
|
186
|
+
|
|
187
|
+
Activations are organized within the CLIPS Agenda.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def __init__(self, env: ffi.CData, act: ffi.CData):
|
|
192
|
+
self._env = env
|
|
193
|
+
self._act = act
|
|
194
|
+
self._pp = activation_pp_string(self._env, self._act)
|
|
195
|
+
self._rule_name = ffi.string(lib.ActivationRuleName(self._act))
|
|
196
|
+
|
|
197
|
+
def __hash__(self):
|
|
198
|
+
return hash(self._act)
|
|
199
|
+
|
|
200
|
+
def __eq__(self, act):
|
|
201
|
+
return self._act == act._act
|
|
202
|
+
|
|
203
|
+
def __str__(self):
|
|
204
|
+
return ' '.join(self._pp.split())
|
|
205
|
+
|
|
206
|
+
def __repr__(self):
|
|
207
|
+
return "%s: %s" % (self.__class__.__name__, ' '.join(self._pp.split()))
|
|
208
|
+
|
|
209
|
+
def _assert_is_active(self):
|
|
210
|
+
"""As the engine does not provide means to find activations,
|
|
211
|
+
the existence of the pointer in the activations list is tested instead.
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
activations = []
|
|
215
|
+
activation = lib.GetNextActivation(self._env, ffi.NULL)
|
|
216
|
+
|
|
217
|
+
while activation != ffi.NULL:
|
|
218
|
+
activations.append(activation)
|
|
219
|
+
activation = lib.GetNextActivation(self._env, activation)
|
|
220
|
+
|
|
221
|
+
if self._act not in activations:
|
|
222
|
+
raise CLIPSError(
|
|
223
|
+
self._env, "Activation %s not in the agenda" % self.name)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def name(self) -> str:
|
|
227
|
+
"""Activation Rule name."""
|
|
228
|
+
return self._rule_name.decode()
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def salience(self) -> int:
|
|
232
|
+
"""Activation salience value."""
|
|
233
|
+
self._assert_is_active()
|
|
234
|
+
return lib.ActivationGetSalience(self._act)
|
|
235
|
+
|
|
236
|
+
@salience.setter
|
|
237
|
+
def salience(self, salience: int):
|
|
238
|
+
"""Activation salience value."""
|
|
239
|
+
self._assert_is_active()
|
|
240
|
+
lib.ActivationSetSalience(self._act, salience)
|
|
241
|
+
|
|
242
|
+
def delete(self):
|
|
243
|
+
"""Remove the activation from the agenda."""
|
|
244
|
+
self._assert_is_active()
|
|
245
|
+
lib.DeleteActivation(self._act)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class Agenda:
|
|
249
|
+
"""In CLIPS, when all the conditions to activate a rule are met,
|
|
250
|
+
The Rule action is placed within the Agenda.
|
|
251
|
+
|
|
252
|
+
The CLIPS Agenda is responsible of sorting the Rule Activations
|
|
253
|
+
according to their salience and the conflict resolution strategy.
|
|
254
|
+
|
|
255
|
+
.. note::
|
|
256
|
+
|
|
257
|
+
All the Agenda methods are accessible through the Environment class.
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
def __init__(self, env: ffi.CData):
|
|
262
|
+
self._env = env
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def agenda_changed(self) -> bool:
|
|
266
|
+
"""True if any rule activation changes have occurred."""
|
|
267
|
+
value = lib.GetAgendaChanged(self._env)
|
|
268
|
+
lib.SetAgendaChanged(self._env, False)
|
|
269
|
+
|
|
270
|
+
return value
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def focus(self) -> Module:
|
|
274
|
+
"""The module associated with the current focus.
|
|
275
|
+
|
|
276
|
+
Equivalent to the CLIPS (get-focus) function.
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
current_focus = lib.GetFocus(self._env)
|
|
280
|
+
if current_focus != ffi.NULL:
|
|
281
|
+
name = ffi.string(lib.DefmoduleName(current_focus)).decode()
|
|
282
|
+
|
|
283
|
+
return Module(self._env, name)
|
|
284
|
+
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
@focus.setter
|
|
288
|
+
def focus(self, module: Module):
|
|
289
|
+
"""The module associated with the current focus.
|
|
290
|
+
|
|
291
|
+
Equivalent to the CLIPS (get-focus) function.
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
return lib.Focus(module._ptr())
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def strategy(self) -> Strategy:
|
|
298
|
+
"""The current conflict resolution strategy.
|
|
299
|
+
|
|
300
|
+
Equivalent to the CLIPS (get-strategy) function.
|
|
301
|
+
|
|
302
|
+
"""
|
|
303
|
+
return Strategy(lib.GetStrategy(self._env))
|
|
304
|
+
|
|
305
|
+
@strategy.setter
|
|
306
|
+
def strategy(self, value: Strategy):
|
|
307
|
+
"""The current conflict resolution strategy.
|
|
308
|
+
|
|
309
|
+
Equivalent to the CLIPS (get-strategy) function.
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
lib.SetStrategy(self._env, Strategy(value))
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def salience_evaluation(self) -> SalienceEvaluation:
|
|
316
|
+
"""The salience evaluation behavior.
|
|
317
|
+
|
|
318
|
+
Equivalent to the CLIPS (get-salience-evaluation) command.
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
return SalienceEvaluation(lib.GetSalienceEvaluation(self._env))
|
|
322
|
+
|
|
323
|
+
@salience_evaluation.setter
|
|
324
|
+
def salience_evaluation(self, value: SalienceEvaluation):
|
|
325
|
+
"""The salience evaluation behavior.
|
|
326
|
+
|
|
327
|
+
Equivalent to the CLIPS (get-salience-evaluation) command.
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
lib.SetSalienceEvaluation(self._env, SalienceEvaluation(value))
|
|
331
|
+
|
|
332
|
+
def rules(self) -> iter:
|
|
333
|
+
"""Iterate over the defined Rules."""
|
|
334
|
+
rule = lib.GetNextDefrule(self._env, ffi.NULL)
|
|
335
|
+
|
|
336
|
+
while rule != ffi.NULL:
|
|
337
|
+
name = ffi.string(lib.DefruleName(rule)).decode()
|
|
338
|
+
yield Rule(self._env, name)
|
|
339
|
+
|
|
340
|
+
rule = lib.GetNextDefrule(self._env, rule)
|
|
341
|
+
|
|
342
|
+
def find_rule(self, name: str) -> Rule:
|
|
343
|
+
"""Find a Rule by name."""
|
|
344
|
+
defrule = lib.FindDefrule(self._env, name.encode())
|
|
345
|
+
if defrule == ffi.NULL:
|
|
346
|
+
raise LookupError("Rule '%s' not found" % name)
|
|
347
|
+
|
|
348
|
+
return Rule(self._env, name)
|
|
349
|
+
|
|
350
|
+
def reorder(self, module: Module = None):
|
|
351
|
+
"""Reorder the Activations in the Agenda.
|
|
352
|
+
|
|
353
|
+
If no Module is specified, the agendas of all modules are reordered.
|
|
354
|
+
|
|
355
|
+
To be called after changing the conflict resolution strategy.
|
|
356
|
+
|
|
357
|
+
"""
|
|
358
|
+
if module is not None:
|
|
359
|
+
lib.ReorderAgenda(module._ptr())
|
|
360
|
+
else:
|
|
361
|
+
lib.ReorderAllAgendas(self._env)
|
|
362
|
+
|
|
363
|
+
def refresh(self, module: Module = None):
|
|
364
|
+
"""Recompute the salience values of the Activations on the Agenda
|
|
365
|
+
and then reorder the agenda.
|
|
366
|
+
|
|
367
|
+
Equivalent to the CLIPS (refresh-agenda) function.
|
|
368
|
+
|
|
369
|
+
If no Module is specified, the agendas of all modules are refreshed.
|
|
370
|
+
|
|
371
|
+
"""
|
|
372
|
+
if module is not None:
|
|
373
|
+
lib.RefreshAgenda(module._ptr())
|
|
374
|
+
else:
|
|
375
|
+
lib.RefreshAllAgendas(self._env)
|
|
376
|
+
|
|
377
|
+
def activations(self) -> iter:
|
|
378
|
+
"""Iterate over the Activations in the Agenda."""
|
|
379
|
+
activation = lib.GetNextActivation(self._env, ffi.NULL)
|
|
380
|
+
|
|
381
|
+
while activation != ffi.NULL:
|
|
382
|
+
yield Activation(self._env, activation)
|
|
383
|
+
|
|
384
|
+
activation = lib.GetNextActivation(self._env, activation)
|
|
385
|
+
|
|
386
|
+
def delete_activations(self):
|
|
387
|
+
"""Delete all activations in the agenda."""
|
|
388
|
+
if not lib.DeleteActivation(self._env, ffi.NULL):
|
|
389
|
+
raise CLIPSError(self._env)
|
|
390
|
+
|
|
391
|
+
def clear_focus(self):
|
|
392
|
+
"""Remove all modules from the focus stack.
|
|
393
|
+
|
|
394
|
+
Equivalent to the CLIPS (clear-focus-stack) function.
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
lib.ClearFocusStack(self._env)
|
|
398
|
+
|
|
399
|
+
def run(self, limit: int = None) -> int:
|
|
400
|
+
"""Runs the activations in the agenda.
|
|
401
|
+
|
|
402
|
+
If limit is not None, the first activations up to limit will be run.
|
|
403
|
+
|
|
404
|
+
Returns the number of activation which were run.
|
|
405
|
+
|
|
406
|
+
"""
|
|
407
|
+
return lib.Run(self._env, limit if limit is not None else -1)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def activation_pp_string(env: ffi.CData, ist: ffi.CData) -> str:
|
|
411
|
+
builder = environment_builder(env, 'string')
|
|
412
|
+
lib.SBReset(builder)
|
|
413
|
+
lib.ActivationPPForm(ist, builder)
|
|
414
|
+
|
|
415
|
+
return ffi.string(builder.contents).decode()
|