prete 2.5.1__py3-none-any.whl
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.
- prete-2.5.1.dist-info/METADATA +503 -0
- prete-2.5.1.dist-info/RECORD +15 -0
- prete-2.5.1.dist-info/WHEEL +4 -0
- prete-2.5.1.dist-info/licenses/LICENSE +21 -0
- rete/__init__.py +52 -0
- rete/alpha.py +127 -0
- rete/beta.py +899 -0
- rete/condition.py +146 -0
- rete/engine.py +186 -0
- rete/fact.py +44 -0
- rete/network.py +597 -0
- rete/prl.py +834 -0
- rete/prl_ast.py +276 -0
- rete/prl_lexer.py +209 -0
- rete/prl_parser.py +617 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prete
|
|
3
|
+
Version: 2.5.1
|
|
4
|
+
Summary: Pure-Python implementation of the Rete algorithm for production rule systems
|
|
5
|
+
Project-URL: Homepage, https://github.com/stefano-bragaglia/pRETE
|
|
6
|
+
Project-URL: Source, https://github.com/stefano-bragaglia/pRETE
|
|
7
|
+
Project-URL: Changelog, https://github.com/stefano-bragaglia/pRETE/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Stefano Bragaglia <stefano-bragaglia@users.noreply.github.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: expert-system,inference,pattern-matching,production-rules,rete,rule-engine,rules
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
27
|
+
Requires-Dist: radon==6.0.1; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff==0.15.18; extra == 'dev'
|
|
29
|
+
Requires-Dist: xenon==0.9.3; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# pRETE (Python RETE)
|
|
33
|
+
|
|
34
|
+
A pure-Python implementation of the Rete algorithm for production rule systems,
|
|
35
|
+
matching over arbitrary Python objects (POPOs — Plain Old Python Objects).
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
[](https://github.com/stefano-bragaglia/pRETE/actions/workflows/ci.yml)
|
|
40
|
+
[](https://pypi.org/project/prete/)
|
|
41
|
+
[](https://pypi.org/project/prete/)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
|
|
44
|
+
> **v2.0.0 breaking change:** the `(id, attribute, value)` triple model has
|
|
45
|
+
> been replaced by Drools-style pattern matching over `@dataclass` objects.
|
|
46
|
+
> `WME`, `Condition`, and `WILDCARD` are removed; use `Fact`, `Pattern`, and
|
|
47
|
+
> `JoinSpec` instead. See [CHANGELOG.md](CHANGELOG.md).
|
|
48
|
+
|
|
49
|
+
> **v2.1.0 — pRETE Rule Language (PRL):** rules can now be written in `.prl`
|
|
50
|
+
> text files (a Python-flavoured subset of Drools Rule Language) and loaded
|
|
51
|
+
> directly into the engine via `load_prl()`. The RETE engine itself is
|
|
52
|
+
> unchanged.
|
|
53
|
+
|
|
54
|
+
> **v2.5.0 — PRL Extra Features:** ten new PRL language constructs — type
|
|
55
|
+
> inheritance (`extends`), identity keys (`@key`), positional/named constraint
|
|
56
|
+
> shorthand, `@no-loop` tag, `import` / `from … import`, `or` disjunction,
|
|
57
|
+
> `forall`, `exists`, CEP event semantics (`@role`, `@timestamp`, `@expires`),
|
|
58
|
+
> and `accumulate` with built-in aggregation functions. New engine nodes:
|
|
59
|
+
> `ExistsNode`, `AccumulateNode`; logical clock for CEP.
|
|
60
|
+
|
|
61
|
+
## Background
|
|
62
|
+
|
|
63
|
+
Implements the algorithm from:
|
|
64
|
+
- Forgy, C. L. (1982). Rete: A fast algorithm for the many pattern/many object
|
|
65
|
+
pattern match problem. *Artificial Intelligence*, 19(1), 17–37.
|
|
66
|
+
- Doorenbos, R. B. (1995). *Production system techniques for large rule bases*
|
|
67
|
+
(CMU-CS-95-113). Carnegie Mellon University.
|
|
68
|
+
|
|
69
|
+
v1.x represented working memory as `(id, attribute, value)` triples per
|
|
70
|
+
Doorenbos §2.1. v2.0 follows the Drools model: any Python object may be a
|
|
71
|
+
fact; patterns match by type then by callable field tests; variable bindings
|
|
72
|
+
are named and carried in the token.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install prete
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For development:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install -e ".[dev]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Core concepts
|
|
91
|
+
|
|
92
|
+
| Term | What it is |
|
|
93
|
+
|------|-----------|
|
|
94
|
+
| `Fact(obj)` | Wraps any Python object as a working-memory element (identity semantics — two `Fact`s wrapping equal objects are distinct) |
|
|
95
|
+
| `Pattern(type_, alpha_tests, join_tests, bindings, negated)` | Matches facts by `isinstance` check then by callable field tests |
|
|
96
|
+
| `JoinSpec(attr_of_fact, var_name)` | Compile-time cross-fact constraint declared inside a `Pattern`; resolved at join time |
|
|
97
|
+
| `Production(lhs, rhs)` | A rule: a list of `Pattern`s / `NccGroup`s and a Python callable that receives the matched `Token` |
|
|
98
|
+
| `Token` | An immutable sequence of matched `Fact`s plus a `bindings: dict[str, Any]` of named variable values |
|
|
99
|
+
| `ReteNetwork` | The compiled network; call `add_fact` / `remove_fact` / `add_production` |
|
|
100
|
+
| `InferenceEngine` | Wraps `ReteNetwork` with a select-and-fire loop; adds `update_fact` |
|
|
101
|
+
|
|
102
|
+
Variable names start with `$`. A variable binds to an object attribute on its
|
|
103
|
+
first match (`Pattern.bindings`) and must equal that value in every subsequent
|
|
104
|
+
condition that references it (`Pattern.join_tests` / `JoinSpec`).
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quick start
|
|
109
|
+
|
|
110
|
+
### Single-pattern rule
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from dataclasses import dataclass
|
|
114
|
+
from rete import Fact, Pattern, Production, ReteNetwork
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class Temperature:
|
|
118
|
+
sensor: str
|
|
119
|
+
value: float
|
|
120
|
+
|
|
121
|
+
def too_hot(obj: Temperature) -> bool:
|
|
122
|
+
return obj.value >= 80.0
|
|
123
|
+
|
|
124
|
+
net = ReteNetwork()
|
|
125
|
+
alarms = []
|
|
126
|
+
|
|
127
|
+
net.add_production(Production(
|
|
128
|
+
lhs=[Pattern(Temperature, alpha_tests=(too_hot,),
|
|
129
|
+
bindings=(("$sensor", "sensor"),))],
|
|
130
|
+
rhs=lambda token: alarms.append(token.bindings["$sensor"]),
|
|
131
|
+
))
|
|
132
|
+
|
|
133
|
+
net.add_fact(Fact(Temperature("T1", 60.0)))
|
|
134
|
+
net.add_fact(Fact(Temperature("T2", 95.0)))
|
|
135
|
+
|
|
136
|
+
for inst in net.conflict_set:
|
|
137
|
+
inst.production.rhs(inst.token)
|
|
138
|
+
|
|
139
|
+
print(alarms) # ['T2']
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Cross-fact binding
|
|
143
|
+
|
|
144
|
+
Use `bindings` to capture a variable and `JoinSpec` to require it in a later
|
|
145
|
+
pattern.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from dataclasses import dataclass
|
|
149
|
+
from rete import Fact, JoinSpec, Pattern, Production, ReteNetwork
|
|
150
|
+
|
|
151
|
+
@dataclass
|
|
152
|
+
class Color:
|
|
153
|
+
block: str
|
|
154
|
+
color: str
|
|
155
|
+
|
|
156
|
+
@dataclass
|
|
157
|
+
class Size:
|
|
158
|
+
block: str
|
|
159
|
+
size: str
|
|
160
|
+
|
|
161
|
+
def is_red(obj: Color) -> bool: return obj.color == "red"
|
|
162
|
+
def is_large(obj: Size) -> bool: return obj.size == "large"
|
|
163
|
+
|
|
164
|
+
net = ReteNetwork()
|
|
165
|
+
|
|
166
|
+
net.add_production(Production(
|
|
167
|
+
lhs=[
|
|
168
|
+
# bind $block = Color.block
|
|
169
|
+
Pattern(Color, alpha_tests=(is_red,), bindings=(("$block", "block"),)),
|
|
170
|
+
# require Size.block == $block
|
|
171
|
+
Pattern(Size, alpha_tests=(is_large,),
|
|
172
|
+
join_tests=(JoinSpec("block", "$block"),)),
|
|
173
|
+
],
|
|
174
|
+
rhs=lambda token: print(f"Block {token.bindings['$block']} is red and large"),
|
|
175
|
+
))
|
|
176
|
+
|
|
177
|
+
net.add_fact(Fact(Color("B1", "red")))
|
|
178
|
+
net.add_fact(Fact(Size("B1", "large")))
|
|
179
|
+
net.add_fact(Fact(Color("B2", "red"))) # B2 has no matching Size → no match
|
|
180
|
+
|
|
181
|
+
for inst in net.conflict_set:
|
|
182
|
+
inst.production.rhs(inst.token)
|
|
183
|
+
# Block B1 is red and large
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
> **Alpha sharing note:** two `Pattern`s that pass the **same function object**
|
|
187
|
+
> in `alpha_tests` share one alpha memory. Always use stable, module-level
|
|
188
|
+
> functions — not inline lambdas — when sharing matters.
|
|
189
|
+
|
|
190
|
+
### Negated conditions
|
|
191
|
+
|
|
192
|
+
`negated=True` makes the pattern a blocking condition: the rule fires only when
|
|
193
|
+
**no** fact satisfies it.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
@dataclass
|
|
197
|
+
class Marker:
|
|
198
|
+
block: str
|
|
199
|
+
key: str
|
|
200
|
+
|
|
201
|
+
def is_broken(obj: Marker) -> bool:
|
|
202
|
+
return obj.key == "broken"
|
|
203
|
+
|
|
204
|
+
net.add_production(Production(
|
|
205
|
+
lhs=[
|
|
206
|
+
Pattern(Color, alpha_tests=(is_red,), bindings=(("$block", "block"),)),
|
|
207
|
+
Pattern(Marker, alpha_tests=(is_broken,),
|
|
208
|
+
join_tests=(JoinSpec("block", "$block"),), negated=True),
|
|
209
|
+
],
|
|
210
|
+
rhs=lambda token: print(f"{token.bindings['$block']} is red and not broken"),
|
|
211
|
+
))
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Negated conjunctive conditions (NCC)
|
|
215
|
+
|
|
216
|
+
`NccGroup` wraps several patterns that must **not** jointly match.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from rete import NccGroup
|
|
220
|
+
|
|
221
|
+
net.add_production(Production(
|
|
222
|
+
lhs=[
|
|
223
|
+
Pattern(Color, alpha_tests=(is_red,), bindings=(("$block", "block"),)),
|
|
224
|
+
NccGroup(conditions=(
|
|
225
|
+
Pattern(Marker, alpha_tests=(is_broken,),
|
|
226
|
+
join_tests=(JoinSpec("block", "$block"),)),
|
|
227
|
+
)),
|
|
228
|
+
],
|
|
229
|
+
rhs=lambda token: print("match"),
|
|
230
|
+
))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
See `src/examples/programmatic/fraud_detection.py` for a full NCC round-trip example.
|
|
234
|
+
|
|
235
|
+
### Retraction
|
|
236
|
+
|
|
237
|
+
Removing a `Fact` automatically retracts every match that depended on it.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
f = Fact(Temperature("T3", 90.0))
|
|
241
|
+
net.add_fact(f)
|
|
242
|
+
# ... conflict set has a new entry ...
|
|
243
|
+
net.remove_fact(f)
|
|
244
|
+
# conflict set entry is gone
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Mutation — `update_fact`
|
|
248
|
+
|
|
249
|
+
POPOs are mutable. Mutate an attribute in place, then call `update_fact` to
|
|
250
|
+
resync the network (equivalent to Drools `modify`). Object identity is
|
|
251
|
+
preserved across the retract / re-assert cycle.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
engine = InferenceEngine()
|
|
255
|
+
# ... add productions and facts ...
|
|
256
|
+
fact.obj.approved = False # mutate in place
|
|
257
|
+
engine.update_fact(fact) # retract → re-assert
|
|
258
|
+
engine.run()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Inference engine — select-and-fire loop
|
|
262
|
+
|
|
263
|
+
`InferenceEngine` wraps `ReteNetwork` with a `run()` loop.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from rete import Fact, InferenceEngine, Pattern, Production
|
|
267
|
+
|
|
268
|
+
@dataclass
|
|
269
|
+
class Item:
|
|
270
|
+
name: str
|
|
271
|
+
|
|
272
|
+
engine = InferenceEngine()
|
|
273
|
+
found = []
|
|
274
|
+
|
|
275
|
+
engine.add_production(Production(
|
|
276
|
+
lhs=[Pattern(Item, bindings=(("$name", "name"),))],
|
|
277
|
+
rhs=lambda token: found.append(token.bindings["$name"]),
|
|
278
|
+
))
|
|
279
|
+
|
|
280
|
+
engine.add_fact(Fact(Item("apple")))
|
|
281
|
+
engine.add_fact(Fact(Item("banana")))
|
|
282
|
+
|
|
283
|
+
fired = engine.run()
|
|
284
|
+
print(f"Fired {fired} rule(s): {sorted(found)}")
|
|
285
|
+
# Fired 2 rule(s): ['apple', 'banana']
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The default conflict-resolution strategy is **recency** (last-added wins).
|
|
289
|
+
`InferenceEngine.fifo_strategy` is also available; pass any callable as
|
|
290
|
+
`InferenceEngine(strategy=...)` for a custom policy.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## MRO dispatch
|
|
295
|
+
|
|
296
|
+
The alpha network dispatches by `type(fact.obj).__mro__`, so a `Dog` fact
|
|
297
|
+
reaches a `Pattern(type_=Animal)` automatically. No explicit registration
|
|
298
|
+
needed.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## pRETE Rule Language (PRL)
|
|
303
|
+
|
|
304
|
+
PRL is a text notation for writing rules without touching Python — a strict
|
|
305
|
+
subset of [Drools Rule Language](https://docs.drools.org) adapted for pRETE.
|
|
306
|
+
Rules live in `.prl` files; `load_prl()` compiles them into `Production`
|
|
307
|
+
objects and hands them to the engine.
|
|
308
|
+
|
|
309
|
+
### What PRL supports
|
|
310
|
+
|
|
311
|
+
#### Core (v2.1.0)
|
|
312
|
+
|
|
313
|
+
| Construct | Example |
|
|
314
|
+
|---|---|
|
|
315
|
+
| Fact-type declaration | `declare Temperature value: double end` |
|
|
316
|
+
| OOPath pattern | `/Temperature[value >= 80]` |
|
|
317
|
+
| Traditional pattern | `Temperature(value >= 80)` |
|
|
318
|
+
| Fact binding | `$t: Temperature(value >= 80)` |
|
|
319
|
+
| Field binding | `$v: value` inside a pattern |
|
|
320
|
+
| Cross-fact join | `field == $bound_var` |
|
|
321
|
+
| Single negation | `not Temperature(value < 0)` |
|
|
322
|
+
| Conjunctive negation (NCC) | `not ( Pattern1() Pattern2() )` |
|
|
323
|
+
| Rule salience | `salience 10` |
|
|
324
|
+
| RHS helpers | `insert(obj)`, `retract(obj)`, `update(obj)` |
|
|
325
|
+
|
|
326
|
+
#### Extra features (v2.5.0)
|
|
327
|
+
|
|
328
|
+
| Construct | Example |
|
|
329
|
+
|---|---|
|
|
330
|
+
| Type inheritance | `declare Dog extends Animal` |
|
|
331
|
+
| Identity key | `@key` before a field in `declare` — custom `__eq__`/`__hash__` |
|
|
332
|
+
| Positional constraints | `Point(0, 0)` — values matched left-to-right by declaration order |
|
|
333
|
+
| Named constraints | `Point(y=0)` — any subset, any order |
|
|
334
|
+
| `@no-loop` tag | `@no-loop` before `rule` — prevents self-re-activation |
|
|
335
|
+
| Python imports | `from myapp.models import Customer` at top of `.prl` file |
|
|
336
|
+
| `or` disjunction | `PatternA() or PatternB()` — compiler expands to N productions |
|
|
337
|
+
| `forall` | `forall(Order(status=="pending"), Approval(orderId==$o.id))` |
|
|
338
|
+
| `exists` | `exists Invoice(overdue == true)` — fires once per left context |
|
|
339
|
+
| `@role(event)` / `@timestamp` / `@expires` | CEP — events expire automatically after `advance_clock(t)` |
|
|
340
|
+
| `accumulate` | `accumulate(Order($a: amount); $total: sum($a); $total > 1000)` |
|
|
341
|
+
|
|
342
|
+
### Quick start
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
// temperature_alarm.prl
|
|
346
|
+
declare Temperature
|
|
347
|
+
sensor: str
|
|
348
|
+
value: float
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
declare Alert
|
|
352
|
+
message: str
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
rule "Too Hot"
|
|
356
|
+
salience 10
|
|
357
|
+
when
|
|
358
|
+
$t: /Temperature[value >= 80]
|
|
359
|
+
then
|
|
360
|
+
insert(Alert("Sensor " + t.obj.sensor + " too hot"))
|
|
361
|
+
end
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from pathlib import Path
|
|
366
|
+
from rete import Fact, InferenceEngine, load_prl
|
|
367
|
+
|
|
368
|
+
engine = InferenceEngine()
|
|
369
|
+
types, productions = load_prl(
|
|
370
|
+
Path("temperature_alarm.prl").read_text(), engine=engine
|
|
371
|
+
)
|
|
372
|
+
for p in productions:
|
|
373
|
+
engine.add_production(p)
|
|
374
|
+
|
|
375
|
+
Temperature = types["Temperature"]
|
|
376
|
+
engine.add_fact(Fact(Temperature(sensor="S1", value=95.0)))
|
|
377
|
+
engine.run()
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### v2.5.0 feature examples
|
|
381
|
+
|
|
382
|
+
**Type inheritance** — a rule on the parent type fires for child facts:
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
declare Animal name: String end
|
|
386
|
+
declare Dog extends Animal breed: String end
|
|
387
|
+
|
|
388
|
+
rule "greet animal"
|
|
389
|
+
when
|
|
390
|
+
$a: Animal()
|
|
391
|
+
then
|
|
392
|
+
greet(a)
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**`exists`** — fires once per account regardless of how many overdue invoices exist:
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
rule "alert account"
|
|
400
|
+
when
|
|
401
|
+
$acc: Account()
|
|
402
|
+
exists Invoice(accountId == $acc.id, overdue == true)
|
|
403
|
+
then
|
|
404
|
+
alert(acc)
|
|
405
|
+
end
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**`accumulate`** — aggregate and constrain in the LHS:
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
rule "flag high spend"
|
|
412
|
+
when
|
|
413
|
+
accumulate(
|
|
414
|
+
Order($amount: amount);
|
|
415
|
+
$total: sum($amount);
|
|
416
|
+
$total > 1000
|
|
417
|
+
)
|
|
418
|
+
then
|
|
419
|
+
results.append(total)
|
|
420
|
+
end
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**CEP** — events expire automatically after the logical clock advances:
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
@role(event)
|
|
427
|
+
@expires(30s)
|
|
428
|
+
declare StockTick
|
|
429
|
+
@timestamp
|
|
430
|
+
ts: float
|
|
431
|
+
symbol: String
|
|
432
|
+
price: float
|
|
433
|
+
end
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
engine.add_fact(Fact(StockTick(ts=0.0, symbol="ACME", price=42.0)))
|
|
438
|
+
engine.advance_clock(31.0) # tick expired — retracted before next run()
|
|
439
|
+
engine.run()
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The grammar is documented in [`reference/prl-grammar.ebnf`](reference/prl-grammar.ebnf).
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Bundled examples
|
|
447
|
+
|
|
448
|
+
Examples are split into two folders:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
# src/examples/programmatic/ — pure Python, no .prl files
|
|
452
|
+
python src/examples/programmatic/blocks_world.py # §2.1 — three-pattern join
|
|
453
|
+
python src/examples/programmatic/negation.py # §2.7 — negated condition
|
|
454
|
+
python src/examples/programmatic/sharing.py # §2.3 — two productions sharing a beta node
|
|
455
|
+
python src/examples/programmatic/loan_application.py # update_fact; cross-fact binding
|
|
456
|
+
python src/examples/programmatic/temperature_alarm.py # alpha test; RHS inserts new facts
|
|
457
|
+
python src/examples/programmatic/family_tree.py # transitive inference
|
|
458
|
+
python src/examples/programmatic/fraud_detection.py # NccGroup; retraction round-trip
|
|
459
|
+
|
|
460
|
+
# src/examples/declarative/ — load rules from .prl files in declarative/prl/
|
|
461
|
+
python src/examples/declarative/blocks_world_prl.py # PRL equivalent of blocks_world
|
|
462
|
+
python src/examples/declarative/negation_prl.py
|
|
463
|
+
python src/examples/declarative/sharing_prl.py
|
|
464
|
+
python src/examples/declarative/loan_application_prl.py
|
|
465
|
+
python src/examples/declarative/temperature_alarm_prl.py
|
|
466
|
+
python src/examples/declarative/family_tree_prl.py
|
|
467
|
+
python src/examples/declarative/fraud_detection_prl.py
|
|
468
|
+
python src/examples/declarative/inheritance_prl.py # ES-1: extends
|
|
469
|
+
python src/examples/declarative/identity_key_prl.py # ES-3: @key
|
|
470
|
+
python src/examples/declarative/compact_patterns_prl.py # ES-4: positional/named constraints
|
|
471
|
+
python src/examples/declarative/self_modify_prl.py # ES-2: @no-loop
|
|
472
|
+
python src/examples/declarative/imported_types_prl.py # ES-5: import
|
|
473
|
+
python src/examples/declarative/disjunction_prl.py # ES-6: or
|
|
474
|
+
python src/examples/declarative/universal_prl.py # ES-6: forall
|
|
475
|
+
python src/examples/declarative/existence_check_prl.py # ES-7: exists
|
|
476
|
+
python src/examples/declarative/event_stream_prl.py # ES-8: CEP
|
|
477
|
+
python src/examples/declarative/aggregation_prl.py # ES-9: accumulate
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Dev
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
hatch run check # xenon (complexity A) + ruff + pytest --cov (fail-under 80)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Individual tools:
|
|
489
|
+
```bash
|
|
490
|
+
xenon --max-absolute A --max-modules A --max-average A src/ tests/
|
|
491
|
+
ruff check src/ tests/
|
|
492
|
+
pytest --cov
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## History
|
|
498
|
+
- **v2.5.1** — test suite expanded to 99%+ coverage
|
|
499
|
+
- **v2.5.0** — ten PRL language extensions: `extends`, `@key`, positional/named constraints, `@no-loop` tag, `import`, `or`/`forall`, `exists`, CEP (`@role`/`@timestamp`/`@expires`), `accumulate`; new `ExistsNode` and `AccumulateNode` beta nodes; logical clock; examples reorganised into `declarative/` and `programmatic/`
|
|
500
|
+
- **v2.1.0** — PRL parser: `load_prl()`, `.prl` files, lexer / AST / compiler pipeline
|
|
501
|
+
- **v2.0.0** — Drools-style POPO matching: `Fact`, `Pattern`, `JoinSpec`; `update_fact`; MRO dispatch; named variable bindings on `Token`
|
|
502
|
+
- **v1.0.1** — incremental fixes
|
|
503
|
+
- **v1.0.0** — triple WME model (`WME`, `Condition`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
rete/__init__.py,sha256=50r1hJHT1_8VvKLuSBtZ3XgD2xO3UtyHna-1h3R6e6g,1045
|
|
2
|
+
rete/alpha.py,sha256=blU5eokmUY7ypOOpuK2AJzRSJqfKlLdj2dnlMA0TyTE,4989
|
|
3
|
+
rete/beta.py,sha256=Sqern2FJ8YHgUtTBCpxB_0CFua1xVpQCcIdh_MPFxEw,31897
|
|
4
|
+
rete/condition.py,sha256=VraQQ5pQR1D4fdjw4IpvWI0d451Se0-NKKFkHJb90LE,5402
|
|
5
|
+
rete/engine.py,sha256=WC4bYB8CrfjBYVLhuX9fowUPcts-si8rLO2LuDCG-xs,6433
|
|
6
|
+
rete/fact.py,sha256=LHJw-AQ71H5HcsZIq4AfOJ1Q-td8aYvCKLYxeJgRCjo,1223
|
|
7
|
+
rete/network.py,sha256=tfW78t8KjnxFyxJ1M7ubx9IgsPZFxmN550tlo5he-ac,22381
|
|
8
|
+
rete/prl.py,sha256=zSc7jKhZ8xWcCgv-WIHcIDjh_JAxbfOcGZ4qE7OWGhM,29493
|
|
9
|
+
rete/prl_ast.py,sha256=53tnA1cHoVkt4kFwkxDVFMbXI11-YfJQ97z0vyxYEH4,8933
|
|
10
|
+
rete/prl_lexer.py,sha256=ohydbfW8YZWJqKQh6FbC1IkEJ6AgqdPxxN1rhX2xFiI,7127
|
|
11
|
+
rete/prl_parser.py,sha256=7z65zTDf9eaxzhPom2J5pssqwByaKR4ckGSe1RsfqLI,22292
|
|
12
|
+
prete-2.5.1.dist-info/METADATA,sha256=AmHpncQxjyL-mKcOB-p6x9o5M00Q5iWDJpCq6hKxX6I,16420
|
|
13
|
+
prete-2.5.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
14
|
+
prete-2.5.1.dist-info/licenses/LICENSE,sha256=Jh-H3Km2nsWIL9fCg17ABcOkEraKHnRczvHq--UGBl0,1074
|
|
15
|
+
prete-2.5.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stefano Bragaglia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rete/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Pure-Python implementation of the Rete algorithm (Forgy 1982, Doorenbos 1995)."""
|
|
2
|
+
|
|
3
|
+
from rete.alpha import AlphaMemory, RootNode
|
|
4
|
+
from rete.beta import (
|
|
5
|
+
BaseJoinNode,
|
|
6
|
+
BetaMemory,
|
|
7
|
+
DummyTopNode,
|
|
8
|
+
Instantiation,
|
|
9
|
+
JoinNode,
|
|
10
|
+
JoinTest,
|
|
11
|
+
LeftNode,
|
|
12
|
+
NccNode,
|
|
13
|
+
NccPartnerNode,
|
|
14
|
+
NccToken,
|
|
15
|
+
NegativeJoinNode,
|
|
16
|
+
NegativeToken,
|
|
17
|
+
PNode,
|
|
18
|
+
RightNode,
|
|
19
|
+
)
|
|
20
|
+
from rete.condition import JoinSpec, NccGroup, Pattern, Production
|
|
21
|
+
from rete.engine import InferenceEngine
|
|
22
|
+
from rete.fact import Fact, Token
|
|
23
|
+
from rete.network import ReteNetwork
|
|
24
|
+
from rete.prl import load_prl
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AlphaMemory",
|
|
28
|
+
"BaseJoinNode",
|
|
29
|
+
"BetaMemory",
|
|
30
|
+
"DummyTopNode",
|
|
31
|
+
"Fact",
|
|
32
|
+
"InferenceEngine",
|
|
33
|
+
"Instantiation",
|
|
34
|
+
"JoinNode",
|
|
35
|
+
"JoinSpec",
|
|
36
|
+
"JoinTest",
|
|
37
|
+
"LeftNode",
|
|
38
|
+
"load_prl",
|
|
39
|
+
"NccGroup",
|
|
40
|
+
"NccNode",
|
|
41
|
+
"NccPartnerNode",
|
|
42
|
+
"NccToken",
|
|
43
|
+
"NegativeJoinNode",
|
|
44
|
+
"NegativeToken",
|
|
45
|
+
"Pattern",
|
|
46
|
+
"PNode",
|
|
47
|
+
"Production",
|
|
48
|
+
"ReteNetwork",
|
|
49
|
+
"RightNode",
|
|
50
|
+
"RootNode",
|
|
51
|
+
"Token",
|
|
52
|
+
]
|